IO流相关概念 流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。 从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出。 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中; 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类 按操作数据单位不同分为:字节流(8bit),字符流(16bit) 按数据流的流向不同分为:输入流,输出流 按流的角色的不同分为:节点流,处理流
流的体系结构
字节流 根据流的体系结构可以知道,字节流分为InputStream与OutputStream,一个对应输入流,一个对应输出流。
那么字节流和字符流有什么区别呢?不同的文件用是什么来处理呢:
对于文本文件(.txt、.java、.c,.cpp),使用字符流处理;
对于非文本文件(.jpg、.mp4、.doc、.avi、.ppt),需要使用字节流处理;
对于单纯的复制操作,可以使用字节流复制文本文件的内容,输出到控制台的话可能会有中文乱码的情况; 说明:若使用字节流处理文本文件,是会可能出现乱码的
FileInputStream和FileOutputStream分别是InputStream与OutputStream的子类,我们通常使用这两个子类来实现文件的读写,读取文件示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void test(){ FileInputStream fileInputStream = null; try { File file1 = new File("C:\\Users\\Desktop\\hello.txt"); fileInputStream = new FileInputStream(file1); int data; while ((data = fileInputStream.read()) != -1){ System.out.print((char) data); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
输出结果如下:
在咱们这个例子中,为了能体现出读取文件的功能,使用了FileInputStream去处理.txt文件,这样的情况下,若txt文件里有中文,是很有可能出现乱码的;所以还是建议使用对应的流去处理不同的文件。 接下来看下fileInputStream.read()方法,可以看到该方法一次只读取一字节的数据,速度很慢:
1 2 3 4 5 6 7 8 9 10 11 /** * Reads a byte of data from this input stream. This method blocks * if no input is yet available. * * @return the next byte of data, or <code>-1</code> if the end of the * file is reached. * @exception IOException if an I/O error occurs. */ public int read() throws IOException { return read0(); }
所以为了性能更好,我们可以使用缓冲流。
字符流-FileReader 根据流的体系结构可以知道,字节流分为Reader与Writer,一个对应输入流,一个对应输出流。字符流用来处理文本文件。
FileReader-从磁盘中读取文件,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** 说明点: 1.read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1 2.异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理 3.读入的文件一定要存在,否则就会报FileNotFoundException 4.read(char cbuf[])方法可以一次读指定数量的字符 */ @Test public void test() throws IOException { File file1 = new File("C:\\Users\\Desktop\\hello.txt"); FileReader fileReader = new FileReader(file1); int data = fileReader.read(); List<String> list = new ArrayList<>(); while (data != -1){ list.add(String.valueOf((char) data)); data = fileReader.read(); } fileReader.close(); System.out.println(list); }
接下来看下fileReader.read()方法,该方法一次读取一个字符,速度也很慢:
1 2 3 4 5 6 7 8 9 10 11 /** * Reads a single character. * * @return The character read, or -1 if the end of the stream has been * reached * * @exception IOException If an I/O error occurs */ public int read() throws IOException { return sd.read(); }
字符流-FileWriter FileWriter,把内容写进磁盘中。 关于FileWriter的一些说明: (1)输出操作,对应的File可以不存在,并不会报异常; (2)如果输出的file不存在,在输出的过程中,会自动创建此文件; (3)如果输出的file存在: 若流使用的构造器是:new FileWriter(file1,false)|new FileWriter(file1):会对原有文件进行覆盖; 若流使用的构造器是:new FileWriter(file1,true):不会对原有文件进行覆盖,而是在原有文件基础上追加内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void test3(){ FileWriter fileWriter = null; try { File file1 = new File("C:\\Users\\Desktop\\hello.txt"); fileWriter = new FileWriter(file1,true); fileWriter.write("ssssssssssstest"); } catch (IOException e) { e.printStackTrace(); } finally { try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } }
处理流之缓冲流 缓冲流有这么几个,作用跟字节、字符流差不多,只不过能提高读写速度: BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
缓冲流的作用:提高流的读取、写入的速度 提高读写速度的原因:内部提供了一个缓冲区,读写的时候先将缓冲区装满后再继续。默认是8kb。
示例代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 /** 实现非文本文件的复制 */ @Test public void test(){ BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; try { // 1.造文件 File file1 = new File("C:\\Users\\Desktop\\hello.txt"); File file2 = new File("C:\\Users\\Desktop\\hello123.txt"); // 2.造流 // 2.1 造节点流 fileInputStream = new FileInputStream(file1); fileOutputStream = new FileOutputStream(file2); // 2.2造缓冲流 bufferedInputStream = new BufferedInputStream(fileInputStream); bufferedOutputStream = new BufferedOutputStream(fileOutputStream); // 3.复制的细节:读取、写入 int len; while ((len = bufferedInputStream.read()) != -1){ bufferedOutputStream.write(len); // bufferedOutputStream.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { // 4.资源关闭 // 要求:先关闭外层的流,再关闭内层的流 // 即先关闭bufferedInputStream、bufferedOutputStream,在关闭fileInputStream、fileOutputStream // 说明:在关闭外层的流的同时,内层流也会自动的进行关闭。关于内层流的关闭,可以省略 if (bufferedInputStream != null){ try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedOutputStream != null){ try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (fileOutputStream != null){ try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
处理流之转换流 转换流(属于字符流): InputStreamReader:将一个字节的输入流转换为字符的输入流(可以理解为编码) OnputStreamWriter:将一个字符的输出流转换为字节的输出流(可以理解为解码)
提供字节流与字符流之间的转换: 解码:字节、字节数组 —> 字符串、字符数组 编码:字符串、字符数组 —> 字节、字节数组
字符集: ASCII:美国标准信息交换码,用一个字节的7位可以表示; ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示; GB2312:中国的中文编码表。最多两个字节编码所有字符; GBK:中国的中文编码表升级,融合了更多的中文文字符号最多两个字节编码; Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示; UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
对象流 对象流的分类:ObjectInputStream 和 ObjectInputOutputStream; 作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
自定义类需要满足如下的要求,方可序列化: (1)需要实现接口:Serializable (2)当前类提供一个全局常量:serialVersionUID;这个常量相当于一个标识,从哪个类序列化来的,就从反序列化回那个类; 若不显示声明(不显示声明会自动生成),序列化对象后,修改对象所在的类,可能会报InvalidClassException异常; (3)除了当前自定义类需要实现Serializable接口之外,还必须保证其内部所有属性 也必须是可序列化的。(默认情况下,基本数据类型也是可序列化的)
注意:ObjectInputStream 和 ObjectInputOutputStream不能序列化static和transient修饰的成员变量。
对于序列化的文章,这里放几个写得挺好的链接:https://juejin.im/post/6844903848167866375#heading-7 https://www.hollischuang.com/archives/1140 https://developer.ibm.com/zh/articles/j-lo-serial/
其中需要注意的是自己自定义序列化算法的时候,调用序列化/反序列化的方法,会通过反射调用自定义的序列化方法。
其他流的使用 标准的输入、输出流 System.in:标准的输入流,默认从键盘输入; System.out:标准的输出流,默认从控制台输出。 System类的setIn(Inputstream is) / setOut(PrintStream in)方式重新指定输入和输出的流。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 public static void main(String[] args) throws IOException { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); while (true){ String data = br.readLine(); if ("exit".equals(data)){ break; } System.out.println(data.toUpperCase()); } br.close(); }
打印流 作用:实现将基本数据类型的数据格式转化为字符串输出; 分类:PrintStream和PrintWriter。
数据流 数据流: 作用:用于读取或写出基本数据类型的变量或字符串; 分类:DataInputStream 和 DataOutputStream; DataInputStream读取的顺序要与DataOutputStream存储的顺序一致,否则会抛出异常。
示例代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Test public void test(){ DataOutputStream dataOutputStream = null; try { dataOutputStream = new DataOutputStream(new FileOutputStream("hello.txt")); dataOutputStream.writeInt(113); dataOutputStream.flush();//刷新操作,将内存中的数据写入文件 dataOutputStream.writeUTF("甲乙丙丁"); dataOutputStream.flush(); dataOutputStream.writeBoolean(true); dataOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (dataOutputStream != null){ try { dataOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test public void test3() { DataInputStream dataInputStream = null; try { dataInputStream = new DataInputStream(new FileInputStream("hello.txt")); System.out.println(dataInputStream.readInt()); System.out.println(dataInputStream.readUTF()); System.out.println(dataInputStream.readBoolean()); } catch (IOException e) { e.printStackTrace(); } finally { if (dataInputStream != null){ try { dataInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }