對于文件必然有讀和寫的操作,讀和寫就對應了輸入和輸出流,流又分成字節和字符流。
1.從對文件的操作來講,有讀和寫的操作——也就是輸入和輸出。
2.從流的流向來講,有輸入和輸出之分。
3.從流的內容來講,有字節和字符之分。
這篇文章先后講解io流中的字節流和字符流的輸入和輸出操作。
一、字節流
1)輸入和輸出流
首先,字節流要進行讀和寫,也就是輸入和輸出,所以它有兩個抽象的父類inputstream、outputstream。
inputstream抽象了應用程序讀取數據的方式,即輸入流。
outputstream抽象了應用程序寫出數據的方式,即輸出流。
2)讀寫結束
在字節流中當讀寫結束,達到文件結尾時,稱為eof = end或者讀到-1就讀到結尾。
3)輸入流基本方法
首先我們要清楚輸入流是什么。比如通過我們的鍵盤在文本文件上輸入內容,這個過程鍵盤充當的就是輸入流,而不是輸出流。因為鍵盤的功能是將內容輸入到系統,系統再寫入到文件上。以下是輸入流的基本方法read():
1
2
3
|
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
2
3
|
out.write( int b) //寫出一個byte到流,b的低8位 out.write( byte [] buf) //將buf字節數組都寫到流 out.write( byte [] buf, int start, int size) //字節數組buf從start位置開始寫size長度的字節到流 |
了解了inputstream、outputstream的基本操作方法后,再來看看它們兩個的“孩子”fileinputstream和fileoutputstream。
這兩個子類具體實現了在文件上讀取和寫入數據的操作,日程編程中更多的是使用這兩個類。
二、fileinputstream和fileoutputstream類的使用
-----------------fileinputstream類的使用
1.使用read()方法讀取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * 讀取指定文件內容,按照16進制輸出到控制臺 * 并且每輸出10個byte換行 * @throws filenotfoundexception */ public static void printhex(string filename) throws ioexception{ //把文件作為字節流進行讀操作 fileinputstream in= new fileinputstream(filename); int b; int count= 0 ; //計數讀到的個數 while ((b=in.read())!=- 1 ){ if (b<= 0xf ){ //單位數前面補0 system.out.println( "0" ); } system.out.print(integer.tohexstring(b& 0xff )+ " " ); if (++count% 10 == 0 ){ system.out.println(); } } in.close(); //一定要關閉流 } |
運行結果(隨便一個文件來測試的):
注意:
fileinputstream()構造函數可以通過文件名(string)也可以通過file對象。上面的案例是使用文件名來構造的。
(b=in.read())!=-1 通過讀到-1來判斷是否讀到文件結尾。
in.close() 使用完io流的對象一定要關閉流,養成好習慣很重要。
2.使用read(byte[] buf,int start, int size)方法讀取文件
上述方法只能一個一個字節讀取,對于較大的文件效率太低,推薦使用這個方法來一次性讀取文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static void printhexbybytes(string filename) throws ioexception{ fileinputstream in= new fileinputstream(filename); byte [] buf= new byte [ 20 * 1024 ]; //開辟一個20k大小的字節數組 /* * 從in中批量讀取字節,放入到buf這個字節數組中 * 從第0個位置開始放,最多放buf.length個 * 返回的是讀到的字節個數 */ //一次性讀完的情況 int count=in.read(buf, 0 , buf.length); int j= 1 ; for ( int i= 0 ;i<count;i++){ if ((buf[i]& 0xff )<= 0xf ){ //單位數前面補0 system.out.print( "0" ); } system.out.print(integer.tohexstring(buf[i]& 0xff )+ " " ); if (j++% 10 == 0 ){ system.out.println(); } } in.close(); } } |
read(byte[] buf,int start, int size)返回的是讀到的字節個數,即buf字節數組的有效長度,所以輸出buf數組時用的長度是count而不是buf.length,因為我們不知道文件大小和數組大小的關系,上述方法適用于文件大小不超過數組大小的情況下,一次性把文件內容讀取到數組里,這里就有一個問題了,如果文件大小超過數組大小,那又該如何讀取才能把文件全部讀完呢??
我們知道讀到-1就是讀到文件末,所以還是利用while循環重復讀取直到讀到-1結束循環,把上述代碼修改后如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public static void printhexbybytes(string filename) throws ioexception{ fileinputstream in= new fileinputstream(filename); byte [] buf= new byte [ 20 * 1024 ]; //開辟一個20k大小的字節數組 /* * 從in中批量讀取字節,放入到buf這個字節數組中 * 從第0個位置開始放,最多放buf.length個 * 返回的是讀到的字節個數 */ int j= 1 ; //一個字節數組讀不完的情況,用while循環重復利用此數組直到讀到文件末=-1 int b= 0 ; while ((b=in.read(buf, 0 , buf.length))!=- 1 ){ for ( int i= 0 ;i<b;i++){ if ((buf[i]& 0xff )<= 0xf ){ //單位數前面補0 system.out.print( "0" ); } system.out.print(integer.tohexstring(buf[i]& 0xff )+ " " ); if (j++% 10 == 0 ){ system.out.println(); } } } in.close(); } } |
好了,我們用一個大于數組的文件來測試一下結果(太長,只截圖末尾):
大家可以比較兩者的不同,第二種優化后更適合日常的使用,因為無論文件大小我們都可以一次性直接讀完。
-----------------fileoutputstream類的使用
fileoutputstream類和fileinputstream類的使用相類似,它實現了向文件中寫出btye數據的方法。里面的一些細節跟fileinputstream差不多的我就不提了,大家自己可以理解的。
1.構造方法
fileoutputstream類構造時根據不同的情況可以使用不同的方法構造,如:
1
2
|
//如果該文件不存在,則直接創建,如果存在,刪除后創建 fileoutputstream out = new fileoutputstream( "demo/new1.txt" ); //以路徑名稱構造 |
1
2
3
|
//如果該文件不存在,則直接創建,如果存在,在文件后追加內容 fileoutputstream out = new fileoutputstream( "demo/new1.txt" , true ); 更多內容可以查詢api。 |
2.使用write()方法寫入文件
write()方法和read()相似,只能操作一個字節,即只能寫入一個字節。例如:
1
2
3
4
5
6
|
out.wirte(‘a ');//寫出了‘a' 的低八位 int a= 10 ; //wirte只能寫八位,那么寫一個int需要寫4次,每次八位 out.write(a>>> 24 ); out.write(a>>> 16 ); out.write(a>>> 8 ); out.wirte(a); |
每次只寫一個字節,顯然是不效率的,outputstream當然跟inputstream一樣可以直接對byte數組操作。
3.使用write(byte[] buf,int start, int size)方法寫入文件
意義:把byte[]數組從start位置到size位置結束長度的字節寫入到文件中。
語法格式和read相同,不多說明
三、fileinputstream和fileoutputstream結合案例
了解了inputstream和outputstream的使用方法,這次結合兩者來寫一個復制文件的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static void copyfile(file srcfile,file destfile) throws ioexception{ if (!srcfile.exists()){ throw new illegalargumentexception( "文件:" +srcfile+ "不存在" ); } if (!srcfile.isfile()){ throw new illegalargumentexception(srcfile+ "不是一個文件" ); } fileinputstream in = new fileinputstream(srcfile); fileoutputstream out = new fileoutputstream(destfile); byte [] buf= new byte [ 8 * 1024 ]; int b; while ((b=in.read(buf, 0 , buf.length))!=- 1 ){ out.write(buf, 0 , b); out.flush(); //最好加上 } in.close(); out.close(); } |
測試文件案例:
1
2
3
4
5
6
7
|
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
2
|
string file= "demo/data.txt" ; dataoutputstream dos= new dataoutputstream( new fileoutputstream(file)); |
2.write方法使用
1
2
3
4
5
6
7
8
|
dos.writeint( 10 ); dos.writeint(- 10 ); dos.writelong(10l); dos.writedouble( 10.0 ); //采用utf-8編碼寫出 dos.writeutf( "中國" ); //采用utf-16be(java編碼格式)寫出 dos.writechars( "中國" ); |
3.read方法使用
以上述的寫方法對立,看下面例子用來讀出剛剛寫的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
string file= "demo/data.txt" ; ioutil.printhex(file); datainputstream dis= new datainputstream( new fileinputstream(file)); int i=dis.readint(); system.out.println(i); i=dis.readint(); system.out.println(i); long l=dis.readlong(); system.out.println(l); double d=dis.readdouble(); system.out.println(d); string s= dis.readutf(); system.out.println(s); dis.close(); |
運行結果:
總結:datainputstream和dataoutputstream其實是對fileinputstream和fileoutputstream進行了包裝,通過嵌套方便我們使用fileinputstream和fileoutputstream的讀寫操作,它們還有很多其他方法,大家可以查詢api。
注意:進行讀操作的時候如果類型不匹配會出錯!
五、字節流的緩沖流bufferredinputstresam&bufferredoutputstresam
這兩個流類為io提供了帶緩沖區的操作,一般打開文件進行寫入或讀取操作時,都會加上緩沖,這種流模式提高了io的性能。
從應用程序中把輸入放入文件,相當于將一缸水倒入另一個缸中:
fileoutputstream---->write()方法相當于一滴一滴地把水“轉移”過去
dataoutputstream---->write()xxx方法會方便一些,相當于一瓢一瓢地把水“轉移”過去
bufferedoutputstream---->write方法更方便,相當于一瓢一瓢水先放入一個桶中(緩沖區),再從桶中倒入到一個缸中。提高了性能,推薦使用!
上述提到過用fileinputstream和fileoutputstream結合寫的一個拷貝文件的案例,這次通過字節的緩沖流對上述案例進行修改,觀察兩者的區別和優劣。
主函數測試:
1
2
3
4
5
6
7
8
9
|
try { long start=system.currenttimemillis(); //ioutil.copyfile(new file("c:\\users\\acer\\desktop\\學習路徑.docx"), new file("c:\\users\\acer\\desktop\\復制文本.docx")); long end=system.currenttimemillis(); system.out.println(end-start); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } |
(1)單字節進行文件的拷貝,利用帶緩沖的字節流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* * 單字節進行文件的拷貝,利用帶緩沖的字節流 */ public static void copyfilebybuffer(file srcfile,file destfile) throws ioexception{ if (!srcfile.exists()){ throw new illegalargumentexception( "文件:" +srcfile+ "不存在" ); } if (!srcfile.isfile()){ throw new illegalargumentexception(srcfile+ "不是一個文件" ); } bufferedinputstream bis= new bufferedinputstream( new fileinputstream(srcfile)); bufferedoutputstream bos= new bufferedoutputstream( new fileoutputstream(destfile)); int c; while ((c=bis.read())!=- 1 ){ bos.write(c); bos.flush(); //刷新緩沖區 } bis.close(); bos.close(); } |
運行結果(效率):
(2)單字節不帶緩沖進行文件拷貝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* * 單字節不帶緩沖進行文件拷貝 */ public static void copyfilebybyte(file srcfile,file destfile) throws ioexception{ if (!srcfile.exists()){ throw new illegalargumentexception( "文件:" +srcfile+ "不存在" ); } if (!srcfile.isfile()){ throw new illegalargumentexception(srcfile+ "不是一個文件" ); } fileinputstream in= new fileinputstream(srcfile); fileoutputstream out= new fileoutputstream(destfile); int c; while ((c=in.read())!=- 1 ){ out.write(c); out.flush(); //不帶緩沖,可加可不加 } in.close(); out.close(); } |
運行結果(效率):
(3)批量字節進行文件的拷貝,不帶緩沖的字節流(就是上面第三點最初的案例的代碼)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/* * 字節批量拷貝文件,不帶緩沖 */ public static void copyfile(file srcfile,file destfile) throws ioexception{ if (!srcfile.exists()){ throw new illegalargumentexception( "文件:" +srcfile+ "不存在" ); } if (!srcfile.isfile()){ throw new illegalargumentexception(srcfile+ "不是一個文件" ); } fileinputstream in = new fileinputstream(srcfile); fileoutputstream out = new fileoutputstream(destfile); byte [] buf= new byte [ 8 * 1024 ]; int b; while ((b=in.read(buf, 0 , buf.length))!=- 1 ){ out.write(buf, 0 , b); out.flush(); //最好加上 } in.close(); out.close(); } |
運行結果(效率):
(4)批量字節進行文件的拷貝,帶緩沖的字節流(效率最高,推薦使用!?。?/p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/* * 多字節進行文件的拷貝,利用帶緩沖的字節流 */ public static void copyfilebybuffers(file srcfile,file destfile) throws ioexception{ if (!srcfile.exists()){ throw new illegalargumentexception( "文件:" +srcfile+ "不存在" ); } if (!srcfile.isfile()){ throw new illegalargumentexception(srcfile+ "不是一個文件" ); } bufferedinputstream bis= new bufferedinputstream( new fileinputstream(srcfile)); bufferedoutputstream bos= new bufferedoutputstream( new fileoutputstream(destfile)); byte [] buf= new byte [ 20 * 1024 ]; int c; while ((c=bis.read(buf, 0 , buf.length))!=- 1 ){ bos.write(buf, 0 , c); bos.flush(); //刷新緩沖區 } bis.close(); bos.close(); } |
運行結果(效率):
注意:
批量讀取或寫入字節,帶字節緩沖流的效率最高,推薦使用此方法。
當使用字節緩沖流時,寫入操作完畢后必須刷新緩沖區,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
string file1= "c:\\users\\acer\\workspace\\encode\\new4\\test1" ; string file2= "c:\\users\\acer\\workspace\\encode\\new4\\test2" ; inputstreamreader isr= new inputstreamreader( new fileinputstream(file1)); outputstreamwriter osw= new outputstreamwriter( new fileoutputstream(file2)); // int c; // while((c=isr.read())!=-1){ // system.out.print((char)c); // } char [] buffer= new char [ 8 * 1024 ]; int c; //批量讀取,放入buffer這個字符數組,從第0個位置到數組長度 //返回的是讀到的字符個數 while ((c=isr.read(buffer, 0 ,buffer.length))!=- 1 ){ string s= new string(buffer, 0 ,c); //將char類型數組轉化為string字符串 system.out.println(s); osw.write(buffer, 0 ,c); osw.flush(); //osw.write(s); //osw.flush(); } isr.close(); osw.close(); |
注意:
字符流操作的是文本文件,不能操作其他類型的文件!!
默認按照gbk編碼來解析(項目默認編碼),操作文本文件的時候,要寫文件本身的編碼格式(在構造函數時在后面加上編碼格式)??!
字符流和字節流的區別主要是操作的對象不同,還有字符流是以字符為單位來讀取和寫入文件的,而字節流是以字節或者字節數組來進行操作的?。?/p>
在使用字符流的時候要額外注意文件的編碼格式,一不小心就會造成亂碼!
七、字符流的文件讀寫流filewriter和filereader
跟字節流的fileinputstream和fileoutputstream類相類似,字符流也有相應的文件讀寫流filewriter和filereader類,這兩個類主要是對文本文件進行讀寫操作。
filereader/filewriter:可以直接寫文件名的路徑。
與inputstreamreader相比壞處:無法指定讀取和寫出的編碼,容易出現亂碼。
1
2
|
filereader fr = new filereader( "c:\\users\\acer\\workspace\\encode\\new4\\test1" ); //輸入流 filewriter fw = new filewriter(c:\\users\\acer\\workspace\\encode\\new4\\test2"); //輸出流 |
1
2
3
4
5
6
7
8
|
char [] buffer= new char [ 8 * 1024 ]; int c; while ((c=fr.read(buffer, 0 , buffer.length))!=- 1 ){ fw.write(buffer, 0 , c); fw.flush(); } fr.close(); fw.close(); |
注意:filereader和filewriter不能增加編碼參數,所以當項目和讀取文件編碼不同時,就會產生亂碼。 這種情況下,只能回歸inputstreamreader和outputstreamwriter。
八、字符流的過濾器bufferedreader&bufferedwriter
字符流的過濾器有bufferedreader和bufferedwriter/printwriter
除了基本的讀寫功能外,它們還有一些特殊的功能。
bufferedreader----->readline 一次讀一行,并不識別換行
bufferedwriter----->write 一次寫一行,需要換行
printwriter經常和bufferedreader一起使用,換行寫入比bufferedwriter更方便
定義方式:
1
2
3
|
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//對文件進行讀寫操作 string file1= "c:\\users\\acer\\workspace\\encode\\new4\\test1" ; string file2= "c:\\users\\acer\\workspace\\encode\\new4\\test2" ; bufferedreader br = new bufferedreader( new inputstreamreader( new fileinputstream(file1))); bufferedwriter bw= new bufferedwriter( new outputstreamwriter( new fileoutputstream(file2))); string line; while ((line=br.readline())!= null ){ system.out.println(line); //一次讀一行,并不能識別換行 bw.write(line); //單獨寫出換行操作 bw.newline(); bw.flush(); } br.close(); bw.close(); } |
在這里我們可以使用printwriter來代替bufferedwriter做寫操作,printwriter相比bufferedwriter有很多優勢:
構造函數方便簡潔,使用靈活
構造時可以選擇是否自動flush
利用println()方法可以實現自動換行,搭配bufferedreader使用更方便
使用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
string file1= "c:\\users\\acer\\workspace\\encode\\new4\\test1" ; string file2= "c:\\users\\acer\\workspace\\encode\\new4\\test2" ; bufferedreader br = new bufferedreader( new inputstreamreader( new fileinputstream(file1))); printwriter pw= new printwriter(file2); //printwriter pw=new printwriter(outputstream, autoflush);//可以指定是否自動flush string line; while ((line=br.readline())!= null ){ system.out.println(line); //一次讀一行,并不能識別換行 pw.println(line); //自動換行 pw.flush(); //指定自動flush后不需要寫 } br.close(); pw.close(); } |
注意:
可以使用bufferedreader的readline()方法一次讀入一行,為字符串形式,用null判斷是否讀到結尾。
使用bufferedwriter的write()方法寫入文件,每次寫入后需要調用flush()方法清空緩沖區;printwriter在構造時可以指定自動flush,不需要再調用flush方法。
在寫入時需要注意寫入的數據中會丟失換行,可以在每次寫入后調用bufferedreader的newline()方法或改用printwriter的println()方法補充換行。
通常將printwriter配合bufferedwriter使用。(printwriter的構造方法,及使用方式更為簡單)。
以上這篇【java io流】字節流和字符流的實例講解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/hysum/archive/2017/09/14/7225762.html