image.png

简单来讲:
你编写了一个java程序,
你用这个java程序从你的电脑上读取文件,就是输入流;
你把你所写的java程序的数据传递给某个文件,就是输出流;

一、四大家族的首领:

java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流

java.io.Reader 字符输入流
java.io.Writer 字符输出流

四大家族的首领都是抽象类。(abstract class)


java.io包下需要掌握的流有16个:

文件专属:
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter

转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter

缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream

数据流专属:
java.io.DataInputStream
java.io.DataOutputStream

标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)

对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)

二、举例

1.FileInputStream

方法摘要 方法 作用
abstract int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 将输入流中读取一定数量 并将其存储在缓冲区数组 b 中。
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。返回总字节数.

演示read[byte[] b]:
image.png
下面while()的初始版

  1. fis=new FileInputStream("E:\\IO\\ceshi\\finally.text");
  2. byte[] bytes=new byte[4];
  3. /* while(true){
  4. int readcount=fis.read(bytes);
  5. if(readcount=-1){
  6. break;
  7. }
  8. //把byte数组转化为字符串,读到几个转换几个
  9. System.out.print(new String(bytes,0,readCount));
  10. }
  11. */

最终版:

  1. import java.io.FileInputStream;
  2. import java.io.FileNotFoundException;
  3. import java.io.IOException;
  4. public class Finally {
  5. public static void main(String[] args) {
  6. FileInputStream fis =null;
  7. try {
  8. fis=new FileInputStream("E:\\IO\\ceshi\\finally.text");
  9. //准备一个byte数组
  10. byte[] bytes=new byte[4];
  11. int readCount=0;
  12. //read(bytes)方法是读到的字节个数为几个,当读到没有时返回-1
  13. while ((readCount=fis.read(bytes))!=-1){
  14. //把byte数组转化为字符串,读到几个转换几个
  15. System.out.print(new String(bytes,0,readCount));
  16. }
  17. } catch (FileNotFoundException e) {
  18. e.printStackTrace();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }finally {
  22. if(fis!=null){
  23. try {
  24. fis.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. }
  31. }

abcdef

2.FileOutputStream

  1. import java.io.FileNotFoundException;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. public class OutTest {
  5. public static void main(String[] args) {
  6. FileOutputStream fos=null;
  7. try {
  8. //这种方式谨慎使用,这种方式会先将原文件清空,然后重新写入
  9. // fos=new FileOutputStream("E:\\IO\\ceshi\\finally.text");
  10. //以追加的方式在文件末尾写入。不会清除原文件内容。
  11. fos=new FileOutputStream("E:\\IO\\ceshi\\finally.text",true);
  12. byte[] bytes ={97,98,99,100};
  13. //将byte数组全部写出!
  14. fos.write(bytes);//abcd
  15. //将byte数组一部分写出!
  16. fos.write(bytes,0,2);//再写出ab
  17. //写完之后一定要刷新
  18. fos.flush();
  19. } catch (FileNotFoundException e) {
  20. e.printStackTrace();
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }finally {
  24. if(fos!=null){
  25. try {
  26. fos.close();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }
  33. }

3.所有文件的复制

  1. import java.io.FileInputStream;
  2. import java.io.FileNotFoundException;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. public class OutTest {
  6. public static void main(String[] args) {
  7. FileInputStream fis=null;
  8. FileOutputStream fos=null;
  9. try {
  10. fis=new FileInputStream("E:\\IO\\ceshi\\resource.txt");
  11. fos=new FileOutputStream("E:\\IO\\ceshi2\\position.txt");
  12. //最核心的:一边读,一边写
  13. byte [] bytes = new byte[1024*1024];//1MB(最多可拷贝1MB)
  14. int readCount=0;
  15. while ((readCount=fis.read(bytes))!=-1){
  16. fos.write(bytes,0,readCount);
  17. }
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }finally {
  23. //分开try,不要一起try
  24. if(fis!=null){
  25. try {
  26. fis.close();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. if(fos!=null){
  32. try {
  33. fos.close();
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. }
  40. }

4.java.io.BufferedReader和转换流

  1. import java.io.*;
  2. /*
  3. 带有缓冲区的字符输入流
  4. 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。
  5. */
  6. public class BufferedReaderTest {
  7. public static void main(String[] args) throws IOException {
  8. /*
  9. 这个构造方法只能传一个字符流。不能传字节流//通过转换流转换(InputStreamReader将字节流转换为字符流)
  10. FileInputStream是节点流,BufferedReader是包装流。
  11. */
  12. BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\IO\\ceshi\\finally.text")));
  13. //br.readLine读一行
  14. String Line = null;
  15. while ((Line = br.readLine()) != null) {
  16. System.out.println(Line);
  17. }
  18. //关闭流
  19. //对于包装流来说,只需要关闭最外层流就行,里面的节点流自动关闭(可以看源码)
  20. br.close();
  21. }
  22. }

5.DataOutputStream和DataInputStream

  1. /*java.io.DataOutputStream:数据专属的流。
  2. 这个流可以将数据连同数据的类型一并写入文件。
  3. 注意:这个文件不是普通文本文档。(这个文件使用记事本打不开。)
  4. */
  5. /*
  6. DataInputStream:数据字节输入流。
  7. DataOutputStream写的文件,只能使用DataInputStream去读。
  8. 并且读的时候你需要提前知道写入的顺序。读的顺序需要和写的顺序一致。才可以正常取出数据。
  9. */
  10. import java.io.DataOutputStream;
  11. import java.io.FileOutputStream;
  12. import java.io.IOException;
  13. public class Data {
  14. public static void main(String[] args) throws IOException {
  15. //创建数据专属的字节输出流
  16. //Dataoutputstream(Outputstream) Outputstream是抽象类无法实例化,我们可以new 他的子类
  17. DataOutputStream dos=new DataOutputStream(new FileOutputStream("E:\\IO\\ceshi\\finally.text"));
  18. //写数据
  19. int i=300;
  20. boolean sex=false;
  21. char c='a';
  22. //写
  23. dos.writeInt(i);
  24. dos.writeBoolean(sex);
  25. dos.writeChar(c);
  26. //刷新
  27. dos.flush();
  28. //关闭最外层
  29. dos.close();
  30. }
  31. }

6.标准输出流

标准输出流是不需要关闭流的

  1. import java.io.FileNotFoundException;
  2. import java.io.FileOutputStream;
  3. import java.io.PrintStream;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Date;
  6. public class Logger {
  7. public static void log(String msg){
  8. try {
  9. //指向一个日志文件
  10. PrintStream out=new PrintStream(new FileOutputStream("log.txt",true));
  11. //改变输出方向
  12. System.setOut(out);
  13. //日期当前时间
  14. Date nowTime = new Date();
  15. SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss: SSS");
  16. String strTime =sdf.format(nowTime);
  17. System.out.println(strTime+":"+msg);
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  1. public class LogTest {
  2. public static void main(String[] args) {
  3. //测试工具类
  4. Logger.log("调用了System类的gc方法,建议启动垃圾回收");
  5. Logger.log("调用了UserService的doSome()方法");
  6. Logger.log("用户尝试登陆,验证失败");
  7. Logger.log("我非常喜欢这个记录工具哦");
  8. }
  9. }
  1. 2021-11-10 17:12:04: 764:调用了System类的gc方法,建议启动垃圾回收
  2. 2021-11-10 17:12:04: 797:调用了UserServicedoSome()方法
  3. 2021-11-10 17:12:04: 801:用户尝试登陆,验证失败
  4. 2021-11-10 17:12:04: 801:我非常喜欢这个记录工具哦

7.序列化和反序列化

IO - 图4
1:参与序列化和反序列化的对象,必须实现Serializable接口。
注意:通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable {
}
2
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
3:
transient关键字表示游离的,不参与序列化。
private transient String name; // name不参与序列化操作

  1. package com.bjpowernode.java.io;
  2. import java.io.Serializable;
  3. public class Student implements Serializable {
  4. private static final long serialVersionUID = 1L;// java虚拟机识别一个类的时候先通过类名,如果类名一致,再通过序列化版本号。
  5. private int no;
  6. private String name;
  7. public Student() {
  8. }
  9. public Student(int no, String name) {
  10. this.no = no;
  11. this.name = name;
  12. }
  13. public int getNo() {
  14. return no;
  15. }
  16. public void setNo(int no) {
  17. this.no = no;
  18. }
  19. public String getName() {
  20. return name;
  21. }
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25. @Override
  26. public String toString() {
  27. return "Student{" +
  28. "no=" + no +
  29. ", name='" + name + '\'' +
  30. '}';
  31. }
  32. }
  1. package com.bjpowernode.java.io;
  2. import java.io.FileOutputStream;
  3. import java.io.ObjectOutputStream;
  4. public class ObjectOutputStreamTest01 {
  5. public static void main(String[] args)throws Exception {
  6. //创建java对象
  7. Student s =new Student(111,"zhangsan");
  8. //序列化
  9. ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("students"));
  10. //序列化对象
  11. oos.writeObject(s);
  12. //刷新
  13. oos.flush();
  14. //关闭
  15. oos.close();
  16. }
  17. }

一次性序列多个对象(参考)

  1. /*
  2. 一次序列化多个对象呢?
  3. 可以,可以将对象放到集合当中,序列化集合。
  4. 提示:
  5. 参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
  6. */
  7. public class ObjectOutputStreamTest02 {
  8. public static void main(String[] args) throws Exception{
  9. List<User> userList = new ArrayList<>();
  10. userList.add(new User(1,"zhangsan"));
  11. userList.add(new User(2, "lisi"));
  12. userList.add(new User(3, "wangwu"));
  13. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
  14. // 序列化一个集合,这个集合对象中放了很多其他对象。
  15. oos.writeObject(userList);
  16. oos.flush();
  17. oos.close();
  18. }
  19. }

8.IO流和Properties的使用

userinfo.properties

  1. username=admin
  2. password=123
  1. import java.io.FileReader;
  2. import java.util.Properties;
  3. public class IoPropertiesTest01 {
  4. public static void main(String[] args) throws Exception {
  5. //新建一个输入流对象
  6. FileReader reader =new FileReader("src/userinfo.properties");
  7. //新建一个Map集合
  8. Properties pro=new Properties();
  9. //调用properties对象的load方法将文件中的数据加载到Map集合中
  10. pro.load(reader);
  11. //通过key来获取value
  12. String username=pro.getProperty("username");
  13. System.out.println(username);
  14. String password=pro.getProperty("password");
  15. System.out.println(password);
  16. }
  17. }

9.File类常用方法

Files.exists():检测⽂件路径是否存在。
Files.createFile():创建⽂件。
Files.createDirectory():创建⽂件夹。
Files.delete():删除⼀个⽂件或⽬录。
Files.copy():复制⽂件。
Files.move():移动⽂件。
Files.size():查看⽂件个数。
Files.read():读取⽂件。
Files.write():写⼊⽂件。

三、面试题

1.序列化和反序列化

  • 序列化: 将对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成对象的过程

序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要的时候把该流读取出来重新构造成一个相同的对象。