一文了解IO框架,从此对IO不再发怵

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

学习java时,常常对各种io流搞得晕头转向,流的种类很多,如果不去分门别类的总结,很容易搞糊涂。本文就尝试去弄清楚io流的分类和使用,以后再见到流可以给别人讲个七七八八。

什么是流:

内存与存储设备之间的传输数据的通道

按照流向的不同,可以分为:
image.png

  • 输入流:将存储设备中的内容读入到内存中
  • 输出流:将内存中的内容写入到存储设备中

image.png
如上图所示:将文件读入内存需要用到输入流,将修改后的内容写会文件需要用到输出流

按照单位不同,可以分为

  • 字节流:以字节为单位,可以读写所有数据
  • 字符流:以字符为单位,只能读写文本数据

按照功能不同,可以分为:

  • 节点流:具有实际传输数据的读写功能
  • 过滤流:在节点流的基础之上增强功能

字节流的类结构

image.png

顶层两个抽象类

  • InputStream:字节输入流 (read()、read(byte[] b)、read(byte[] b,int off,int len))
  • OutputStream:字节输出流 (write()、write(byte[] b)、write(byte[] b,int off,int len))

文件字节流的使用

  1. FileInputStream:读取文件的字节流,将文件读入内存进行自定义的操作
  2. FileOutputStream:写入文件的字节流,将操作之后的文件重新写入文件,可以写入一个新的文件,或者覆盖原来的文件

FileInputStream的使用:

public static void main(String[] args) throws IOException {
                //创建字节输入流
		FileInputStream fileInputStream = new FileInputStream("d:\\aaa.txt");
		//读取一个字节 如果文件读完了则返回-1
		//fileInputStream.read();
		int data = 0;
		while((data = fileInputStream.read())!=-1){
			System.out.println(data);
		}
                //关闭字节流
		fileInputStream.close();
}
复制代码

FileOutputStream的使用,以及使用两者完成文件复制的功能

public static void main(String[] args) throws IOException {
		
		//复制
		//定义输入流
		FileInputStream fis = new FileInputStream("d:\\1.jpg");
		//定义输出流
		FileOutputStream fos = new FileOutputStream("d:\\2.jpg");
		//设置数组
		byte[] buf = new byte[1024];
		int c = 0;
		//一边读 一边写 实现复制
		while((c = fis.read(buf))!=-1){
			fos.write(buf,0,c);
		}
		//关闭流
		fis.close();
		fos.close();
		
		
	}
复制代码

字节缓冲流:

BufferedInputStream、BufferedOutputStream,会创建缓冲区,将文件先放到缓冲区中,再一次性读取或者写出,这样有两个好处

  • 提高io效率,减少访问磁盘的次数,磁盘的访问是相当耗时和消耗资源的。
  • 数据存储在缓冲区中,调用flush将缓冲区的内容一次性写入文件中,也可以直接close(内部会调用flush)。

查看源码,发现BufferedInputStream默认的缓冲区大小为8K,BufferedOutoutStream也是一样的

class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    /**
     * The internal buffer array where the data is stored. When necessary,
     * it may be replaced by another array of
     * a different size.
     */
    protected volatile byte buf[];

 }
复制代码

字节缓冲流的使用方法,跟普通的字节流使用方法很像,只不过创建的类是不一样的。同时创建的时候需要包裹一个基础的字节流,这也是装饰者模式的体现,对原始对象进行封装,添加一些其他的行为。

                //字节输入流
		FileInputStream fileInputStream1 = new FileInputStream("d:\\aaa.txt");
		//创建缓冲流,将fileInputStream1当作参数,对其进行包裹并添加行为方法
		BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream1);
		//通过read()方法一个字节一个字节读取
		int data1 = 0;
		while((data1 = bufferedInputStream.read())!=-1){
			System.out.println((char)data1);
		}
                
                //***************** 多个字节一起读取 *************
		//自定义缓冲区 一块一块读取
		byte[] b1 = new byte[128];
		int count1 = 0;
		while((count1 = bufferedInputStream.read(b1))!=-1){
			System.out.println(new String(b1,0,count1));
		}
		
		bufferedInputStream.close();
复制代码

BufferedOutputStream使用write方法写入缓冲区,flush方法写入磁盘。如果不调用flush方法,则只是写入到缓冲区中,没有写入磁盘文件中。close方法中会自己调用flush方法。

public static void main(String[] args) throws IOException {
		
		//新建缓冲输出流
		FileOutputStream fileOutputStream = new FileOutputStream("d:\\buffer.txt");
		BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
		
		for(int i=0;i<10;i++){
			//写入缓冲区(默认8k)
			bufferedOutputStream.write("abcdefg\r\n".getBytes());
			//刷新到磁盘
			bufferedOutputStream.flush();
		}
		//关闭(内部会调用flush方法)
		bufferedOutputStream.close();
	}
复制代码

对象流:

ObjectOutputStream、ObjectInputStream,使用流传输对象的过程称为序列化(将对象写入流中write的过程)和反序列化(读取并重构一个对象read的过程),所以在序列化和反序列化的过程中,会伴随有大量的io操作,使用对象流的几点好处:

  • 增强缓冲区功能
  • 增强了八种基本数据类型和字符串功能
  • 增强了读写对象的功能(readObject()从流中读取一个对象、writeObject(Object obj)向流中写入一个对象)
序列化和反序列化的实现:

首先创建实例类,用于序列化和反序列化

//创建对象并实现Serializable接口
public class Student implements Serializable{

	/**
	 * 序列化版本号id--保证序列化的类和反序列化的类是同一个类
	 */
	private static final long serialVersionUID = 1L;
        
        int age;
	String name;
        ....
}
复制代码

实现对象的序列化和反序列化操作:

	public static void main(String[] args) throws IOException {
		
                //序列化:
		//表示一个二进制文件
		FileOutputStream fileOutputStream = new FileOutputStream("d:\\stu.bin");
		//创建ObjectOutputStream
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
		//新建对象(该实例需要实现Serializable表明该对象可以序列化)
		Student stu = new Student(12,"zs");
		//写入文件 实现序列化
		objectOutputStream.writeObject(stu);
		//关闭输出流(内部会调用flush方法)
		objectOutputStream.close();
                
                //反序列化:
		//新建文件输入流和对象输入流
		FileInputStream fileInputStream = new FileInputStream("d:\\stu.bin");
		ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
		//读取对象(反序列化)
		Student student = (Student) objectInputStream.readObject();
		//关闭流
		objectInputStream.close();
		System.out.println(student.toString());
	}
复制代码
实现对象的序列化和反序列化时,需要注意的几点:
  • 序列化类必须要实现Serializable接口
  • 序列化类中的对象属性也需要实现Serializable接口
  • 序列化版本号ID(serialVersionUID),保证序列化的类和反序列化的类是同一个类
  • 使用transient(瞬时的)修饰属性,这个属性就不能序列化了
  • 静态属性是不能序列化的,静态属性属于类不属于某个对象
  • 序列化多个对象,可以借助List集合来实现

字符流

字符编码和字符集是两个基础性的概念,很多开发人员对其都并不陌生,但是很少有人能将其讲得很准确。

字符集(character set)是一个系统支持的所有抽象字符的集合。字符(character)就是各种文字和符号,包括国家文字、标点符号、图形符号、数字等。但是我们常说的字符集,其实是指编码字符集,编码字符集是指,这个字符集里的每一个字符,都对应到唯一的一个代码值。

字符编码的定义:是编码字符集的字符和实际的存储值之间的转换关系,当编码方式和解码方式不同,会出现乱码

常见的编码字符集有:

  • ISO-8859-1:收录除了ASCII外,还包括西欧、希腊、泰语、阿拉伯语、希伯来语对应的文字符号
  • UTF-8:针对Unicode码表的可变长度字符编码(一个汉字三个字节)
  • GB2312:简体中文
  • GBk:简体中文、扩充 GB2312的升级版
  • BIG5:繁体中文

字符流的类结构

image.png

文件字符流:

FileReader:

  • read():读取单个字符
  • read(char[] c):从流中读取多个字符,将读到的内容存入c数组,返回实际读到的字符数;如果达到文件的尾部(读完了),则返回-1

使用文件字符流读取文件:

	public static void main(String[] args) throws IOException {
		//创建FileReader
		FileReader fReader = new FileReader("d:\\h.txt");
		//读取
		//单个字符读取
		int data = 0;
		while((data = fReader.read())!=-1){
			System.out.println((char)data);
			
		}
		
		//使用字符数组,多字符读取
		char[] buf = new char[128];
		int count = 0;
		while((count = fReader.read(buf))!=-1){
			System.out.println(new String(buf,0,count));
		}
		
		fReader.close();
	}
复制代码

FileWriter:

  • write(String str)一次性写入多个字符,将b数组中所有的字符,写入输出流

使用文件字符流写入文件:

	public static void main(String[] args) throws IOException {
		//创建FileWriter
		FileWriter fWriter = new FileWriter("d:\\h.txt");
		for(int i = 0;i<10;i++){
			fWriter.write("abcdefg");
			fWriter.flush();
		}
		
		fWriter.close();
	}
复制代码
  • 使用FileReader和FileWriter实现文件复制(只能复制文本文件,不能复制图片/音频等其他二进制文件):
	public static void main(String[] args) throws IOException {
		//创建FileReader
		FileReader fReader = new FileReader("d:\\h1.txt");
		//创建FileWriter
		FileWriter fWriter = new FileWriter("d:\\h2.txt");
		
		int data = 0;
		//读写
		while((data = fReader.read())!=-1){
			fWriter.write(data);
		}
		fReader.close();
		fWriter.close();
	}
复制代码

字符缓冲流:

  • BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现高效读取
  • BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
  1. 使用缓冲流读文件:
    public static void main(String[] args) throws IOException {
		
		//创建缓冲流
		FileReader fileReader = new FileReader("d:\\h.txt");
		BufferedReader bReader = new BufferedReader(fileReader);
		
		//方法1 使用字节数组
		char[] buf = new char[1024];
		int count = 0;
		while((count = bReader.read(buf))!=-1){
			System.out.println(new String(buf,0,count));
		}
		//方法二 使用readLine读取一行
		String line = null;
		while((line = bReader.readLine())!=null){
			System.out.println(line);
		}
		bReader.close();
	}
复制代码
  1. 使用缓冲流写入文件:
	public static void main(String[] args) throws IOException {
		
		//创建
		FileWriter fileWriter = new FileWriter("d:\\w.txt");
		BufferedWriter bWriter = new BufferedWriter(fileWriter);
		//写入
		for(int i = 0;i<10;i++){
			bWriter.write("abcdefg");
			//写入换行符
			bWriter.newLine();
			bWriter.flush();
		}
		bWriter.close();
	}
复制代码

转换流:

InputStreamReader/OutputStreamWriter:

  • 可以将字节流转换为字符流(InputStreamReader将字节输入流转换为字符输入流,OutputStreamWriter将字节输出流转换为字符输出流)
  • 可以设置字符的编码方式

InputStreamReader是字节流通向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符,它使用的字符集可以由名称指定或显式给定。
OutputStreamWriter:字符流通向字节流的桥梁

	public static void main(String[] args) throws IOException {
		
		//创建
		FileInputStream fis = new FileInputStream("d:\\a.txt");
                //设置字符的编码方式
		InputStreamReader isr = new InputStreamReader(fis, "utf-8");
		//读取
		int data = 0;
		while((data = isr.read())!=-1){
			System.out.println((char)data);
		}
		isr.close();
		
		//创建
		FileOutputStream fos = new FileOutputStream("d:\\b.txt");
		OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos,"utf-8");
		//写入
		for(int i = 0 ;i<10;i++){
			outputStreamWriter.write("abcdefg");
		}
		//关闭
		outputStreamWriter.close();
	}
复制代码

File文件相关的api

代表物理盘符中的一个文件或者文件夹。常用的方法如下:

  • createNewFile()—创建一个文件
  • mkdir()—创建一个新目录
  • delete()—删除文件或空目录
  • exists()—判断file对象所代表的对象是否存在
  • getAbsoulutePath()—获取文件的绝对路径
  • getName()—获取名字
  • getParent()—获取文件所在的目录
  • isDirectory()—是否是目录
  • isFile()—是否是文件
  • length()—获取文件的长度
  • listFiles()—获取目录中所有的内容
  • renameTo()— 修改文件名为

结语

本文只是从基本的使用上展示了io框架是如何应用的,通过本文可以从宏观上了解io框架,同时对io框架的分类以及应用有一个更清楚的认识,当然更多的使用还是要看api的,在工作中灵活的使用才能有更深刻的认识。接下来还会从应用中更深入的剖析io。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享