对于文件必然有读和写的操作,读和写就对应了输入和输出流,流又分成字节和字符流。
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)批量字节进行文件的拷贝,带缓冲的字节流(效率最高,推荐使用!!)
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编码来解析(项目默认编码),操作文本文件的时候,要写文件本身的编码格式(在构造函数时在后面加上编码格式)!!
字符流和字节流的区别主要是操作的对象不同,还有字符流是以字符为单位来读取和写入文件的,而字节流是以字节或者字节数组来进行操作的!!
在使用字符流的时候要额外注意文件的编码格式,一不小心就会造成乱码!
七、字符流的文件读写流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