Java中的IO流

本文详细解析了IO流的原理、分类,涵盖了字节流和字符流的使用、缓冲流、转换流、打印流以及对象流,包括File类的操作和典型实例。重点介绍了流的关闭与刷新技巧,以及如何通过File类进行文件操作和路径管理。
摘要由CSDN通过智能技术生成


1. IO 流概述

1.1 什么是 IO 流

I:Input,O:Output。IO 流可以完成硬盘文件的读和写。

1.2 IO 流的分类

  • 按流的方向分:

    • 输入流:进到内存中叫输入;
    • 输出流;从内存中出去叫输出;
  • 按读取数据的方式分:

    • 字节流:按字节方式读取数据,一次读取 1 个字节 byte,等同于 8 个二进制位,万能流,所有类型文件都可以读取;
    • 字符流:按字符方式读取数据,一次读取一个字符,只能读取纯文本文件(txt 文件),不能读取音频等文件,word 也不行;

1.3 close() 和 flush()

所有流都实现了 Closeable 接口,都是可关闭的,都有 close() 方法,用完之后一定要关闭,养成良好习惯,否则会耗费很多资源。

所有输出流都实现了 Flushable 接口,都是可刷新的,都有 flush() 方法,养成良好习惯,输出流最终输出后,一定要记得 flush() 刷新以下,表示将通道中剩余的未输出的数据强行输出完(清空管道)(抖两下(狗头)),否则可能会导致丢失数据。

1.4 流的继承关系图

字节流:
在这里插入图片描述
字符流:
在这里插入图片描述

2. 流的四大家族

  • InputStream:字节输入流

  • OutputStream:字节输出流

  • Reader:字符输入流

  • Writer:字符输出流

它们都是抽象类,是基础的 IO 类,被很多子类继承。而且大部分的 IO 的源码都是 native 标志的,就是说源码都是 C/C++ 写的。

2.1 InputStream

InputStream 是字节输入流, InputStream 是一个抽象类,所有继承了 InputStream 的类都是字节输入流,主要了解以下子类即可:
在这里插入图片描述

  • FileInputStream:文件字节输入流
  • ObjectInputStream:对象字节输入流(反序列化输入流)
  • BufferedInputStream:缓冲字节输入流(继承 FilterInputStream,是包装流)

该类所有方法在出错条件下会引发 IOException 异常。主要方法有:

方法介绍
close()关闭流并释放资源
read()读取下一个字节数据,读不到返回 -1
available()返回流中可读的字节数量
read(byte[] b)从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中
read(byte[] b, int off, int len)从 off 位置读取 len 个字节放入字节数组
skip(long n)跳过指定的字节不读

2.2 OutputStream
在这里插入图片描述

  • FileOutputStream:文件字节输出流
  • ObjectOutputStream:对象字节输出流(基本类型输出流)
  • BufferedInputStream:缓冲字节输出流(继承 FilterOutputStream,是包装流)
  • PrintStream:字节打印流

该类所有方法返回 void,在出错条件下会引发 IOException 异常。主要方法有:

方法介绍
close()关闭流并释放资源
flush()刷新输出流,强制写出所有的缓冲字节
write(int b)向输出流中写入单个字节
write(byte[] b)将 b.length 个字节从指定字节数组中写入输出流
write(byte[] b, int off, int len)将指定字节数组中从 off 开始的 len 个字节写入输出流

2.3 Reader
在这里插入图片描述

  • BufferReader:缓冲字符输入流
  • InputStreamReader:转换流(字节输入流转字符输入流)
  • FileReader:文件字符输入流

该类方法在出错条件下会引发 IOException 异常。主要方法有:

方法介绍
close()关闭流并释放资源
read()读取下一个字符,读不到返回 -1
read(char[] b)将字符读入数组
read(char[] b, int off, int len)读取 b 中从 off 开始的 len 个字符

2.4 Writer
在这里插入图片描述

  • BufferWriter:缓冲字符输出流
  • OutputStreamWriter:转换流(字节输出流转字符输出流)
  • FileWriter:文件字符输出流
  • PrintWriter:字符打印流

该类所有方法返回 void,在出错条件下会引发 IOException 异常。主要方法有:

方法介绍
close()关闭流并释放资源
flush()刷新输出流
write(int c)向输出流写入单个字符
write(char[] b)向输出流中写入字符数组
write(char[] b, int off, int len)向输出流中写入以 off 为起点的 len 个字符
append(char[] c)追加指定字符到此 writer

3. 文件流

文件流主要有:

  • FileInputStream:文件字节输入流
  • FileOutputStream:文件字节输出流
  • FileReader:文件字符输入流
  • FileWriter:文件字符输入流

3.1 FileInputStream

FileInputStream 主要按照字节方式读取文件, 例如我们准备读取一个文件,该文件是 C 盘下的 test.txt。

public class FileInputStreamTest01 {
	public static void main(String[] args) {
		FileInputStream fis = null;		// 输入流
		try {
			fis = new FileInputStream("c:\\test.txt");
			int readCount = 0;
			while ((readCount = fis.read()) != -1) {
				//直接打印
				//System.out.print(b);
				//输出字符
				System.out.print((char)b);
			}
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {			
			if (fis != null){
				try {
					fis.close();
				}catch(IOException e) {
					e.printStackTrace();
				}
			}			
		}
	}
}

3.2 FileOutputStream

FileOutputStream 主要按照字节方式写文件,例如:我们做文件的复制,首先读取文件,读取后再将该文件另写一份保存到磁盘上,这就完成了备份。

public class FileOutputStreamTest01 {
	public static void main(String[] args) {
		FileInputStream fis = null;		// 输入流
		FileOutputStream fos = null;		// 输出流
		try {
			fis = new FileInputStream("c:\\test.txt");
			fos = new FileOutputStream("d:\\test.txt.bak");
			byte[] bytes = new byte[1024 * 1024];	// 一次最多拷贝 1M
			int readCount = 0;
			while ((readCount = fis.read(bytes)) != -1) {	// 边读边写
				fos.write(bytes, 0, readCount);		// 读多少写多少
			}
			System.out.println("文件复制完毕! ");
			
			fos.flush();	// 输出流要刷新
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			// 建议分开 try,一起try的话,第一个异常,那第二个就关不了
			if (fis != null){
				try {
					fis.close();
				}catch(IOException e) {
					e.printStackTrace();
				}
			}
			if (fos != null){
				try {
					fos.close();
				}catch(IOException e) {
					e.printStackTrace();
				}
			}	
		}
	}
}

3.3 FileReader

FileReader 是一字符为单位读取文件,也就是一次读取两个字节。

public class FileReaderTest01 {
	public static void main(String[] args) {
		FileReader fr = null;
		try {
			fr = new FileReader("c:\\test.txt");
			int readCount = 0;
			while ((readCount = fr.read()) != -1) {
				//输出字符
				System.out.print((char)b);
		}
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			if (fr != null){
				try {
					fr.close();
				}catch(IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

3.4 FileWriter

public class FileWriterTest01 {
	public static void main(String[] args) {
		FileWriter fw = null;
		try {
			//以下方式会将文件的内容进行覆盖,FileOutputStream 也是一样
			//w = new FileWriter("c:\\test.txt");
			//w = new FileWriter("c:\\test.txt", false);
			
			//以下为 true 表示,在文件后面追加
			fw = new FileWriter("c:\\test.txt", true);
			fw.write("你好你好!!!! ");	
			fw.write("\n");		//换行
			char[] chars = {'我', '是', '中', '国', '人'};
			fw.write(chars);
			
			fw.flush();
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			if (fw != null){
				try {
					fw.close();
				}catch(IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

4. 缓冲流

缓冲流主要是为了提高效率而存在的,减少物理读取次数,使用时不需要 byte、char 数组,自带缓冲,主要有:

  • BufferedInputStream:字节缓冲输入流
  • BufferedOutputStream:字节缓冲输出流
  • BufferedReader:字符缓冲输入流,提供了实用方法 readLine(),可以直接读取一行
  • BufferedWriter:字符缓冲输出流,提供了 newLine() 可以写换行符

下图是 BufferedInputStream 和 BufferedOutputStream 的作用示意图,其中 FileInputStream 是作为参数传递到 BufferedInputStream 中的,即 BufferedInputStream(FileInputStream)。当一个流的构造方法需要一个流的时候,像 FileInputStream 这样作为参数传递进来的叫节点流,外层的流就是包装流

BufferedReader 和 BufferedWriter 也是类似的。
在这里插入图片描述

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{
        FileReader reader = new FileReader("Copy02.java");
        // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
        // 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
        // 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
        BufferedReader br = new BufferedReader(reader);

        // br.readLine()方法读取一个文本行,但不带换行符。
        String s = null;
        while((s = br.readLine()) != null){
            System.out.print(s);
        }
        
        // 关闭流,对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
        br.close();
    }
}

5. 转换流

转换流主要有两个 InputStreamReader 和 OutputStreamWriter:

  • InputStreamReader:主要是将字节流输入流转换成字符输入流
  • OutputStreamWriter:主要是将字节流输出流转换成字符输出流
public class BufferedReaderTest02 {
    public static void main(String[] args) throws Exception{
        // 字节流
        FileInputStream in = new FileInputStream("Copy02.java");

        // 通过转换流转换(字节流-->字符流)
        InputStreamReader reader = new InputStreamReader(in);

        // 这个构造方法只能传字符流。
        BufferedReader br = new BufferedReader(reader);
        
        String line = null;
        while((line = br.readLine()) != null){		// 读一行返回字符串
            System.out.println(line);
        }

        // 关闭最外层
        br.close();
    }
}

6. 打印流

打印流主要包含两个:PrintStream 和 PrintWriter,分别对应字节流和字符流。

System.out 其实对应的就是 PrintStream,默认输出到控制台,我们可以重定向它的输出,可以定向到文件,也就是执行 System.out.println(“hello”)不输出到屏幕,而输出到文件。

public class PrintStreamTest {
    public static void main(String[] args) throws Exception{

        // 联合起来写
        System.out.println("hello world!");

        // 分开写
        PrintStream ps = System.out;
        ps.println("hello zhangsan");
        ps.println("hello lisi");
        ps.println("hello wangwu");

        // 标准输出流不需要手动close()关闭。
        // 可以改变标准输出流的输出方向吗? 可以
        /*
        // 这些是之前System类使用过的方法和属性。
        System.gc();
        System.currentTimeMillis();
        PrintStream ps2 = System.out;
        System.exit(0);
        System.arraycopy(....);
         */

        // 标准输出流不再指向控制台,指向“log”文件。
        PrintStream printStream = new PrintStream(new FileOutputStream("log"));
        // 修改输出方向,将输出方向修改到"log"文件。
        System.setOut(printStream);
        // 再输出
        System.out.println("hello world");
        System.out.println("hello kitty");
        System.out.println("hello zhangsan");
    }
}

7. 对象流

对象流可以将 Java 对象转换成二进制写入磁盘,这个过程通常叫做序列化,并且还可以从磁盘读出完整的 Java 对象,而这个过程叫做反序列化

对象流主要包括: ObjectInputStream 和 ObjectOutputStream。

实现序列化和反序列化

如果实现序列化该类必须实现序列化接口 java.io.Serializable,该接口没有任何方法,该接口只是一种标记接口,标记这个类是可以序列化和反序列化的。

那么它起到一个什么作用呢?起到标识的作用,标志的作用,Java 虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。Serializable 这个标志接口是给 Java 虚拟机参考的,Java 虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。

可以一次序列化多个对象,将对象放入集合,序列化集合,集合和里面的对象都要实现 Serializable 接口。

如果不希望类里面的某个属性序列化,可以加关键字 transient

Java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

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

所以凡是一个类实现了 Serializable 接口,建议给该类提供一个不变的序列化版本号:
private static final long serialVersionUID = xxxxxxxxx;建议手动写,不建议自动生成。

序列化:

public class ObjectOutputStreamTest01 {
    public static void main(String[] args) throws Exception{
        // 创建student对象,实现了 Serializable 接口
        Student s = new Student(1111, "zhangsan");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));

        // 序列化对象
        oos.writeObject(s);

        // 刷新
        oos.flush();
        // 关闭
        oos.close();
    }
}

反序列化:

public class ObjectInputStreamTest01 {
    public static void main(String[] args) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
        // 开始反序列化,读
        Object obj = ois.readObject();
        // 反序列化回来是一个学生对象,所以会调用学生对象的toString方法。
        System.out.println(obj);
        ois.close();
    }
}

8. File 类

File 类和四大家族没有关系,所以File类不能完成文件的读和写。

一个 File 对象有可能对应的是目录,也可能是文件。File 只是一个路径名的抽象表示形式。

常用方法如下:

public class FileTest01 {
    public static void main(String[] args) throws Exception {
        // 创建一个File对象
        File f1 = new File("D:\\file");

        // 判断是否存在!
        System.out.println(f1.exists());

        // 如果D:\file不存在,则以文件的形式创建出来
        /*if(!f1.exists()) {
            // 以文件形式新建
            f1.createNewFile();
        }*/

        // 如果D:\file不存在,则以目录的形式创建出来
        /*if(!f1.exists()) {
            // 以目录的形式新建。
            f1.mkdir();
        }*/

        // 可以创建多重目录吗?
        File f2 = new File("D:/a/b/c/d/e/f");
        /*if(!f2.exists()) {
            // 多重目录的形式新建。
            f2.mkdirs();
        }*/

        File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
        // 获取文件的父路径
        String parentPath = f3.getParent();
        System.out.println(parentPath); //D:\course\01-开课
        File parentFile = f3.getParentFile();
        System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());

        File f4 = new File("copy");
        System.out.println("绝对路径:" + f4.getAbsolutePath()); // C:\Users\Administrator\IdeaProjects\javase\copy
    }
}
public class FileTest02 {
    public static void main(String[] args) {
        File f1 = new File("D:\\course\\01-开课\\开学典礼.ppt");
        // 获取名称
        System.out.println("文件名称:" + f1.getName());

        // 判断是否是一个目录
        System.out.println(f1.isDirectory());

        // 判断是否是一个文件
        System.out.println(f1.isFile());

        // 获取文件最后一次修改时间
        long haoMiao = f1.lastModified(); // 这个毫秒是从1970年到现在的总毫秒数。
        // 将总毫秒数转换成日期?????
        Date time = new Date(haoMiao);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(time);
        System.out.println(strTime);

        // 获取文件大小
        System.out.println(f1.length()); //216064字节。
    }
}
public class FileTest03 {
    public static void main(String[] args) {
        // File[] listFiles()
        // 获取当前目录下所有的子文件。
        File f = new File("D:\\course\\01-开课");
        File[] files = f.listFiles();
        // foreach
        for(File file : files){
            //System.out.println(file.getAbsolutePath());
            System.out.println(file.getName());
        }
    }
}

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaIO流操作基本流程如下: 1. 创建流对象:通过File类或其他相关类创建输入流或输出流对象。 2. 打开流:使用输入流或输出流对象的open()方法打开流,这样就可以读取或写入数据。 3. 读取或写入数据:使用输入流或输出流对象的read()或write()方法读取或写入数据。 4. 关闭流:使用输入流或输出流对象的close()方法关闭流,释放资源。 需要注意的是,在使用IO流操作时,要遵循“先打开、后关闭”的原则,以确保数据的完整性和流的正确性。同时,在操作过程也需要进行异常处理,以避免出现不必要的错误。 ### 回答2: JavaIO流基本操作流程如下: 1. 打开文件或者建立网络连接:使用File类或者URL类打开文件或者建立网络连接。 2. 创建流对象:根据需要选择输入流(读取数据)或输出流(写入数据),并创建相应的流对象。常见的输入流有FileInputStream、BufferedReader等,常见的输出流有FileOutputStream、BufferedWriter等。 3. 读取或写入数据:使用流对象读取或写入数据。对于输入流,可以通过调用相关方法(如read()、readline()等)逐个字符或逐行读取数据;对于输出流,可以通过调用相应方法(如write()、print()等)逐个字符或逐行写入数据。 4. 关闭流:读取或写入完成后,需要关闭文件或网络连接,以释放资源。可以调用流对象的close()方法来关闭流。 需要注意的是,在处理IO流时,应该始终使用try-catch-finally块,以确保在发生异常时能够正确关闭流。可以把IO操作放在try块,catch块用于捕获异常,并在finally块关闭流。 另外,为了提高IO效率,可以考虑使用缓冲流来进行读写操作。缓冲流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)可以通过缓冲区将数据从源读入到缓冲区,再从缓冲区写入到目标,提高读写的速度。 以上就是JavaIO流基本操作的流程。根据实际需要选择合适的流对象,并遵循打开、读取/写入、关闭的流程,可以实现灵活、高效的IO操作。 ### 回答3: 在JavaIO流是用于处理输入输出操作的工具。下面是JavaIO流的基本操作流程: 1. 创建流对象:通过使用Java的InputStream和OutputStream类来创建流对象。InputStream类用于读取输入流,而OutputStream类用于写入输出流。 2. 打开流:通过使用流对象对应的构造函数和方法来打开输入和输出流。根据具体情况,可以选择文件流、网络流或内存流来打开流。 3. 读取/写入数据:使用流对象提供的读取和写入方法来读取和写入数据。例如,使用InputStream的`int read()`方法来读取一个字节的数据,使用OutputStream的`void write(int b)`方法来写入一个字节的数据。 4. 关闭流:在读取或写入结束后,必须关闭流以释放相关资源。通过调用流对象的`close()`方法来关闭流。 需要注意的是,在处理异常的时候,我们需要对可能出现的`IOException`进行处理。可以使用try-catch语句块来捕获和处理异常。 流程示例: ```java import java.io.*; public class IOExample { public static void main(String[] args) { try { // 1. 创建流对象 FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); // 2. 打开流 // 3. 读取/写入数据 int data; while ((data = fis.read()) != -1) { fos.write(data); } // 4. 关闭流 fis.close(); fos.close(); } catch(IOException e) { e.printStackTrace(); } } } ``` 上述示例,我们创建了一个用于将一个文件的内容拷贝到另一个文件的程序。首先,我们创建了一个FileInputStream对象来读取输入文件的内容,然后创建了一个FileOutputStream对象来写入输出文件。接下来,我们通过循环从输入流读取一个字节的数据,并将其写入到输出流,直到读取完所有的数据。最后,我们关闭了流对象来释放资源。 这就是JavaIO流的基本操作流程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值