1.什么是IO

IO流实际上是内存和硬盘(中的文件)之间的通道。
1.png

流都在java.io.*包下。

无论是输出还是输入,读还是写,都是针对内存而言的。


IO流的分类

按照流的方向进行分类(以内存为参照物):

输入流:往内存中去,叫输入(Input),或者叫读(Read)。

输出流:从内存中出来,叫输出(Output),或者叫写(Write)。

另一种方式是按照读取数据方式的不同进行分类的。

字节流:按照字节的方式读取数据,一次读取一个字节,这种流是万能的,什么文件都可以读。

字符流:按照字符的方式读取数据,一次读取一个字符,专为读取文本文件而存在的,而且只能读取文本文件。

注:windows操作系统中一个字符“a”占一个字节,用字节流读“a”就是一个“a”,因为.txt文件和java没有关系。


IO流的四大家族

java.io.InputStream 字节输入流

java.io.OutputStream 字节输出流

java.io.Reader 字符输入流

java.io.Writer 字符输出流

四大家族都是abstract类。

所有类都实现了java.io.Closeable接口,都是可关闭的,其中有close()方法,用完流之后要进行对流的关闭。

所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法,输出流在输出结束后要进行刷新,用于将管道中的数据强行输出完(清空流管道),如果没有flush可能会导致数据的丢失。


java中主要学的16个类
2.png


2.FileInputStream

首先看看FileInputStream是如何定义的。

  1. FileInputStream fis = null; // 在try之外进行定义,注意一定要赋值为null
  2. // 有try
  3. fis = new FileInputStream("D:\\CODE\\java\\IO\\test.txt");
  4. // 这里会抛出异常,所以要进行异常的处理

read方法

  1. int data = fis.read(); // 一次读取一个字符,以int形式返回
  2. System.out.println(data); // 读到字符“a”,然后返回97

通过while循环对read方法进行改进

  1. int data = fis.read(); //
  2. while(data != -1){ // 这里不能写fis.read() != -1,因为文件指针会跳过一个字节,所以得用一个变量进行判断
  3. System.out.println(data);
  4. data = fis.read();
  5. }

或者可以这么写

  1. int data = 0;
  2. while((data = fis.read()) != -1){ // 注意里面的括号
  3. System.out.println(data);
  4. }

但一次只读取一个字节,内存和硬盘交换太频繁。

我们可以使用byte数组+while循环去读文件。

  1. fis = new FileInputStream("D:\\CODE\\java\\IO\\test.txt");
  2. byte [] bytes = new byte[4]; // 我们new出来一个4个长度(当然可以更多)的byte数组
  3. int datacount = 0; // datacount作为每次读到的byte数组的长度
  4. while((datacount = fis.read(bytes)) != -1){ // 如果没读完
  5. System.out.print(new String(bytes,0,datacount)); // 使用String类中的构造方法一并输出
  6. }

available方法,判断有流中有多少个字节没读

  1. System.out.println("文件共多少个字节:"+fis.available()); // 可以在一开始就明确文件有多少个字节
  2. byte [] bytes = new byte[fis.available()]; // 字节数量作为byte数组的长度,可以只读一次
  3. System.out.println(new String(bytes));

skip方法,跳过多少个字节不读


3.FileOutputStream

FileOutputStream流把内存中的数据写出硬盘上。

  1. fos = new FileOutputStream("myfile");
  2. String s = "i am Chanese";
  3. byte [] bs = s.getBytes(); // 将字符串转为byte数组的方法
  4. fos.write(bs);
  5. // fos.write(bs,0,2) 表示从byte数组的0位置写入,写两个字节
  6. // 注意写完之后一定要记得刷新一下
  7. fos.flush();

这种方法谨慎使用,因为会清空原文件原先的内容,FileOutputStream的重载构造方法中可以在后面加上true,表示写入的内容是追加而不是覆盖。

  1. fos = new FileOutputStream("myfile" , true);

文件的拷贝,原理就是把硬盘上的东西输入到内存中,再从内存中把数据写到硬盘里。使用字节流的拷贝,可以使得要拷贝的文件类型是任意的,不局限于txt文件。

  1. FileOutputStream fos = null;
  2. FileInputStream fis = null;
  3. fis = new FileInputStream("E:\\迷醉.Euphoria.S01E01.中英字幕.WEB-1080P-人人影视.mp4");
  4. fos = new FileOutputStream("D:\\迷醉1.mp4");
  5. byte [] bytes = new byte[1024*1024];
  6. // 一次最多可以拷贝1M
  7. int count = 0;
  8. while ((count = fis.read(bytes)) != -1){
  9. // 读到多少个字节数,则拷贝多少个字节
  10. fos.write(bytes,0,count);
  11. }
  12. // 最后要记得刷新
  13. fos.flush();
  1. finally {
  2. // 注意这里一定要分开处理异常,否则一个异常处理了会导致另外一个流没有关闭
  3. if (fis != null) {
  4. try {
  5. fis.close();
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. if (fos != null) {
  11. try {
  12. fos.close();
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

4.FileReader和FileWriter

文件字符输入输出流,只能读取不同的文本文件,不能读取媒体文件。

读取文本文件时,比较方便,快捷。

  1. // 定义一个FileWriter对象out
  2. // 定义一个FileReader对象in
  3. // 字符的输入输出流不能用byte数组,只能用Sring数组。
  4. char [] chars = {'我','是','中','国','人'};
  5. out.write(chars);// 可以直接在write的参数中添加一个字符串数组形参
  6. System.out.println(chars,0,readcount); // 可以像读字节一样指定读几个字符
  7. out.write("我是中国人"); // 也可以直接读一个字符串(最方便)

5.BufferedReader和BufferedWriter

BufferedReader:带有缓冲区的字符输入流,使用这个流时不需要定义char数组(byte数组),自带缓冲。

  1. BufferedReader br = null;
  2. FileReader reader = null;
  3. reader = new FileReader("test.txt");
  4. // 注意这里要传一个Reader类型的参数,但是Reader是abstra类型的,不能实例化,便传一个Reader的子类进去。
  5. br = new BufferedReader(reader);
  6. // readLine方法可以一次读一行
  7. // 但是注意readLine方法读一行时不带换行符
  8. String s = br.readLine();
  9. System.out.println(s);
  1. finally {
  2. // 注意这里处理异常的时候只需要处理最外层的异常就行
  3. if (br != null) {
  4. try {
  5. br.close();
  6. } catch (IOException e) {
  7. e.printStackTrace();
  8. }
  9. }

通常把里面的流叫节点流,把外面的流叫包装流。

readLine在读不到东西时会返回null,所以可以用while循环实现把文件中的东西全部读出来。

  1. String s = null;
  2. while((s = br.readLine()) != null){
  3. System.out.println(s);
  4. }

在定义BufferedReader时,参数只能写Reader的子类,否则(比如写一个FileInputStream)会报错。

但是我们可以使用转换流把FileInputStream转换成Reader流。

  1. // 先定义一个FileInputStream流
  2. FileInputStream in = new FileInputStream("test.txt");
  3. // 再用转换流把FileInputStream转换成Reader(相当于做了一个对象的转换)
  4. InputStreamReader reader = new InputStreamReader(in);
  5. // 最后在定义BufferedReader流的时候把转换后的对象传进去
  6. BufferedReader br = new BufferedReader(reader);
  7. // 只用关闭最外层的包装流就行了
  8. br.close();

当然我们可以简写

  1. BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt");

BufferedWriter:带有缓冲的字符输出流

  1. // 创建一个自带缓冲的字节输出流
  2. BufferedWriter bw = new BufferedWriter(new FileWriter("test2.txt"));
  3. // 像FileWriter一样,可以在write方法里直接写字符串
  4. bw.write("我是中国人\n");
  5. bw.write("我骄傲!");
  6. // 最后注意流的刷新和关闭
  7. bw.flush();
  8. bw.close();

6.数据流

java.io.DataOutputStream:数据专属的流。

这个流可以将数据连通数据的类型一并写入文件。

注:这个文件不是普通的文本文件,使用记事本打不开。

java.io.DataInputStream:数据字节输入流。

DataOutputStream写的文件,只能使用DataInputStream读,而且读的时候需要提前知道写入的顺序。

读的顺序和写的顺序一直,才能正常取出数据。


7.标准输出流

PrintStream

  1. // System.out是System类中的一个属性,初始值为空,类型是PrintStream的
  2. PrintStream ps = System.out;
  3. // println方法实际上实在PrintStream类中的。
  4. ps.println("123");

实际上,我们可以改变输出的方向(默认输出在控制台上),需要调用setOut方法

  1. // 我们想将数据输出在log文件中
  2. // PrintStream类实例化时,参数需要一个OutputStream类型的节点流
  3. PrintStream ps = new PrintStream(new FileOutputStream("log"));
  4. // setOut方法是在System类中的
  5. // 调用这个函数,数据便会通过标准输出流输出到log文件中
  6. System.setOut(ps);
  7. System.out.println("123123");

8.File类

File类不属于四大家族的类,所以不能对文件进行读写。
File类的实例化对象是文件和目录路径的抽象表示。

File类常用的方法

  1. import java.io.*;
  2. public class demo1 {
  3. public static void main(String[] args) throws Exception{
  4. File f = new File("log");
  5. // exists方法,用于判断对应路径有没有指定的文件
  6. System.out.println(f.exists());
  7. if(!f.exists()){
  8. // createNewFile方法,创建一个新的文件
  9. f.createNewFile();
  10. // mkdir方法,创建一个新的目录(文件夹)
  11. f.mkdir();
  12. }
  13. File f2 = new File("a/b/c/d/hello.txt");
  14. if(!f2.exists()){
  15. // 创建树结构的目录
  16. f2.mkdirs();
  17. }
  18. // 打印文件的上级目录
  19. System.out.println(f2.getParent());
  20. // 打印文件的绝对地址
  21. System.out.println(f2.getAbsolutePath());
  22. // 打印文件的字节数(大小)
  23. System.out.println(f2.length());
  24. File f3 = new File("D:\\CODE");
  25. // 获取当前目录下的所有文件,包装成一个数组
  26. File [] flist = f3.listFiles();
  27. // 遍历
  28. for(File file : flist){
  29. // 打印文件的名字
  30. System.out.println(file.getName());
  31. }
  32. }
  33. }

9.序列化和反序列化

在关闭程序的时候,程序中实例化的对象会被垃圾回收,有没有一种技术可以在程序结束之前把对象保存起来,我们可以使用序列化和反序列化。
3.png