IO

四大顶流














java四大流

字节流

字符流

InputStream

OutputStream

Reader

Writer

Closeable

Flushable


  1. 所有以Reader结尾的都是输入字符流;所有以Writer结尾的都是输出字符流;
  2. 四大流都实现了Closeable接口,说明都可以关闭,都有close()方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭。不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭.
  3. 只有输出流实现了Flushable接口。养成一个好习惯,输出流在最终输出之后,一定要记得flush()
    刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据.

文件专属流

FileInputStream FileOutputStream FileReader FileWriter

  1. FileInputStream 万能流, 任何类型的文件都可以以字节读入到内存,但是一次读取一个字节byte ,这样内存和硬盘交互太频繁,基本上时间资源都耗费在交互上面了。标准模版:
FileInputStream fis = null;
try {
   fis = new FileInputStream("src/Test.java");
   // 这种available()方法开辟空间不适合大文件
   byte[] bytes = new byte[fis.available()];
   int readcount;
   // read方法可以接收一个byte数组,每次读取byte数组长度的字节,并返回实际读到的字节数,因为有可能
   // 文件最后一次读取时读不满 byte.length 个字节。当 readcount=-1时,表示读不到数据了。
   while((readcount = fis.read(bytes)) != -1) {
      // String又一个构造方法是可以传入一个byte数组,并设置从哪个索引开始,将多少个字节数组元素转换为
      // String
      System.out.println(new String(bytes, 0, readcount));
   }
} catch (IOException e) {
   e.printStackTrace();
} finally {
   try {
      if (fis != null)
         fis.close();
   } catch (IOException e) {
      e.printStackTrace();
   }
}

FileInputStream 其他常用方法:

  • available() 返回当前流中文件剩余没有读到的字节数量
  • skip(n) 跳过n个字节不读
  1. FileOutputStream
FileOutputStream fos =null;
try {
   // 如果该文件不存在则创建文件,如果存在则会覆盖原有内容
   fos = new FileOutputStream("/Users/ymy/test.txt");
   // 加上true后保证是在原有内容上追加
   fos = new FileOutputStream("/Users/ymy/test.txt", true);
   byte[] bytes = {101, 102, 23, 33};
   // 将整个字节数组写入
   fos.write(bytes);
   // 从0索引开始写入2个元素
   fos.write(bytes, 0, 2);
   // 写完之后一定要刷新
   fos.flush();
} catch (IOException e) {
   e.printStackTrace();
}
  1. FileReader 只能读取文本文件
FileReader fr = null;
try {
   fr = new FileReader("/Users/ymy/test.txt");
   char[] chars = new char[6];
   int readCount = 0;
   while ((readCount = fr.read(chars)) != -1)
      System.out.println(new String(chars, 0, readCount));
} catch (IOException e) {
   e.printStackTrace();
} finally {
   if (fr != null) {
      try {
         fr.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}
  1. FileWriter 只能输出普通文本
FileWriter fw = null;
try {
   // 逻辑和FileOutputStream一样
   fw = new FileWriter("/Users/ymy/test.txt", true);
   // FileWriter 支持4种写入方式
   char[] chars = {'我', '是', '中', '国', '人'};
   fw.write(chars);
   fw.write(chars, 0, 3);
   fw.write(new String("哈哈哈哈"));
   fw.write(new String("一二三四"), 0, 3);
   fw.write("aaa");
   // 输出流一定要记得刷新
   fw.flush();
} catch (IOException e) {
   throw new RuntimeException(e);
} finally {
   if (fw != null) {
      try {
         fw.close();
      } catch (IOException e) {
         throw new RuntimeException(e);
      }
   }
}

FileReader 和 FileWriter 只能处理普通文本文件

转换流

InputStreamReader OutputStreamWriter

缓冲流

BufferedReader BufferedWriter BufferedInputStream BufferedOutputStream

  1. BufferedReader 带有缓冲区的字符输入流。使用这个流的时候不需要自定Xchar数组,或者说不需要自定byte数组。自带缓冲。比较方便写代码。
BufferedReader br = null;
try {
   // 当一个流的构造方法中需要- -个流的时候,这个被传进来的流叫做:节点流。
   // 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
   // 像当前这个程序来说: FileReader就是-一个节点流。BufferedReader 就是包装流/处理流。
   br = new BufferedReader(new FileReader("/Users/ymy/test.java"));

   String s = null;
   // readline()方法读取一个文本行,但不会读取换行符。
   while ((s = br.readLine()) != null)
      System.out.println(s);
} catch (FileNotFoundException e) {
   e.printStackTrace();
} catch (IOException e) {
   throw new RuntimeException(e);
} finally {
   if (br != null) {
      try {
         // 关闭流。对于包装流来说,只需要关闭最外层流就行,里面的结点流会自动关闭
         br.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}
  1. BufferedWriter 一样,构造器接收一个Writer 类型的对象。其他操作和FileWriter一样

数据流

DataInputStream DataOutputStream

  1. DataOutputStream :数据专属的流。这个流可以将数据连同数据的类型一并写入文件。
    注意:这个文件不是普通文本文档。 (这个文件使用记事本打不开。 )

标准输出流

PrintWriter PrintStream

  1. PrintStream 标准的字节输出流。默认输出到控制台。并且该流不需要手动关闭close() 。我们最经常用的方法System.out.println()中,System.out 返回的就是一个 PrintStream 类的对象。
PrintStream out = System.out;
out.println("hahah");

还可以将标准输出流的输出方向修改为某个文件。不过setOut方法要接收一个PrintStream类对象。

这个就是实现日志记录的方法。

try {
   PrintStream printStream = new PrintStream(new FileOutputStream("log.txt"));
   System.setOut(printStream);
   printStream.println("调用了方法");
} catch (FileNotFoundException e) {
   throw new RuntimeException(e);
}

对象流

ObjectInputStream ObjectOutputStream

序列化: Serialize java对象存储到文件中。 将java对象的状态保存下来的过程。
反序列化: DeSerialize将硬盘上的数据重新恢复到内存当中,恢复成java对象。

  1. 如果要让自定义类可序列化和反序列化,一定要实现 Serializable 接口
  2. 通过源代码发现, Serializable接口只是一个标志接口:这个接口当中什么代码都没有。那么它起到一个什么作用呢?起到标识的作用,标志的作用, jqva虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
  3. Serial izable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成
    一个序列化版本号。

序列化单一对象

ObjectOutputStream oos = null;
try {
   oos = new ObjectOutputStream(new FileOutputStream("students"));
   Student s1 = new Student("abc");
   oos.writeObject(s1);
   oos.flush();
} catch (IOException e) {
   throw new RuntimeException(e);
} finally {
   if (oos != null) {
      try {
         oos.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

反序列化单一对象

ObjectInputStream ois = null;
try {
   ois = new ObjectInputStream(new FileInputStream("students"));
   Object student = ois.readObject();
   System.out.println(student);
} catch (IOException | ClassNotFoundException e) {
   e.printStackTrace();
} finally {
   if (ois != null) {
      try {
         ois.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

序列化多个对象 此时要将多个对象放在集合中,并且该集合也需要实现 Serializable 接口

List<Student> list = new ArrayList<>();
list.add(new Student("qwe"));
list.add(new Student("xyz"));
list.add(new Student("jkl"));
ObjectOutputStream oos = null;
try {
   oos = new ObjectOutputStream(new FileOutputStream("objects"));
   oos.writeObject(list);
   oos.flush();
} catch (IOException e) {
   e.printStackTrace();
} finally {
   if (oos != null) {
      try {
         oos.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

反序列化多个对象 反序列化的结果也是放在一个集合中

ObjectInputStream ois = null;
try {
   ois = new ObjectInputStream(new FileInputStream("objects"));
   List<Student> list = (List<Student>) ois.readObject();
   System.out.println(list);
} catch (IOException | ClassNotFoundException e) {
   e.printStackTrace();
} finally {
   if (ois != null) {
      try {
         ois.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

transient 修饰的属性表示不参与序列化

序列化版本号

java语言中是采用什么机制来区分类的?

  1. 首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
  2. 如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

Java,虚拟机看到serializable接口之后,会自动生成一个序列化版本号。这里没有手动写出来, java虚拟机会默认提供这个序列化版本号。建议将序列化版本号手动的写出来。不建议自动生成。

A编写了一个类: com.abc.java.bean.Student implements Serializable B编写了一个类: com.abc.java.bean.Student implements Serializable 不同的人编写了同一个类,但“这两个类确实不是同-一个类”。这个时候序列化版本就起上作用了。对于java,虚拟机来说, java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不-样。所以区分开了。( 这是自动生成序列化版本号的好处)

这种自动生成序列化版本号有什么缺陷?
这种自动生成的序列化版本号缺点是:一旦代码确定之后 ,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号。这个时候java虚拟机会认为这是一个全新的类。( 这样就不好了! )

最终结论:凡是一个类实现了Serial izable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变, java虚拟机会认为是同-一个类。

文件类File

一个File对象有可能对应的是目录,也可能是文件。File只是一个路径名的抽象表示形式。File类中有很多常用方法。