三、节点流与处理流
1、节点流
介绍说明
节点流也称文件流,对应节点流包含了字节流与字符流
字节流:FileInputSream、FileOutputSream
字符流:FileReader、FileWriter
注意点:
①字符流不能用来读取图片、视频等因为字符涉及到编码转换问题,在读取使用字节存储的图片时会对于某些字节转换不到造成问题;
②字节流可以用来读文本文件之类,不过在读取过程中若是打印显示可能会有乱码存在,中文一般占2-4个字节,有时候读取了一半就显示就会出现问题。
实际小案例
案例1:使用字节流来复制图片
import org.junit.Test; import java.io.*; public class Main { @Test public void test02(){ FileInputStream fis = null; FileOutputStream fos = null; try { //目标图片1234.jpg fis = new FileInputStream(new File("1234.jpg")); //复制地址 fos = new FileOutputStream(new File("图片.jpg")); byte[] data = new byte[1024]; int len; //读取字节 while((len = fis.read(data)) != -1){ //写入data数组中读取到的len个字节 fos.write(data,0,len); } } catch (IOException e) { e.printStackTrace(); }finally { if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
输入输出流对象创建需要带有File对象或者路径,如果File对应的路径不存在,会自动新建的。
说明:这个案例是将相对路径下的1234.jpg复制一份为图片.jpg。复制视频只要更改路径即可,一样的方法。
案例2:复制文本文件,并且在控制台显示
import org.junit.Test; import java.io.*; public class Main { //这里主要为了演示就不像之前那么规范,直接抛出异常 @Test public void test02() throws IOException { FileReader fr = new FileReader("changlu.txt");; FileWriter fw = new FileWriter("cl.txt"); char[] data = new char[1024]; int len; while((len = fr.read(data)) != -1){ //显示在控制台中 System.out.print(new String(data,0,len)); fw.write(data,0,len); } fr.close(); fw.close(); } }
这里是复制到指定路径下的文件目录中(覆盖操作)。若是想要追加可使用FileWriter(File file, boolean append)这种构造器方式,第二个参数填true表示数据写入从文件末尾开始。
总结
对于文本文件(例如.txt,.java,.c,.cpp)尽量使用字符流处理,字节流也是可以的。
对于非文本文件(例如.jpg,.mp3,.mp4,.avi,.doc,.ppt,…) 之类使用字节流处理。
2、缓冲流
缓冲流介绍
首先看一下缓冲流,前两个是用于传输字节的缓冲流,后两个是传输字符的缓冲流
看一下继承关系:
传输字节的两个缓冲流都是继承于FilterInputStream:
传输字符的两个缓冲流都是继承于Writer
缓冲流的作用:提高文件的读写效率
提高读写速度的原因:内部提供了一个缓冲区
对于缓冲流对象其中会创建一个内部缓冲区数组,缺省使用8192个字节(8kb)的缓冲区,以前读取、写入数据每使用read()或write()方法就会写入到文件中,而现在执行会先写到缓冲区中,直到缓冲区写满之后才一次性写到文件里。
这就有个问题:有时候明明去读取或写入了却没有读到或者文件内没有存储,那可能就是存储到缓冲区没有到文件中,可以使用flush()方法强制将缓冲区内容写入到输出流中。
使用了缓冲区与没有使用的图示:
使用缓冲流
实际上使用缓冲流很简单,直接在节点流上包一层,缓冲流也是需要进行手动关闭的,关闭的同时会将节点流也关闭。
示例:这里
//字节流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("changlu.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("test.txt"))); //字符流 BufferedReader br = new BufferedReader(new FileReader("file.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("file2.txt"));
针对于缓冲流自己实现了一个方法
void newLine():向文件中写入换行符
一般写入完之后我们还需要使用flush()方法来将缓存区的内容手动写入到文件中。
一般只有当缓冲区存满8kb字节时才会写入,若是我们写入内容不足以8kb时就需要我们自己手动写入,养成好的习惯都要写。
测试复制66MB的视频速度:
字节流:932ms
缓冲流:189ms
介绍图片加密的方法:
//加密data数组中0-len中的字节 public static byte[] encryptChar(byte[] data,int len){ for (int i = 0; i < len; i++) { data[i] ^= 5;//通过异或的方式 } return data; } //写入操作 省略了内容 byte[] data = new byte[1024]; int len; //读取字节 while((len = bis.read(data)) != -1){ //写入data数组中读取到的len个字节 bos.write(encryptChar(data,len)); }
如何解密呢?重新读取再次进行异或即可实现解密的效果!!!
3、转换流
认识转换流
转换流提供了在字节流和字符流之间的转换
InputStreamReader:将InputStream转换为Reader 字节转字符
OutputStreamWriter:将Writer转换为OutputStream 字符转字节
当字节流中的数据都是字符时,转成字符流更高效。大多通过使用转换流来处理文件乱码问题,实现编码和解码功能!
编码:字符串 =》字节数组
解码:字节数组 =》字符串
简单举个例子:将changlu.txt(UTF-8编码)先通过InputStreamReader转为字符,再通过使用OutputStreamWriter指定另一个编码转为长changlu.txt。(gbk编码)
转换流的编码应用:
可以将字符按指定编码格式存储
可以对文本数据按指定编码格式来解读
指定编码表的动作由构造器完成
实际小案例
使用转换流将一个UTF-8编码文件转为GBK编码的文件
public class Main { public static void main(String[] args) throws IOException { //转换流 将一个UTF-8编码的转为GBK编码的 InputStreamReader isr = new InputStreamReader(new FileInputStream("changlu.txt"), StandardCharsets.UTF_8); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("changluya.txt"), "GBK"); //使用缓冲流加速 BufferedReader br = new BufferedReader(isr); BufferedWriter bw = new BufferedWriter(osw); char[] data = new char[1024]; int len; while((len = br.read(data)) != -1){ bw.write(data,0,len); } br.close(); bw.close(); } }
4、标准输入、输出流
系统标准的输入和输出设备分别为:System.in与System.out。默认输入设备:键盘;默认输出设备:显示器
System.in:实际类型为InputStream
System.out:实际类型为PrintStream,其次是OutputStream的子类
我们可以更改System的输入输出设备通过System的的setIn,setOut方法。
实际小案例
传统我们通过使用Scanner来进行数据的输入获取,在这里不允许使用Scanner,要求从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续 进行输入操作,直至当输入“e”或者“exit”时,退出程序。
public static void main(String[] args) throws IOException { //System.in是InputStream实例(字节流),这里包一层转换流转换为字符流 InputStreamReader is = new InputStreamReader(System.in); //包装上一层缓冲流 BufferedReader br = new BufferedReader(is); String str; //键盘中每读取一行数据进行循环 while((str = br.readLine()) != null){ if("e".equals(str) || "exit".equals(str)){ System.out.println("安全退出"); break; } str = str.toUpperCase(); //System.out => PrintStream System.out.println(str); System.out.println("继续输入信息"); } br.close(); }
5、打印流(PrintStream与PrintWriter)
基本介绍
实现将基本数据的类型格式转换为字符串输出
提供了一系列重载的print()和println()方法,用于多种数据类型的输出。
这两个打印流都不会抛出IOException异常(受检)。
都有自动flush()功能。
PrintStream打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下应该使用PrintWriter
单个介绍:
PrintStream:在实现OutputStream接口上又实现了打印各种数据的print方法,通常使用系统默认的System.out调用方法输出
属于字节流
最终输出的总是byte数据
PrintWriter:扩展了Write接口,也实现了许多print打印输出方法
属于字符流
最终输出的是char字符
//使用方式:配合StringWriter获取数据并打印到控制台 public static void main(String[] args) throws IOException { //内部定义了一个StringBuffer存储数据 StringWriter str = new StringWriter(); try (PrintWriter pw = new PrintWriter(str)){ //将指定内容写入到str中,实际上还是调用了write方法 pw.println("hello"); pw.println("changlu"); pw.println(2020); } //将StringBuffer对象打印 System.out.println(str.toString()); }
小案例
案例描述:将原本输出到控制台的内容输入到文件中
思路:更改System中的输出设备(显示屏 =》文件)
public static void main(String[] args) throws IOException { //try(声明1;声明2;){ ... } 这种方式会自动执行close()方法 try(//①创建文件字节流 FileOutputStream fos = new FileOutputStream("changlu.txt"); //②PrintStream处理流包裹节点流 PrintStream ps = new PrintStream(fos);){ //更改System的输出设备为文件流 System.setOut(ps); //输出到文件中 System.out.println("长路&林儿"); } }
执行结果:成功创建changlu.txt,并输入到文件中。
6、数据流
只有两个流都是字节流,分别"套接"在InputStream和OutputStream子类的流上。
目的:为了方便操作Java的基本数据类型与String类型,可以使用数据流。
这里列举一下DataInputStream的几个方法如下:byte readByte()、char readChar() 、float readFloat()、long readLong()、int readInt()、String readUTF()
例如:int readInt()一次性读出四个字节并将其转为int值读出
OutputStream几个类似read换write即可。
例如:void writeInt(int v)一次写入四个字节并将其转为字节写出
直接上小案例:输出流与输入流配合使用(增添了一个缓冲流来提升速度)
public class Main { //使用数据流的输出流存储不同类属数据 @Test public void test01(){ try(BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream("changlu.data")); DataOutputStream dos = new DataOutputStream(bis);){ dos.writeUTF("长路"); dos.writeInt(666); dos.writeBoolean(false); dos.flush(); } catch (IOException e) { e.printStackTrace(); } } //使用数据流的输入流来获取指定顺序的数据类型 @Test public void test02(){ try(BufferedInputStream bos = new BufferedInputStream(new FileInputStream("changlu.data")); DataInputStream dis = new DataInputStream(bos);) { String name = dis.readUTF(); int num = dis.readInt(); boolean bol = dis.readBoolean(); System.out.println(name+"\n"+num+"\n"+bol); } catch (IOException e) { e.printStackTrace(); } } }
7、对象流(序列化)
详细对象流以及序列化见:IO流—对象序列化
8、随机存取文件流
认识RandomAccessFile
接下来要介绍的随机存取文件流只有一个类RandomAccessFile,它实现了DataOutput、DataInput接口,直接继承于Object,说明其实现了读取与写入的功能。
RandomAccessFile类功能描述:
支持随机访问的方式,程序可以直接跳到文件的任意位置来读、写文件。
支持只访问文件的部分内容。
可以向已存在的文件后追加内容。
包含一个记录指针,用来标示当前读写处的位置。
可以自由获取并移动指针的位置,例如:long getFilePointer()获取指针的位置,void seek(long pos)将指针定位到pos位置。
构造器介绍:RandomAccessFile(String name, String mode)、RandomAccessFile(File file, String mode)
第一个参数:File对象,其中name实际上也是创建的File实例;
第二个参数:mode参数指定的是该类的访问模式,访问模式如下:
r:只读方式打开。
rw:打开以便读取和写入。
rwd:打开以便读取和写入;同步文件内容的更新。
rws:打开以便读取与写入;同步文件内容和元数据的更新。
注意点:若模式为r(只读),不能够创建文件,只能读取存在的文件,若是不存在就会出现异常;rw模式是若是不存在就会去创建文件。
针对于JDK1.6上面的每次write数据,rw模式,数据不会立即写入到硬盘中,一旦写入过程中有异常数据全部丢失;rwd模式数据会被立即写入硬盘。一旦写数据发生异常,rwd模式中会将已被写入的数据保存到硬盘中。
小案例
案例1:复制图片
@Test public void test01(){ //创建两个随机存取流的实例对象,分为来进行读或写的操作 try(RandomAccessFile rafRead = new RandomAccessFile("1234.jpg", "r"); RandomAccessFile rafWrite = new RandomAccessFile("changlu.jpg", "rw");){ byte[] data = new byte[1024]; int len; while((len = rafRead.read(data))!=-1){ rafWrite.write(data,0,len); } }catch (IOException e) { e.printStackTrace(); } }
定义两个实例来进行读与写的操作,该类读取的也是字节,所以大致与之前使用节点流差不多。
案例2:复制一个文件中的内容到另一个文件追加内容
@Test public void test01(){ //创建两个随机存取流的实例对象,分为来进行读或写的操作 try(RandomAccessFile rafRead = new RandomAccessFile("changlu.txt", "r"); RandomAccessFile rafWrite = new RandomAccessFile("changlu222.txt", "rw");){ //获取其中的字节 int fileLength = (int) rafRead.length(); byte[] data = new byte[fileLength]; for(int i=0;i<data.length;i++){ data[i] = rafRead.readByte(); } //复制内容到其他文件中 rafWrite.seek(2);//空两格 for(int i = 0 ;i<data.length;i++){ rafWrite.writeByte(data[i]); } //新增指定内容 // byte[] bytes = "想对林儿说我想你了".getBytes("utf-8"); // for(int i=0;i<bytes.length;i++){ // rafWrite.writeByte(bytes[i]); // } rafWrite.writeUTF("想对林儿说我想你了"); }catch (IOException e) { e.printStackTrace(); } }
出现乱码,比较迷糊搞不清
NIO扩展
java.nio这个类带来了重要的效能提升并可以充分利用执行程序的机器上的原始容量。
java1.4版新增的输入输出java.nio这个类带来了重要的效能提升并可以充分利用执行程序的机器上的原始容量。
包含一项关键能力是可以直接控制buffer以及nonblocking的输入域输出,它能让你的输入/输出程序代码在没有东西可读取或写入
时不用等在那里。
对于nio可能会引发效能损失,非nio的输入/输出适合九成以上的应用,依旧可以使用FileInputStream并通过getChannel()方法来开始使用nio。
NIO.2中Path、Paths、Files类的使用见:语雀-NIO部分
参考资料
[1]. Java中Native关键字的作用
[2]. Java一个汉字占几个字节(详解与原理) 特别详细
[3]. 为什么用字符流复制的图片打不开,而用字节流复制的却可以打开?
[4]. 对比字节流和字符流,回答为什么FileReader不能用来拷贝图片
[5]. Java I/O流之随机流详解,包含随机流读写数据时编码格式问题!
[6]. 尚硅谷Java视频-IO流(宋红康)