八、常用流详解
8.1 文件字节流
8.1.1 FileInputStream文件字节输入流
FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。文件字节流是一个字节一个字节读取数据,如果数据源的编码方式和目的地的解码方式不同就会造成乱码问题。
package cn.it.bz.IO; import java.io.FileInputStream; import java.io.IOException; public class TestFileInputStream { public static void main(String[] args) { //将磁盘D中a.txt以字节输入到程序中 try(FileInputStream fileInputStream = new FileInputStream("D:/a.txt");) { StringBuilder stringBuilder = new StringBuilder(); int temp = 0; while ((temp = fileInputStream.read()) != -1){//获取文件中数据的字节 //将字节转成字符 stringBuilder.append((char)temp); } System.out.println(stringBuilder); } catch (IOException e) { e.printStackTrace(); } } }
需要注意的是read()方法无参数,返回值是int类型,代表的是文件中字符对应的ASCII编码。返回值如果是-1的话表示文件已经读取完毕。对于得到的ASCII一般需要在程序中再将其强制转换为char字符类型
8.1.2 FileOutputStream文件字节输出流
FileOutputStream 通过字节的方式写数据到文件中,适合所有类型的文件(图像、视频、文本文件等。这里的字节指的是写出数据的参数是字节,不是将字符的字节输到文件中。
package cn.it.bz.IO; import java.io.FileOutputStream; import java.io.IOException; public class TestFileOutputStream { public static void main(String[] args) { //将Java以字节输出到D盘下的a.txt文件中 //true:表示会追加到文件末尾。false(默认):表示重写整个文件的内容。 try (FileOutputStream fileOutputStream = new FileOutputStream("d:/a.txt",true);) { //准备输出的数据 String data = " java"; //将字符串转为字节数组:j=>106 a=>97 v=>118 a=>97 byte[] bytes = data.getBytes(); fileOutputStream.write(data.getBytes()); //刷新,将数据从内存中写入到磁盘中 fileOutputStream.flush(); } catch (IOException E) { E.printStackTrace(); } } }
需要注意的是write()方法的参数是字节或者是字节数组,输出到文件的时候会自动将字节或者是字节数组转换为对应ASCII码表示的字符。
8.1.3 通过字节缓冲区提高读写速度
package cn.it.bz.IO; import java.io.FileInputStream; import java.io.FileOutputStream; public class TestFileBuffer { public static void main(String[] args) { //获取当前时间的毫秒数 long startTime = System.currentTimeMillis(); copyFile("D:/a.txt","d:/b.txt"); long endTime = System.currentTimeMillis(); System.out.println(endTime-startTime); } //文件复制。source:源文件。destination:目的地文件 public static void copyFile(String source,String destination){ //流的关闭顺序是后开先关 try(FileInputStream fileInputStream = new FileInputStream(source); FileOutputStream fileOutputStream = new FileOutputStream(destination)) { //字节缓冲区。因为输入输出流是文件字节流,因此使用字节数组作为缓冲区 byte[] buffer = new byte[1024]; //先将文件读到程序 int temp = 0; while ((temp = fileInputStream.read(buffer))!= -1){ System.out.println("temp:"+temp); //将文件写出去。buffer:表示一次写出的字节的大小。off:表示从第0个位置开始写出。temp:表示写出的文件取决于temp,temp决定了缓冲区有多少字节数。 fileOutputStream.write(buffer,0,temp); } //将数据写出到磁盘中 fileOutputStream.flush(); }catch (Exception e){ e.printStackTrace(); } } }
fileInputStream.read(buffer)
将文件中的数据,读入到缓冲区buffer中,返回的是该组数据的大小。假设,被读取文件的最后一组只有200个字节,那么输入字节流也只会读取这200个字节存放到缓冲区,缓冲区剩下的空间是没有数据的。
fileOutputStream.write(buffer,0,temp);
将本次循环中的数据写出到目标文件中,如果不给0,temp这两个参数,则将缓冲区中1024个字节全部输出到目标文件中,也就是即使缓冲区中1024个字节只有200个字节是数据,那输出流也会将这1024个字节输出到你目标文件中,剩下的824个字节用空格补齐。
注意 在使用字节缓冲区时,我们需要注意:
为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为:read(byte[] b);写入时的方法为:write(byte[ ] b, int off, int length)
程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。
8.1.4 缓冲字节流
Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组(其实和前面的缓冲区数组一样,只不过缓冲区大小默认是8192个字节,可以通过构造方法来修改该缓冲区的大小)来提高操作流的效率。
package cn.it.bz.IO; import java.io.*; public class TestFileBufferedStream { public static void main(String[] args) { //获取当前时间的毫秒数 long startTime = System.currentTimeMillis(); copyFile("D:/abc.jpg","d:/2.jpg"); long endTime = System.currentTimeMillis(); long time = endTime-startTime; System.out.println("时间:"+time); } public static void copyFile(String source,String destination){ //两个节点流,两个处理流 try(FileInputStream fileInputStream = new FileInputStream(source); FileOutputStream fileOutputStream = new FileOutputStream(destination); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); ) { int temp = 0; while ((temp = bufferedInputStream.read()) != -1){ bufferedOutputStream.write(temp); } }catch (IOException e){ e.printStackTrace(); } } }
8.2 文件字符流
前面介绍的文件字节流可以处理所有的文件,如果我们处理的是文本文件,也可以使用文件字符流,它以字符为单位进行操作。
文件字符流一般用于读写文本文件,不用于读取二进制文件,会产生乱码。字符流在遇到英文时一次读取一个字节,在遇到中文时一次读取多个字节(gbk编码一次读三个。utf-8一次读两个),这和字符集有关。
8.2.1 FileReader文件字符输入流
使用文件字符输入流读取数据时,遇到汉字会读取多个字节,GBK编码的汉字一次读取两个字节,UTF-8 编码的一次读取三个字节,在程序得到汉字字节时会将其按照指定或者是默认的解码规则解码得到十进制数。
package cn.it.bz.IO; import java.io.FileReader; import java.io.IOException; public class TestFileReader { public static void main(String[] args) { //创建文件字符输入流对象 try(FileReader fileReader = new FileReader("D:/a.txt");) { StringBuilder stringBuilder = new StringBuilder(); int temp = 0; while ((temp = fileReader.read()) != -1){ System.out.println(temp); stringBuilder.append((char)temp); //将字符解码得到的十进制数据还原回字符 } System.out.println("输出:"+stringBuilder); }catch (IOException e){ e.printStackTrace(); } } }
8.2.2 FileWriter文件字符输出流
package cn.it.bz.IO; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class TestFileWriter { public static void main(String[] args) { //创建文件字符输出流对象,默认将源文件的内容覆盖,true表示开启追加。 try(FileWriter fileWriter = new FileWriter("D:/a.txt",true);) { fileWriter.write("张三\r\n");//"\r\n"表示回车换行 fileWriter.flush(); }catch (IOException e){ e.printStackTrace(); } } }
8.2.3 缓冲字符流
BufferedReader/BufferedWriter增加了缓存机制,大大提高了读写文本文件的效率。
字符输入缓冲流
BufferedReader是针对字符输入流的缓冲流对象,提供了更方便的按行读取的方法:readLine(); 在使用字符流读取文本文件时,我们可以使用该方法以行为单位进行读取。
package cn.it.bz.IO; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class TestBufferedReader { public static void main(String[] args) { //创建字符输入缓冲流和字符输入节点流 try(BufferedReader bufferedReader = new BufferedReader(new FileReader("D:/a.txt"))) { String temp = ""; while ((temp = bufferedReader.readLine()) != null){//循环读取一行字符串 System.out.println(temp); } }catch (IOException e){ e.printStackTrace(); } } }
字符输出缓冲流
BufferedWriter是针对字符输出流的缓冲流对象,在字符输出缓冲流中可以使用newLine();方法实现换行处理。
package cn.it.bz.IO; import java.io.*; public class TestBufferedWriter { public static void main(String[] args) { //创建字符输入缓冲流和字符输入节点流 try(BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:/a.txt"))) { //向文件写出字符串 bufferedWriter.write("摄像头"); bufferedWriter.newLine(); //换行 bufferedWriter.write("青青子衿悠悠我心"); bufferedWriter.flush(); }catch (IOException e){ e.printStackTrace(); } } }
注意
- readLine()方法是BufferedReader的方法,可以对文本文件进行更加方便的读取操作。
- newLine()方法BufferedWriter的方法,可以使用newLine()方法换行。
8.2.4 小练习:为文件中的内容添加行号
package cn.it.bz.IO; import java.io.*; public class TestLineNumber { public static void main(String[] args) { try(BufferedReader bufferedReader = new BufferedReader(new FileReader("D:/a.txt")); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:/a1.txt")) ) { String temp = " "; int i = 1; while ((temp = bufferedReader.readLine()) != null){ //向a1.txt文件写入数据 bufferedWriter.write(i+"、"+temp); //换行 bufferedWriter.newLine(); //i+1 i++; } //刷新 bufferedWriter.flush(); }catch (IOException e){ e.printStackTrace(); } } }
8.3 转换流
InputStreamReader字节流转换为字符流/OutputStreamWriter将字符流转换为字节流。转换流也是处理流。
8.3.1 转换流处理乱码问题
计算机中数据的存储规则:
计算机中的数据都是二进制的数据,八个二进制位组成一个字节,英文字母占一个字节,汉字占两个字节。
ASCII字符集:
ASCII字符集最多表示128个字符(7位)且只能存储英文字符,一个字节最多表示256个字符(8位),因此英文字符占一个字节足够。计算机中存储的二进制数据都是8位,只有7位的
ASCII码是不能直接存储到计算机中的。因此还需要对7位的ASCII码进行编码,使其转换为8为的二进制数据,编码的结果就是在ASCII码的最高位补0。
GBK字符集:
中国win电脑上显示的ANSI实际上就是指的GBK字符编码集。GBK完全兼容ASCII
需要注意的是:解码是将二进制转为十进制。
Unicode字符编码:
存储全世界的字符编码,Unicode字符编码针对的是全世界的字符,因此针对不同国家地区的文字采取不同的编码方式,最常见的就是UTF-8(注意UTF-8不是字符集),规定英文字符占一个字节且最高位为0(0xxxxxxx),汉字占三个字节从高位到低位依次是 1110xxxx
10xxxxxxxx 10xxxxxxxx。
乱码产生的原因:
1、读取字符时没有读完。
2、编码和解码方式不统一。
如何避免产生乱码?
1、不使用字节流读取文件
2、使用相同的编码和解码方式
package cn.it.bz.IO; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; public class TestInputStreamReader { public static void main(String[] args) { try( //创建文件字节输入流对象 FileInputStream fileInputStream = new FileInputStream("D:/a2.txt"); //创建字节到字符的转换流 InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"gbk"); ) { StringBuilder stringBuilder = new StringBuilder(); int temp = 0; while ((temp = inputStreamReader.read()) != -1){ stringBuilder.append((char) temp); } System.out.println(stringBuilder); }catch (IOException e){ e.printStackTrace(); } } }
8.3.2 小练习:通过字节流读取文本文件并添加行号
package cn.it.bz.IO; import java.io.*; public class TestLineNumber2 { public static void main(String[] args) { try ( //创建字节文件输入流 FileInputStream fileInputStream = new FileInputStream("D:/a.txt"); //将字节流转换为字符流(解决中文乱码) InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"utf-8") ; //再使用字符缓冲(读一行) BufferedReader bufferedReader = new BufferedReader(inputStreamReader); //创建字符文件输出流 FileWriter fileWriter = new FileWriter("D:/a1.txt"); //创建文件字符缓冲区 BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); ) { int i = 1; StringBuilder stringBuilder = new StringBuilder(); String s = ""; while ((s = bufferedReader.readLine()) != null){ System.out.println(s); //添加行号 bufferedWriter.write(i+"、"+s); //换行,相当于是bufferedWriter.write("\r\n") bufferedWriter.newLine(); i++; } //释放资源 bufferedWriter.close(); } catch (IOException e) { e.printStackTrace(); } } }
原理图: