【Java IO流】字節流和字符流詳解


字節流和字符流

對於文件必然有讀和寫的操作,讀和寫就對應了輸入和輸出流,流又分成字節和字符流。

1.從對文件的操作來講,有讀和寫的操作——也就是輸入和輸出。

2.從流的流向來講,有輸入和輸出之分。

3.從流的內容來講,有字節和字符之分。

這篇文章先后講解IO流中的字節流和字符流的輸入和輸出操作。

一、字節流

1)輸入和輸出流

首先,字節流要進行讀和寫,也就是輸入和輸出,所以它有兩個抽象的父類InputStream、OutputStream。

  • InputStream抽象了應用程序讀取數據的方式,即輸入流。
  • OutputStream抽象了應用程序寫出數據的方式,即輸出流。

2)讀寫結束

在字節流中當讀寫結束,達到文件結尾時,稱為EOF = End或者讀到-1就讀到結尾

3)輸入流基本方法

首先我們要清楚輸入流是什么。比如通過我們的鍵盤在文本文件上輸入內容,這個過程鍵盤充當的就是輸入流,而不是輸出流。因為鍵盤的功能是將內容輸入到系統,系統再寫入到文件上。以下是輸入流的基本方法read():

 int b = in.read(); //讀取一個字節無符號填充到int低八位。-1是EOF。
 in.read(byte[] buf); //讀取數據填充到字節數組buf中。返回的是讀到的字節個數。
 in.read(byte[] buf,int start, int size)//讀取數據到字節數組buf從buf的start位置開始存放size長度分數據

其中in是InputStream抽象類的實例,可以發現這個方法和RandomAccessFile類中的read()方法差不多,因為兩者都是通過字節來讀取的。

4)輸出流基本方法

輸出流是進行寫的操作,其基本操作方法是write(),可以將此方法與輸入read()方法一 一去對應,更好理解。

1 out.write(int b)//寫出一個byte到流,b的低8位
2 out.write(byte[] buf)//將buf字節數組都寫到流
3 out.write(byte[] buf, int start,int size) //字節數組buf從start位置開始寫size長度的字節到流

 了解了InputStream、OutputStream的基本操作方法后,再來看看它們兩個的“孩子”FileInputStream和FileOutputStream。

這兩個子類具體實現了在文件上讀取和寫入數據的操作,日程編程中更多的是使用這兩個類。

二、FileInputStream和FileOutputStream類的使用

-----------------FileInputStream類的使用

1.使用read()方法讀取文件

 1 /**
 2      * 讀取指定文件內容,按照16進制輸出到控制台
 3      * 並且每輸出10個byte換行
 4      * @throws FileNotFoundException 
 5      */
 6     public static void printHex(String fileName) throws IOException{
 7         //把文件作為字節流進行讀操作
 8         FileInputStream in=new FileInputStream(fileName);
 9         int b;
10         int count=0;//計數讀到的個數
11         while((b=in.read())!=-1){
12             if(b<=0xf){
13                 //單位數前面補0
14                 System.out.println("0");
15             }
16             System.out.print(Integer.toHexString(b& 0xff)+" ");
17             if(++count%10==0){
18                 System.out.println();
19             }
20         }
21         in.close();//一定要關閉流
22     }

運行結果(隨便一個文件來測試的):

注意:

  • FileInputStream()構造函數可以通過文件名(String)也可以通過File對象。上面的案例是使用文件名來構造的。
  • (b=in.read())!=-1 通過讀到-1來判斷是否讀到文件結尾。
  • in.close() 使用完IO流的對象一定要關閉流,養成好習慣很重要。

2.使用read(byte[] buf,int start, int size)方法讀取文件

上述方法只能一個一個字節讀取,對於較大的文件效率太低,推薦使用這個方法來一次性讀取文件。

 1 public static void printHexByBytes(String fileName) throws IOException{
 2         FileInputStream in=new FileInputStream(fileName);
 3         byte[] buf=new byte[20*1024];//開辟一個20k大小的字節數組
 4         /*
 5          * 從in中批量讀取字節,放入到buf這個字節數組中
 6          * 從第0個位置開始放,最多放buf.length個
 7          * 返回的是讀到的字節個數
 8          */
 9         //一次性讀完的情況
10         int count=in.read(buf, 0, buf.length);
11         int j=1;
12         for(int i=0;i<count;i++){
13             if((buf[i]&0xff)<=0xf){
14                 //單位數前面補0
15                 System.out.print("0");
16             }
17             System.out.print(Integer.toHexString(buf[i]&0xff)+ " ");
18             if(j++%10==0){
19                 System.out.println();
20             }
21         }
22              in.close();
23     }
24 }

read(byte[] buf,int start, int size)返回的是讀到的字節個數,即buf字節數組的有效長度,所以輸出buf數組時用的長度是count而不是buf.length,因為我們不知道文件大小和數組大小的關系,上述方法適用於文件大小不超過數組大小的情況下,一次性把文件內容讀取到數組里,這里就有一個問題了,如果文件大小超過數組大小,那又該如何讀取才能把文件全部讀完呢??

我們知道讀到-1就是讀到文件末,所以還是利用while循環重復讀取直到讀到-1結束循環,把上述代碼修改后如下:

 1     public static void printHexByBytes(String fileName) throws IOException{
 2         FileInputStream in=new FileInputStream(fileName);
 3         byte[] buf=new byte[20*1024];//開辟一個20k大小的字節數組
 4         /*
 5          * 從in中批量讀取字節,放入到buf這個字節數組中
 6          * 從第0個位置開始放,最多放buf.length個
 7          * 返回的是讀到的字節個數
 8          */
 9         int j=1;
10         //一個字節數組讀不完的情況,用while循環重復利用此數組直到讀到文件末=-1
11         int b=0;
12         while((b=in.read(buf, 0, buf.length))!=-1){
13             for(int i=0;i<b;i++){
14                 if((buf[i]&0xff)<=0xf){
15                     //單位數前面補0
16                     System.out.print("0");
17                 }
18                 System.out.print(Integer.toHexString(buf[i]&0xff)+ " ");
19                 if(j++%10==0){
20                     System.out.println();
21                 }
22             }
23         }
24         in.close();
25     }
26 }

 好了,我們用一個大於數組的文件來測試一下結果(太長,只截圖末尾):

大家可以比較兩者的不同,第二種優化后更適合日常的使用,因為無論文件大小我們都可以一次性直接讀完。

-----------------FileOutputStream類的使用

FileOutputStream類和FileInputStream類的使用相類似,它實現了向文件中寫出btye數據的方法。里面的一些細節跟FileInputStream差不多的我就不提了,大家自己可以理解的。

1.構造方法

 FileOutputStream類構造時根據不同的情況可以使用不同的方法構造,如:

1 //如果該文件不存在,則直接創建,如果存在,刪除后創建
2         FileOutputStream out = new FileOutputStream("demo/new1.txt");//以路徑名稱構造
1 //如果該文件不存在,則直接創建,如果存在,在文件后追加內容
2         FileOutputStream out = new FileOutputStream("demo/new1.txt",true);

更多內容可以查詢API。

2.使用write()方法寫入文件

write()方法和read()相似,只能操作一個字節,即只能寫入一個字節。例如:

1 out.wirte(‘A’);//寫出了‘A’的低八位
2 int a=10;//wirte只能寫八位,那么寫一個int需要寫4次,每次八位
3 out.write(a>>>24);
4 out.write(a>>>16);
5 out.write(a>>>8);
6 out.wirte(a);

每次只寫一個字節,顯然是不效率的,OutputStream當然跟InputStream一樣可以直接對byte數組操作。

3.使用write(byte[] buf,int start, int size)方法寫入文件

意義:把byte[]數組從start位置到size位置結束長度的字節寫入到文件中。

語法格式和read相同,不多說明

三、FileInputStream和FileOutputStream結合案例

了解了InputStream和OutputStream的使用方法,這次結合兩者來寫一個復制文件的方法。

 1 public static void copyFile(File srcFile,File destFile)throws IOException{
 2         if(!srcFile.exists()){
 3             throw new IllegalArgumentException("文件:"+srcFile+"不存在");
 4         }
 5         if(!srcFile.isFile()){
 6             throw new IllegalArgumentException(srcFile+"不是一個文件");
 7         }
 8         FileInputStream in =new FileInputStream(srcFile);
 9         FileOutputStream out =new FileOutputStream(destFile);
10         
11         byte[] buf=new byte[8*1024];
12         int b;
13         while((b=in.read(buf, 0, buf.length))!=-1){
14             out.write(buf, 0, b);
15             out.flush();//最好加上
16         }
17         in.close();
18         out.close();
19     }

 測試文件案例:

try {
        IOUtil.copyFile(new File("C:\\Users\\acer\\workspace\\encode\\new4\\test1"), new File("C:\\Users\\acer\\workspace\\encode\\new4\\test2"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    }

運行結果:

復制成功!

四、DataInputStream和DataOutputStream的使用

 DataInputStream、DataOutputStream 是對“流”功能的擴展,可以更加方便地讀取int,long。字符等類型的數據。

對於DataOutputStream而言,它多了一些方法,如

writeInt()/wirteDouble()/writeUTF()

 這些方法其本質都是通過write()方法來完成的,這些方法都是經過包裝,方便我們的使用而來的。

 1.構造方法

以DataOutputStream為例,構造方法內的對象是OutputStream類型的對象,我們可以通過構造FileOutputStream對象來使用。

1 String file="demo/data.txt";
2         DataOutputStream dos= new DataOutputStream(new FileOutputStream(file));

2.write方法使用

1         dos.writeInt(10);
2         dos.writeInt(-10);
3         dos.writeLong(10l);
4         dos.writeDouble(10.0);
5         //采用utf-8編碼寫出
6         dos.writeUTF("中國"); 7         //采用utf-16be(java編碼格式)寫出
8         dos.writeChars("中國");

3.read方法使用

以上述的寫方法對立,看下面例子用來讀出剛剛寫的文件

 1 String file="demo/data.txt";
 2         IOUtil.printHex(file);
 3         DataInputStream dis=new DataInputStream(new FileInputStream(file));
 4         int i=dis.readInt();
 5         System.out.println(i);
 6         i=dis.readInt();
 7         System.out.println(i);
 8         long l=dis.readLong();
 9         System.out.println(l);
10         double d=dis.readDouble();
11         System.out.println(d);
12         String s= dis.readUTF();
13         System.out.println(s);
14         dis.close();

運行結果:

總結:DataInputStream和DataOutputStream其實是對FileInputStream和FileOutputStream進行了包裝,通過嵌套方便我們使用FileInputStream和FileOutputStream的讀寫操作,它們還有很多其他方法,大家可以查詢API。

注意:進行讀操作的時候如果類型不匹配會出錯!

五、字節流的緩沖流BufferredInputStresam&BufferredOutputStresam

這兩個流類為IO提供了帶緩沖區的操作,一般打開文件進行寫入或讀取操作時,都會加上緩沖,這種流模式提高了IO的性能。

從應用程序中把輸入放入文件,相當於將一缸水倒入另一個缸中:

FileOutputStream---->write()方法相當於一滴一滴地把水“轉移”過去

DataOutputStream---->write()XXX方法會方便一些,相當於一瓢一瓢地把水“轉移”過去

BufferedOutputStream---->write方法更方便,相當於一瓢一瓢水先放入一個桶中(緩沖區),再從桶中倒入到一個缸中。提高了性能,推薦使用!

上述提到過用FileInputStream和FileOutputStream結合寫的一個拷貝文件的案例,這次通過字節的緩沖流對上述案例進行修改,觀察兩者的區別和優劣。

主函數測試:

1 try {
2         long start=System.currentTimeMillis();
3         //IOUtil.copyFile(new File("C:\\Users\\acer\\Desktop\\學習路徑.docx"), new File("C:\\Users\\acer\\Desktop\\復制文本.docx"));
4         long end=System.currentTimeMillis();
5         System.out.println(end-start);
6     } catch (IOException e) {
7         // TODO Auto-generated catch block
8         e.printStackTrace();
9     }

(1)單字節進行文件的拷貝,利用帶緩沖的字節流

 1 /*
 2      * 單字節進行文件的拷貝,利用帶緩沖的字節流
 3      */
 4     public static void copyFileByBuffer(File srcFile,File destFile)throws IOException{
 5         if(!srcFile.exists()){
 6             throw new IllegalArgumentException("文件:"+srcFile+"不存在");
 7         }
 8         if(!srcFile.isFile()){
 9             throw new IllegalArgumentException(srcFile+"不是一個文件");
10         }
11         BufferedInputStream bis=new BufferedInputStream(new FileInputStream(srcFile));
12         BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(destFile));
13         int c;
14         while((c=bis.read())!=-1){
15             bos.write(c);
16             bos.flush();//刷新緩沖區
17         }
18         bis.close();
19         bos.close();
20     }

 運行結果(效率):

(2)單字節不帶緩沖進行文件拷貝

 1 /*
 2      * 單字節不帶緩沖進行文件拷貝
 3      */
 4     public static void copyFileByByte(File srcFile,File destFile)throws IOException{
 5         if(!srcFile.exists()){
 6             throw new IllegalArgumentException("文件:"+srcFile+"不存在");
 7         }
 8         if(!srcFile.isFile()){
 9             throw new IllegalArgumentException(srcFile+"不是一個文件");
10         }
11         FileInputStream in=new FileInputStream(srcFile);
12         FileOutputStream out=new FileOutputStream(destFile);
13         int c;
14         while((c=in.read())!=-1){
15             out.write(c);
16             out.flush();//不帶緩沖,可加可不加
17         }
18         in.close();
19         out.close();
20     }

運行結果(效率):

(3)批量字節進行文件的拷貝,不帶緩沖的字節流(就是上面第三點最初的案例的代碼)

 1 /*
 2      * 字節批量拷貝文件,不帶緩沖
 3      */
 4     public static void copyFile(File srcFile,File destFile)throws IOException{
 5         if(!srcFile.exists()){
 6             throw new IllegalArgumentException("文件:"+srcFile+"不存在");
 7         }
 8         if(!srcFile.isFile()){
 9             throw new IllegalArgumentException(srcFile+"不是一個文件");
10         }
11         FileInputStream in =new FileInputStream(srcFile);
12         FileOutputStream out =new FileOutputStream(destFile);
13         
14         byte[] buf=new byte[8*1024];
15         int b;
16         while((b=in.read(buf, 0, buf.length))!=-1){
17             out.write(buf, 0, b);
18             out.flush();//最好加上
19         }
20         in.close();
21         out.close();
22     }
View Code

運行結果(效率):

(4)批量字節進行文件的拷貝,帶緩沖的字節流(效率最高,推薦使用!!)

 1 /*
 2      * 多字節進行文件的拷貝,利用帶緩沖的字節流
 3      */
 4     public static void copyFileByBuffers(File srcFile,File destFile)throws IOException{
 5         if(!srcFile.exists()){
 6             throw new IllegalArgumentException("文件:"+srcFile+"不存在");
 7         }
 8         if(!srcFile.isFile()){
 9             throw new IllegalArgumentException(srcFile+"不是一個文件");
10         }
11         BufferedInputStream bis=new BufferedInputStream(new FileInputStream(srcFile));
12         BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(destFile));
13         byte[] buf=new byte[20*1024];
14         int c;
15         while((c=bis.read(buf, 0, buf.length))!=-1){
16             bos.write(buf, 0, c);
17             bos.flush();//刷新緩沖區
18         }
19         bis.close();
20         bos.close();
21     }

運行結果(效率):

注意:

  • 批量讀取或寫入字節,帶字節緩沖流的效率最高,推薦使用此方法。

  • 當使用字節緩沖流時,寫入操作完畢后必須刷新緩沖區,flush()。

  • 不使用字節緩沖流時,flush()可以不加,但是最好加上去。

六、字符流

首先我們需要了解以下概念。

1)需要了解編碼問題---->轉移至《計算機中的編碼問題

2)認識文本和文本文件

java的文本(char)是16位無符號整數,是字符的unicode編碼(雙字節編碼)

文件是byte byte byte...的數據序列

文本文件是文本(char)序列按照某種編碼方案(utf-8,utf-16be,gbk)序列化byte的存儲

3)字符流(Reader Writer)

字符的處理,一次處理一個字符;

字符的底層依然是基本的字節序列;

4)字符流的基本實現

InputStreamReader:完成byte流解析成char流,按照編碼解析。

OutputStreamWriter:提供char流到byte流,按照編碼處理。

-------------------------Reader和Writer的基本使用-------------------------------

 1 String file1="C:\\Users\\acer\\workspace\\encode\\new4\\test1";
 2         String file2="C:\\Users\\acer\\workspace\\encode\\new4\\test2";
 3         InputStreamReader isr=new InputStreamReader(new FileInputStream(file1));
 4         OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream(file2));
 5 //        int c;
 6 //        while((c=isr.read())!=-1){
 7 //            System.out.print((char)c);
 8 //        }
 9         char[] buffer=new char[8*1024];
10         int c;
11         //批量讀取,放入buffer這個字符數組,從第0個位置到數組長度
12         //返回的是讀到的字符個數
13         while((c=isr.read(buffer,0,buffer.length))!=-1){
14             String s=new String(buffer,0,c);//將char類型數組轉化為String字符串
15             System.out.println(s);
16             osw.write(buffer,0,c);
17             osw.flush();
18             //osw.write(s);
19             //osw.flush();
20         }
21         isr.close();
22         osw.close();

注意:

  • 字符流操作的是文本文件,不能操作其他類型的文件!!
  • 默認按照GBK編碼來解析(項目默認編碼),操作文本文件的時候,要寫文件本身的編碼格式(在構造函數時在后面加上編碼格式)!!
  • 字符流和字節流的區別主要是操作的對象不同,還有字符流是以字符為單位來讀取和寫入文件的,而字節流是以字節或者字節數組來進行操作的!!
  • 在使用字符流的時候要額外注意文件的編碼格式,一不小心就會造成亂碼!

七、字符流的文件讀寫流FileWriter和FileReader

跟字節流的FileInputStream和FileOutputStream類相類似,字符流也有相應的文件讀寫流FileWriter和FileReader類,這兩個類主要是對文本文件進行讀寫操作。

FileReader/FileWriter:可以直接寫文件名的路徑。

與InputStreamReader相比壞處無法指定讀取和寫出的編碼,容易出現亂碼

1 FileReader fr = new FileReader("C:\\Users\\acer\\workspace\\encode\\new4\\test1"); //輸入流
2 FileWriter fw = new FileWriter(C:\\Users\\acer\\workspace\\encode\\new4\\test2");//輸出流
1 char[] buffer=new char[8*1024];
2         int c;
3         while((c=fr.read(buffer, 0, buffer.length))!=-1){
4             fw.write(buffer, 0, c);
5             fw.flush();
6         }
7         fr.close();
8         fw.close();

注意:FileReader和FileWriter不能增加編碼參數,所以當項目和讀取文件編碼不同時,就會產生亂碼。 這種情況下,只能回歸InputStreamReader和OutputStreamWriter。

八、字符流的過濾器BufferedReader&BufferedWriter

字符流的過濾器有BufferedReader和BufferedWriter/PrintWriter

除了基本的讀寫功能外,它們還有一些特殊的功能。

  • BufferedReader----->readLine 一次讀一行,並不識別換行
  • BufferedWriter----->write 一次寫一行,需要換行
  • PrintWriter經常和BufferedReader一起使用,換行寫入比BufferedWriter更方便

定義方式:

BufferedReader br =new BufferedReader(new InputStreamReader(new FileInputStream(目錄的地址)))
BufferedWriter br =new BufferedWriter(new InputStreamWriter(new FileOutputStream(目錄的地址)))
PrintWriter pw=new PrintWriter(目錄/Writer/OutputStream/File);

使用方法:

 1 //對文件進行讀寫操作
 2         String file1="C:\\Users\\acer\\workspace\\encode\\new4\\test1";
 3         String file2="C:\\Users\\acer\\workspace\\encode\\new4\\test2";
 4         BufferedReader br = new BufferedReader(new InputStreamReader(
 5                 new FileInputStream(file1)));
 6         BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(
 7                 new FileOutputStream(file2)));
 8 String line;
 9         while((line=br.readLine())!=null){
10             System.out.println(line);//一次讀一行,並不能識別換行
11             bw.write(line);
12             //單獨寫出換行操作
13             bw.newLine();
14             bw.flush();
15 }
16                 br.close();
17         bw.close();
18 }        

在這里我們可以使用PrintWriter來代替BufferedWriter做寫操作,PrintWriter相比BufferedWriter有很多優勢:

  • 構造函數方便簡潔,使用靈活
  • 構造時可以選擇是否自動flush
  • 利用println()方法可以實現自動換行,搭配BufferedReader使用更方便

使用方法:

 1 String file1="C:\\Users\\acer\\workspace\\encode\\new4\\test1";
 2         String file2="C:\\Users\\acer\\workspace\\encode\\new4\\test2";
 3         BufferedReader br = new BufferedReader(new InputStreamReader(
 4                 new FileInputStream(file1)));
 5 PrintWriter pw=new PrintWriter(file2);
 6 //PrintWriter pw=new PrintWriter(outputStream, autoFlush);//可以指定是否自動flush
 7 String line;
 8         while((line=br.readLine())!=null){
 9             System.out.println(line);//一次讀一行,並不能識別換行
10             pw.println(line);//自動換行
11             pw.flush();//指定自動flush后不需要寫
12         }
13         br.close();
14         pw.close();
15 }

注意:

  • 可以使用BufferedReader的readLine()方法一次讀入一行,為字符串形式,用null判斷是否讀到結尾。
  • 使用BufferedWriter的write()方法寫入文件,每次寫入后需要調用flush()方法清空緩沖區;PrintWriter在構造時可以指定自動flush,不需要再調用flush方法。
  • 在寫入時需要注意寫入的數據中會丟失換行,可以在每次寫入后調用BufferedReader的newLine()方法或改用PrintWriter的println()方法補充換行。
  • 通常將PrintWriter配合BufferedWriter使用。(PrintWriter的構造方法,及使用方式更為簡單)。

-----------------更多java流的操作和內容請自行查閱API------------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM