第1關:位元組輸入/輸出流實現數據的保存和讀取 package step1; import java.io.\*; import java.util.\*; public class SortArray { public static void main(String[] args) { _/\*\* ...
第1關:位元組輸入/輸出流實現數據的保存和讀取
package step1;
import java.io.\*;
import java.util.\*;
public class SortArray {
public static void main(String[] args) {
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
// 創建保存整型數據的數組(數組大小10)
byte[] data=new byte[10];
// 給數組賦隨
Scanner sc = new Scanner(System._in_);
for (int i = 0; i \< data.length; i++) {
data[i] = sc.nextByte();
}
// 將數組元素按有小到大順序排列
Arrays._sort_(data);
try {
// 創建數據保存文件,如果文件不存在,重新創建
File file = new File("data.txt");
if (!file.exists()) {
file.createNewFile();
}
// 創建FileOutputStream和DataOutputStream 輸出流
FileOutputStream fileout = new FileOutputStream(file);
DataOutputStream dataout = new DataOutputStream(fileout);
// 利用輸出流向文件中寫入數組數據
fileout.write(data,0,data.length);
// 關閉輸出流
fileout.flush();
fileout.close();
dataout.close();
// 創建FileInputStream和DataInputStream 輸入流
FileInputStream fileinput = new FileInputStream(file);
// 利用輸入流從文件讀取數據並輸出
int n=0;
while((n=fileinput.read(data))!=-1){
for (int i = 0; i \< data.length; i++) {
if(i!=data.length-1){
System._out_.print(data[i]+"\<");
} else if (i==data.length-1) {
System._out_.print(data[i]);
}
}
}
// 關閉輸入流
fileinput.close();
} catch (IOException e) {
// 異常處理
System._out_.println("讀寫發生異常");
}
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
}
}
第2關:字元輸入/輸出流實現發送電報
package step2;
import java.io.\*;
import java.util.Scanner;
public class Encrypt {
public static void main(String[] args) throws IOException {
// 創建要發送的電報
Scanner sc = new Scanner(System._in_);
String data = sc.next();
// 將電報分割成字元數組
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
char[] array=data.toCharArray();
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
// 打開指定存放電報的文件,如果文件不存在,則創建
File file = new File("data.txt");
if(!file.exists()) {
file.createNewFile();
}
// 迴圈遍歷字元數組,將每個字元加密處理
for (int i = 0; i \< array.length; i++) {
array[i] = (char) (array[i] ^ 'q');
}
// 利用字元輸出流FileWriter將加密後的字元數組寫入文件中
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
FileWriter out=new FileWriter(file);
System._out_.println("密文:");
out.write(array,0,array.length);
out.flush();
out.close();
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
// 利用字元輸入流FileReader讀取文件,將密文輸出
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
int n;
FileReader in =new FileReader(file);
char[] buf=new char[10];
while((n=in.read(buf))!=-1){
String s=new String(buf);
System._out_.println(s);
}
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
// 利用字元輸入流FileReader讀取文件,將密文轉換為明文輸出
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
FileReader in2=new FileReader(file);
System._out_.println("明文:");
while((n=in2.read(buf))!=-1){
for(int i=0;i\<n;i++){
buf[i]=(char)(buf[i] ^ 'q');
}
String s=new String(buf);
System._out_.print(s);
}
in2.close();
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
}
}
第3關:簡單TCP通信
Client
package step3;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
Server server = new Server();
server.start();
Scanner sc = new Scanner(System._in_);
//創建客戶端Socket(s),指定伺服器端IP地址和埠號
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
Socket s=new Socket("127.0.0.1",8000);
_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
System._out_.println(dis.readUTF());
String name = sc.next();
dos.writeUTF(name);
System._out_.println(dis.readUTF());
s.close();
}
}
Server
package step3;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server extends Thread {
@Override
public void run() {
try {
//創建伺服器端ServerSocket(ss),指定埠號8000
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
ServerSocket ss=new ServerSocket(8000);
_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
Socket s = ss.accept();
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
dos.writeUTF("你已經連上伺服器了,告訴我你的姓名...");
String name = dis.readUTF();
dos.writeUTF("再見:" + name);
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
第4關:TCP通信實現奇偶數判斷
ClientPlus
package step4;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class ClientPlus {
public static void main(String[] args) {
Scanner sc = new Scanner(System._in_);
ServerPlus server = new ServerPlus();
server.start();
try {
//創建客戶端Socket(s),指定伺服器端IP地址和埠號
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
Socket s=new Socket("127.0.0.1",8000);
_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
//客戶端通過迴圈依次接收伺服器返回的結果,並輸入新的整數傳遞給伺服器
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
while(true){
System._out_.println(dis.readUTF());
String num=sc.next();
dos.writeUTF(num);
}
_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
} catch (EOFException e) {
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerPlus
package step4;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerPlus extends Thread {
@Override
public void run() {
try {
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
//創建伺服器端ServerSocket(ss),指定埠號8000
ServerSocket ss=new ServerSocket(8000);
_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
Socket s = ss.accept();
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
ReceiveThread thread = new ReceiveThread(s, dos, dis);
thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ReceiveThread
package step4;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
class ReceiveThread extends Thread {
DataOutputStream dos;
DataInputStream dis;
Socket s;
public ReceiveThread(Socket s, DataOutputStream dos, DataInputStream dis) {
this.s = s;
this.dos = dos;
this.dis = dis;
}
@Override
public void run() {
try {
dos.writeUTF("請輸入一個整數,我知道是奇數還是偶數");
while(true) {
String num = dis.readUTF();
if("-1".equals(num)) {
s.close();
break;
}
String result = (Integer._parseInt_(num)%2==0)?"偶數":"奇數";
dos.writeUTF(num + "是..." + result);
}
} catch (SocketException e) {
try {
s.close();
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (IOException e) {
System._out_.println("數據讀取異常");
}
}
}
第1關:位元組輸入/輸出流實現數據的保存和讀取
任務描述
本關任務:編寫應用程式(SortArray.java),使用位元組輸入/輸出流實現數據的保存和讀取。
相關知識
Java 流功能相關的類都封裝在 java.io包中,所以要使用流類,必須導入java.io包。數據流是 Java 進行 I/O 操作的對象,它按照不同的標準可以分為不同的類別,流的分類如圖1所示。
圖1 流的分類
- 根據數據流向的不同,主要分為輸入(Input)流和輸出(Output)流兩大類。輸入流只能從其讀取數據而不能向其寫入數據,同樣對於輸出流只能向其寫入數據而不能從其讀取數據。
- 數據流按照數據類型的不同分為位元組流和字元流。位元組流(byte stream)以位元組為單位進行數據傳輸,可用於讀寫二進位數據。位元組流類一般以InputStream/OutputStream結尾。字元流(character stream)以字元為單位進行數據傳輸,一個字元由兩個位元組組成,用於處理文本數據,支持Unicode編碼。字元流一般以Reader/Writer結尾。
Java提供了專門用於輸入/輸出功能的包java.io,其中包括5個非常重要的類,InputStream、OutputStream、Reader、Writer和File。其他與輸入/輸出有關的類均是這5個類基礎上的擴展。
File 類
藉助File対象可以獲取文件和相關目錄的屬性信息,其主要方法如表2所示。
表2 File類的主要方法
方法 | 功能 |
---|---|
String getName() | 返迴文件名 |
--- | --- |
String getPath() | 返迴文件或目錄路徑 |
String getAbsolutePath() | 返回絕對路徑 |
String getParent() | 獲取文件所在口錄的父目錄 |
boolean exists() | 文件是否存在 |
boolean canWrite() | 文件是否可寫 |
boolean canRead() | 文件是否可讀 |
boolean isFile() | 是否為一個正確定義的文件 |
boolean isDirectory() | 是否為目錄 |
long lastModified() | 文件的最後修改日期 |
long length() | 文件長度 |
boolean mkdir() | 創建當前目錄的子目錄 |
String[] list() | 列出目錄中的文件 |
boolean renameTo(File newFile) | 將文件改名為新文件名 |
void delete() | 刪除文件 |
boolean equals(File f) | 比較兩個文件或目錄是否相等 |
面向位元組的輸入與輸出流
位元組流是從InputStream(輸入流類)和OutputStream(輸出流類)派生出來的一系列類,這類流以位元組(Byte)為基本處理單位。它們除了可以用來處理二進位文件的數據之外,也可以用來處理文本文件。 註意:雖然位元組流可以操作文本文件,但不提倡這樣做,因為用位元組流操作文本文件,如果文件中有漢字,可能會出現亂碼。這是因為位元組流不能直接操作Unicode字元所致。因此Java語言不提倡使用位元組流讀寫文本文件,而建議使用字元流操作文本文件。
InputStream和OutputStream都是抽象類,不能直接使用,所以在具體應用時使用的都是由它們所派生出來的子類,用於不同情況數據的輸入/輸出操作。
(1) 類** Inputstream **是面向位元組的輸入流的根 它是所有位元組輸入流的父類,其層次結構如圖2所示:
圖2 位元組輸入流層次結構
InputStream類定義了位元組輸入流的基本操作,如讀取數據和關閉輸入流等功能。InputStream類中所有方法遇到錯誤時都會引發 IOException異常。InputStream類的常用方法如表2所示。
表2 類InputStream的方法
方法 | 功能 |
---|---|
int read() | 讀一個位元組 |
--- | --- |
int read (byte b[]) | 讀多個位元組到位元組數組 |
int read(byte[] b, int off, int len) | 讀指定長度的數據到位元組數組,數據從位元組數組的off處開始存放 |
long skip(long n) | 輸入指針跳過n個位元組 |
void mark() | 在當前指針位置處做一標記 |
void reset() | 將位置指針返回標記處 |
void close() | 關閉流 |
類OutputStream是面向位元組輸出流的根,是所有位元組輸出流的父類,其層次結構如圖 4 所示。
圖4 位元組輸出流層次結構
OutputStream類定義了位元組輸出流的基本操作,如輸出數據和關閉輸出流等功能。OutputStream類中所有方法遇到錯誤時也會引發 IOException異常。OutputStream類的常用方法如表3所示。。
表3 類OutputStream的方法
方法 | 功能 |
---|---|
void write(int b) | 將指定位元組的數據內容寫入到輸出流。註意這裡的參數b是int類型,但實際寫入到輸出流的只是b的低8位數據,高24位數據被忽略 |
--- | --- |
void write(byte b[]) | 將指定位元組數組的內容寫入輸出流 |
void write(byte b[], int off, int len) | 將指定位元組數組從 off 位置開始的 len 位元組的內容寫入到輸出流 |
void flush() | 實際的輸出流的實現從性能上考慮,往往不會將每次write操作的數據都寫到目標數據源去,而是將數據先緩存起來,再一次性寫到目標數據源。flush()方法是針對這種情況的輸出流定義的,它的作用是刷新輸出流,強行將緩衝區的內容寫入到輸出流 |
void close() | 關閉數據流,當完成對數據流的操作之後需要關閉數據流 |
(2) 流的關閉 在Java編程過程中,如果打開了外部資源(文件、資料庫連接、網路連接等),我們必須在這些外部資源使用完畢後,手動關閉它們。因為外部資源不由JVM管理,無法享用JVM的垃圾回收機制,如果我們不在編程時確保在正確的時機關閉外部資源,就會導致外部資源泄露,緊接著就會出現文件被異常占用,資料庫連接過多導致連接池溢出等諸多很嚴重的問題。 為了確保外部資源一定要被關閉,通常關閉代碼被寫入finally代碼塊中,當然我們還必須註意到關閉資源時可能拋出的異常,因此關閉流一般採用如下模式的代碼:
- FileInputStream inputStream =null;
- try{
- inputStream =newFileInputStream(newFile("test"));
- // 與流相關的操作代碼
- }catch(IOException e){
- //異常處理代碼
- }finally{
- if(inputStream !=null){
- try{
- inputStream.close();
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
在finally代碼塊中關閉流,這樣可以保證無論是正常使用還是出現異常,流最終都會被關閉。但這種書寫方式過於複雜。在JDK7中新增了try-with-resources語法,當一個外部資源對象(比如FileInputStream對象)實現了java.lang.AutoCloseable介面,那麼就可以將上面的代碼簡化為如下形式:
- try(FileInputStream inputStream =newFileInputStream(newFile("test"))){
- // 與流相關的操作代碼
- }catch(IOException e){
- //異常處理代碼
- }
創建外部資源對象的代碼放在try關鍵字後面的括弧中,當有多個資源時用";"分隔開,當這個try-catch代碼塊執行完畢後,Java會確保外部資源的close方法被調用。自動關閉資源的try語句相當於包含了隱式的finally語句塊,該語句塊會自動調用實現了AutoCloseable介面的資源對象的close()方法關閉所訪問的資源對象。
**(3) FileInputStream****和 **FileOutputStream FileInputStream和FileOutputStream分別是InputStream和OutputStream的直接子類,這兩個子類主要是負責完成對本地磁碟文件的順序輸入輸出操作的流。在進行文件的讀寫操作時會產生IOException異常,該異常必須捕獲或聲明拋出。
**(4) DataInputStream****和 **DataOutputStream 有時按位元組為基本單位進行數據的讀寫處理並不方便,如一個二進位文件中存放有100個整數值,從中讀取時,自然希望按照int為基本單位進行讀取,每次讀取一個整數值,而不是每次讀取1位元組。Java語言中按照基本數據類型進行讀寫的就是數據輸入流類DataInputStream和數據輸出流類DataOutputStream,它們分別是過濾位元組輸入流FilterInputStream和過濾位元組輸出流FilterOutputStream的子類,同時分別實現了DataInput和DataOutput介面。
在DataInput和DataOutput介面中定義了獨立於具體機器的帶格式的讀寫操作,從而實現了對不同類型數據的讀寫。DataInput介面中描述了用於從輸入流中讀取基本類型值的一組方法,即從輸入流中成組地讀取位元組,並將其視為基本類型值。而DataOutput介面中描述了將基本類型值寫入輸出流中的一組方法。
DataInputStream 是用來裝飾其它輸入流,它允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 數據類型,它允許應用程式將基本Java數據類型寫到基礎輸出流中。利用類DataInputStream和DataOutputStream處理位元組或位元組數組,實現對基本數據類型數值的讀寫,提供的讀寫方法包括read()、readByte()、readInt()、write()、writeChar()、writeBoolean()等等。
數據輸入流類DataInputStream和數據輸出流類DataOutputStream的構造方法如下:
- DataInputStream(InputStream in):建立一個新的數據輸入流,從指定的輸入流in讀數據。
- DataOutputStream(OutputStream out):建立一個新的數據輸出流,向指定的輸出流out寫數據。
面向字元的輸入與輸出流
(1) 類Reader是面向字元的輸入流的根,其提供的方法與InputStream類似,只是將基於Byte的參數改為基於Char。 (2) 類Writer是面向字元的輸出流類的根,其提供的方法與OutputStream類似,只是將基於Byte的參數改為基於Char。
文件的順序讀寫
(1) 面向位元組的文件訪問 面向位元組的文件訪問以二進位文件作為數據源,FilelnputStream類和FileOutputStream類分別用於文件的讀、寫訪問。 利用InputStream和Outputstream的方法可實現文件的讀寫操作。 可用 DataInputStream對 FilelnputStream流進行過濾;用 DataOuputStream對FileOutputStream流進行過濾,以實現具體數據類型數據的讀寫。 (2) 面向字元的文件訪問 面向字元的文件訪問以字元文件作為數據源,包括FileReader類和FileWriter類,分別用於字元文件的讀、寫訪問。 基於字元流的數據讀寫方法與基於位元組流的類似,只是將讀寫的單位由位元組改為字元,方法中的位元組數組參數相應改為字元數組即可。例如,int read(char b[])表示從文件讀數據填滿數組,返回讀到的字元數。 可用 BufferedReader對 FileReader流進行過濾;用 BufferedWriter對 FileWriter流進行過濾,其中包含newLine()方法,可用於寫入一個換行。
編程要求
根據提示,在右側編輯器補充代碼。 編寫應用程式(SortArray.java),使用位元組輸入/輸出流實現數據的保存和讀取。 要求功能如下:
- 輸入1~100之間的整型數據保存到數組(數組大小為10)中
- 將數組排序(由小到大)後的元素保存到指定的文件中
- 如果文件不存在,則創建文件
- 從文件中讀取排序後的元素並按指定格式輸出
- 利用異常處理機制處理可能發生的錯誤
測試說明
平臺會對你編寫的代碼進行測試:
測試輸入:70 94 21 43 76 60 70 37 75 80 ;
預期輸出: 21<37<43<60<70<70<75<76<80<94
第2關:字元輸入/輸出流實現發送電報
任務描述
本關任務:編寫應用程式(Encrypt.java),使用字元輸入/輸出流實現發送電報的功能。
相關知識
什麼是位元組
位元組是指一小組相鄰的二進位數位。通常是8位作為一個位元組。它是構成信息的一個小單位,並作為一個整體來參加操作,比字小,是構成字的單位。
位元組(Byte) 是一種計量單位,表示數據量的多少,它是電腦信息技術用於計量存儲容量的一種計量單位.
什麼是字元
我們想象一下,給你一串二進位碼,要你來分辨它是什麼含義,是代表數字還是字母還是漢字,你能有效的分辨嗎?
顯然不能,一般來說,我們是比較難以理解一串二進位碼代表的含義的,而且一串二進位碼是代表什麼含義也無法很直觀的表示出來。
我們比較好識別的是文字,字母和符號。
所以就有了字元,字元是指電腦中使用的文字和符號,比如1、2、3、A、B、C、~!·#¥%……—*()——+、等等。
字元在電腦中可以看做: 位元組** + **編碼表
什麼意思呢?
我們知道,電腦是只識別二進位的,但是我們日常操作電腦,需要輸入文字,字母,數字這些,我們不可能先去記住一串二進位數字,比如說A這個字母的二進位是什麼,因為這樣太麻煩,也記不住,所以 編碼表 ,就誕生了,編碼表的作用就是在我們進行輸入的時候, 將我們輸入的字元轉換成電腦能識別的二進位 ,在我們閱讀數據的時候,將二進位轉換成我們人能識別的文字字母和數字。
最先普及的就要數ASCII碼表了,ASCII碼表是美國信息交換標準代碼,是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其他西歐語言。
看到這你肯定會有疑問,這ASCII碼表只有英語和西歐語呀,那漢語呢,其他語言呢?
是的,自從ASCII碼表推出之後,很多國家也都推出了本國語言的編碼表。像中國就有GB2312,GBK等等。
現在我們一起設想一個場景,當我們編輯一個文本文件,輸入了很多字元,這些字元都用ASCII碼表編碼,然後我們查看這個文本文件的時候,是使用的GBK碼表解碼,會出現什麼問題嗎?
相信你已經有答案了,這會出現軟體開發中非常常見的問題: 亂碼 。
當我們對位元組進行編碼的時候使用的是一種編碼表,而解碼的時候使用的是另一種編碼表的時候,就會出現亂碼的問題了,是因為每一個編碼表,它的字元對應二進位的位元組是不一致的。
但是互聯網是一個互聯互通的平臺,所以如果每個國家都使用自己的一套編碼器,就會出現許多問題。
在1992年的時候,推出了UTF-8編碼規範,是一種針對Unicode的可變長度字元編碼,又稱萬國碼,UTF-8用1到6個位元組編碼Unicode字元。用在網頁上可以統一頁面顯示中文簡體繁體及其它語言(如英文,日文,韓文)。
UTF-8也是我們目前在應用開發中使用的最多的編碼格式。
Java中預設採用的是Unicode編碼格式(具體來說是UTF-16編碼)。
什麼是IO流
IO流中的IO是Input,Output,輸入和輸出的意思,是用來處理設備與設備之間的數據傳輸的,不僅能處理內部設備(比如CPU、GPU、記憶體),還能處理外部設備(比如手機和PC,客戶端與伺服器)。
在Java中定義數據 按照流向 ,分為 輸入流 和 輸出流 。
首先我們來瞭解輸入流,從字面上就很容易理解,凡是從外部流入的數據都可以通過輸入流來處理。比如讀取文件。
輸出流,就表示從內部流出的數據,比如:我們編輯了一個文本文件,當我們按下ctrl+s的時候,就將該文件從記憶體保存到了硬碟,這就是一個將數據從記憶體中輸出到硬碟的過程。
除了輸出和輸入流,流按照操作的數據還分為: 位元組流 和 字元流 。
總體結構如下圖:
輸入流
我們通過一個示例,來看看輸入流應該如何使用,首先我們在D盤下創建一個hello.txt文件。輸入文本Hello Java Hello InputStream。
在main方法中加入如下代碼:
輸出:
Hello Java Hello InputStream
代碼解釋:
這個例子我們主要目的是,讀取文件中的數據並將數據顯示在控制台。
實現步驟是:首先讀取文件轉換成文件輸入流(FileInputStream),然後定義一個位元組數組作為容器用來存儲即將讀取到的數據。fs.read(b)函數的作用是將數據讀取到b數組中,最後通過編碼表,將位元組數組編碼成字元。
輸出流
我們使用輸出流將字元串hello educoder寫入到一個文件中:
運行這段代碼,打開D盤下你會發現test.txt文件被創建了,並且文件的內容是hello educoder。
代碼解釋:
最佳實踐 上面作為示例的兩段代碼都是存在很大問題的,什麼問題呢?
因為在Java中對於流的操作是非常消耗資源的,如果我們使用流對一個資源進行操作了之後卻沒有釋放它的資源,這就會造成系統資源的浪費,如果積累了很多這種空置的資源,最後可能會導致系統崩潰。
上述代碼的最佳實踐為:
- OutputStream out =null;
- try{
- String file ="D://test.txt";
- out =newFileOutputStream(file);
- String str ="hello educoder";
- byte[] b = str.getBytes();
- out.write(b);
- out.flush();
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- if(out !=null){
- try{
- out.close();// 釋放該輸出流
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
核心就是在使用完流之後,釋放它所占用的資源。
Writer 字元流的使用很簡單,和位元組輸入流類似,以FileWriter舉例:
執行上述代碼即可看到在D盤下創建了一個名為hello.txt的文件,文件的內容為hello。
上面代碼fw.flush()和fw.close()也可以省略fw.flush(),只寫fw.close()就可以了,但是都省略是不對的,如果都省略你會發現文本沒有寫入到hello.txt文件。
Reader Reader的使用也很簡單,以FileReader為例:
輸出:
hello+ 1019個空格
使用上述代碼的會輸出hello.txt中的內容,但是會有一個問題:輸出hello的同時還輸出了1019個空格,這是什麼原因呢,如何解決這些問題呢?請你思考。
在上面我們遇到了一個問題:hello.txt文件只有五個字元,而用來存儲字元的數組有1024個字元,直接使用FileReader的read()方法讀取然後輸出就會有1019個空字元,如何來解決這個問題呢?
很容易想到的方法就是,我們定義一個長度為5的字元數組就可以了,這樣確實可以暫時解決問題,可是我們往往不知道讀取的文件有多大,如果文件中不止5個字元,而是有幾萬個字元我們又應該怎麼辦呢?
這就需要我們深入的瞭解IO流的常用函數了。
read()****方法 我們來看read方法的詳細解釋:
方法 | 參數說明 | 方法描述 |
---|---|---|
int read(char[] cbuf) | 字元數組 | 將字元流中的數據讀入到字元數組中,如果讀取到文件末尾則返回-1,否則返回讀取到的長度 |
--- | --- | --- |
理解了read方法,之前的問題就好解決了。
代碼:
- String file ="D://hello.txt";
- FileReader fr =newFileReader(file);
- char[] cbuf =newchar[1024];
- int len = fr.read(cbuf);//將數據讀入到cbuf中並返回讀取到的數據長度
- StringBuilder builder =newStringBuilder();
- builder.append(cbuf,0,len);//將cbuf 0 到len長度的數據添加到builder
- System.out.println(builder.toString());
運行這段代碼,我們會發現輸出是正確的,沒有再列印出多餘的空格。
可能我們又會有疑問了,如果文本文件大於1K,這段代碼肯定就行不通了,怎麼辦呢?
很簡單,加個迴圈就可以啦:
- String file ="D://hello.txt";
- FileReader fr =newFileReader(file);
- char[] cbuf =newchar[1024];
- int len =0;// 每次讀取的長度
- StringBuilder builder =newStringBuilder();
- while((len = fr.read(cbuf))!=-1){
- builder.append(cbuf,0,len);
- }
- System.out.println(builder.toString());
這樣修改之後我們就可以讀取任意的文件,並將其內容輸出到控制台了。
write()****方法write()方法有兩種常用的重載方法:
方法 | 參數說明 | 方法描述 |
---|---|---|
void write(String str) | 字元串 | 將字元串寫入到字元流中 |
--- | --- | --- |
void write(char[] cbuf,int off,int len) | 字元數組,偏移量(從什麼位置開始),寫入的長度 | 將字元數組按照偏移量寫入到字元流中寫入的長度和偏移量與len有關 |
理解了這兩種方法,我們現在如果要複製一個文本文件就很方便了,現在我們就來將D盤下hello.txt文件複製到E盤下,並重命名為abc.txt:
- FileReader fr =newFileReader("D://hello.txt");//定義FileReader讀取文件
- int len =0;//每次讀取的字元數量
- char[] cbuf =newchar[1024];//每次讀取數據的緩衝區
- FileWriter fw =newFileWriter("E://abc.txt");//定義FileWriter寫文件
- while((len = fr.read(cbuf))!=-1){
- fw.write(cbuf,0,len);
- }
- fw.close();//釋放資源 刷新緩衝區
- fr.close();
這段代碼就是一個邊讀邊寫的過程,運行之後我們發現E盤下已經有了abc.txt文件並且內容和hello.txt一致。
使用位元組流讀寫文件 到目前為止我們一直操作的都是文本文件,不過我們電腦中存儲的文件可不止有文本文件,還有很多其他類型的,比如圖片,視頻,等等。
如果要對非文本類型的文件進行操作,應該怎麼做呢?這個時候字元流還能不能派上用場呢?
答案是否定的,字元流只適用於操作字元類型的文件,不能操作非字元類型的。
所以這個時候應該用什麼來操作呢?
相信你已經想到了:位元組流。
是的我們需要使用位元組流來操作非字元類文件。
接下來,我們使用位元組流來複制一個圖片文件,代碼:
- FileInputStream fs =newFileInputStream("D://user.jpg");//定義文件輸入流讀取文件信息
- FileOutputStream fos =newFileOutputStream("E://new.jpg");//定義文件輸出流寫文件
- int len =0;//每次讀取數據的長度
- byte[] bys =newbyte[1024];//數據緩衝區
- while((len = fs.read(bys))!=-1){
- fos.write(bys,0, len);
- }
- //釋放資源 刷新緩衝區
- fs.close();
- fos.close();
運行即可看到E盤下生成了一個名為new.jpg的文件,且內容和user.jpg一致 可以發現上述代碼和之前的字元流很像,確實原理都是類似的。
可能學到這,你會有很多疑問:
- 位元組流既然可以用來讀取非字元構成的文件,那可以讀取字元類型的文件嗎? 答案是可以的,位元組流可以操作所有類型的文件,因為電腦中的數據都是以位元組的方式存儲的;
- 既然位元組流可以用來操作所有的文件,那還要字元流幹啥咧? 因為字元流操作字元類型的數據和文件要比位元組流快很多。
擴展 使用BufferedReader讀取字元文件的速度要比我們之前使用的位元組流和FileReader快很多,示例代碼:
- BufferedReader bf =newBufferedReader(newFileReader("D://hello.txt"));
- BufferedWriter writer =newBufferedWriter(newFileWriter("D://abc.txt"));
- String str ="";
- while((str = bf.readLine())!=null){
- writer.write(str);
- }
- bf.close();
- writer.close();
編程要求
根據提示,在右側編輯器補充代碼。
- 電報內容經過加密後使用字元輸出流存儲在指定的文件中
- 如果文件不存在,則創建文件
- 使用字元輸入流從文件中分別讀取密文和明文的內容並顯示出來
- 利用異常處理機制處理可能發生的錯誤
註意 :在對字元加密時採用了異或(q)的簡單字元加密處理,解密時也要進行逐個字元異或(q)的處理。
測試說明
平臺會對你編寫的代碼進行測試:
測試輸入: 今晚10點發動總攻
預期輸出: 密文: 亻昫@A烈厠務恊敊 明文: 今晚10點發動總攻
第3關:簡單TCP通信
任務描述
本關任務:編寫簡單的TCP通信程式。
相關知識
InetAddress類的使用
在JDK中,提供了一個與IP地址相關的InetAddress類,該類用於封裝一個IP地址,並提供了一系列與IP地址相關的方法,下表中列舉了InetAddress類的一些常用方法。
上表中,列舉了InetAddress的五個常用方法。其中,前兩個方法用於獲得該類的實例對象,第一個方法用於獲得表示指定主機的InetAddress對象,第二個方法用於獲得表示本地的InetAddress對象。通過InetAddress對象便可獲取指定主機名,IP地址等。
代碼示例:
- import java.net.InetAddress;
- publicclassInetAddressDemo{
- publicstaticvoid main(String[] args)throwsException{
- InetAddress address =InetAddress.getLocalHost();
- // 獲取主機名
- String name = address.getHostName();
- // 獲取IP地址
- String ip = address.getHostAddress();
- System.out.println(name +"---"+ ip);
- }
- }
Socket通信原理
- 客戶端和服務端分別用Socket和ServerSocket類實現連接。
- Socket的工作過程是服務端首先執行等待連接,根據指定埠建立ServerSocket 對象,通過該對象的accept方法監聽客戶連接,客戶端創建Socket對象請求與服務端的特定埠進行連接,連接成功後,雙方將建立一條Socket通道,利用Socket對象的 getInputStream()和getOutputStream()方法可得到對Socket通道進行讀寫操作的輸入/輸出流,通過流的讀寫實現客戶與伺服器的通信。
- Java Socket通信編程經常結合多線程技術,一個伺服器可以和多個客戶機建立連接,進行併發通信。
基於流套接字的客戶/伺服器模型的通信過程如圖1所示。
圖1 基於流套接字的客戶/伺服器模型的通信過程
基於流套接字的客戶/伺服器通信的基本步驟如下:
- 在伺服器端創建一個ServerSocket對象,並指定埠號,或根據需要指定允許連接的最大用戶數。
- 運行ServerSocket對象的accept()方法,等候客戶端請求。
- 客戶端創建一個Socket對象,指定連接伺服器的IP地址和埠號,向伺服器發出連接請求。
- 伺服器端接收到客戶端連接請求後,創建伺服器端Socket對象與客戶端建立連接。
- 伺服器和客戶端分別建立輸入輸出流,進行數據傳輸。
- 通信結束後,伺服器和客戶端分別關閉各自的Socket連接。
- 伺服器端程式運行結束後,調用ServerSocket對象的close()方法停止TCP連接服務。
編程要求
根據提示,在右側編輯器補充代碼,使用Socket和ServerSocket套接字編程,完成簡單的TCP通信。
測試說明
平臺會對你編寫的代碼進行測試:
測試輸入:張三
預期輸出: 你已經連上伺服器了,告訴我你的姓名... 再見:張三
第4關:TCP通信實現奇偶數判斷
任務描述
本關任務:編寫TCP通信程式完成奇偶數判斷。
相關知識
在上面簡單TCP通信實驗中,客戶端和伺服器端只能完成一次TCP通信,程式就停止運行。 如果客戶端要與伺服器進行多次TCP通信,則伺服器端需要迴圈接收客戶端發送的數據並處理。同時,為了防止因 readUTF()方法而阻塞伺服器端主線程的運行,其他客戶機不能同時連接伺服器了。因此,有必要將與每個客戶端的通信任務從主線程中分離出去,這就需要為每個客戶端創建通信子線程ReceiveThread,用於接收連接指定客戶端的數據並對此數據進行處理。 下麵通信子線程ReceiveThread通過String num = dis.readUTF()代碼接收客戶端讀取過來的整數,然後判斷該整數的奇偶性,並通過dos.writeUTF(num + "是..." + result)代碼將結果返回給客戶端。當接收的整數值為-1時,程式結束運行。 通信子線程ReceiveThread程式代碼如下所示:
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.net.Socket;
- import java.net.SocketException;
- classReceiveThreadextendsThread{
- DataOutputStream dos;
- DataInputStream dis;
- Socket s;
- publicReceiveThread(Socket s,DataOutputStream dos,DataInputStream dis){
- this.s = s;
- this.dos = dos;
- this.dis = dis;
- }
- @Override
- publicvoid run(){
- try{
- dos.writeUTF("請輸入一個整數,我知道是奇數還是偶數");
- while(true){
- String num = dis.readUTF();
- //當輸入整數為-1時,程式結束運行
- if("-1".equals(num)){
- s.close();
- break;
- }
- String result =(Integer.parseInt(num)%2==0)?"偶數":"奇數";
- dos.writeUTF(num +"是..."+ result);
- }
- }catch(SocketException e){
- try{
- s.close();
- }catch(IOException e1){
- e1.printStackTrace();
- }
- }catch(IOException e){
- System.out.println("數據讀取異常");
- }
- }
- }
客戶端通過迴圈依次接收伺服器返回的結果,並輸入新的整數傳遞給客戶端進行奇偶性判斷。
- while(true){
- System.out.println(dis.readUTF());
- String num = sc.next();
- dos.writeUTF(num);
- }
同學們也可以實現多個客戶端同時連接伺服器進行通信的程式,伺服器端相關代碼如下所示:
- publicclassServerPlus{
- publicstaticvoid main(String[] args){
- try{
- //創建伺服器端ServerSocket ,指定埠號8000
- ServerSocket ss =【補充代碼】;
- while(true){
- Socket s = ss.accept();
- DataOutputStream dos =newDataOutputStream(s.getOutputStream());
- DataInputStream dis =newDataInputStream(s.getInputStream());
- ReceiveThread thread =newReceiveThread(s, dos, dis);
- thread.start();
- }
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
由於實訓平臺限制,無法完成多客戶端相關實驗內容,請同學們查看實驗指導書自行完成多客戶端通信。
編程要求
根據提示,在右側編輯器補充代碼,對輸入的整數進行奇偶性判斷;當輸入的整數為-1時,程式結束運行。
測試說明
平臺會對你編寫的代碼進行測試:
測試輸入:1 2 3 4 -1
預期輸出: 請輸入一個整數,我知道是奇數還是偶數 1是...奇數 2是...偶數 3是...奇數 4是...偶數