IO流学习笔记

IO流

1. IO流的分类

按照输入/输出分类
把硬盘中的数据读入内存,叫输入流
把内存中的数据写入硬盘,叫输出流
按照字节/字符分类
IO流还可以分为字节流和字符流

所以两种分类结合,就可以分为这个四类,这几个类都是IO流的顶层父类
在这里插入图片描述

2.字节流

一切皆为字节,计算机上的文件、图片、视频,都是以字节的形式存储的。传输时,字节流可以传输任意类型的文件。
在这里插入图片描述

2.1 字节输出流的顶层类OutputStream

java.io.OutputStream是一个抽象类,是所有字节输出流的超类。
这个类定义了子类共性的成员方法。

成员方法
方法1.public void close()
关闭字节输出流,并释放与此流相关的任何系统资源。

方法2.public void flush()
刷新字节输出流,并强制缓冲中的字节被写出。

方法3.public void write(byte[] b)
将字节数组b,写入此输出流
byte[]是字节数组,byte是字节
1个字节=8个比特位(例如10100111)

方法4.public void write(byte[] b, int off, int len)
将字节数组b,从off开始的,len长度的字节,写入此输出流。

方法5.public abstract void write(int b)
将指定的字节写入此输出流

2.2 文件的字节输出流FileOutputStream

1.继承关系: java.io.FileOutputStream extends OutputStream
2.功能: FileOutputStream把内存中的数据,写入到硬盘的文件中。
3.把数据写入文件的原理:
java程序 —> JVM —> OS操作系统 —> OS调用写数据的方法 —> 把数据写入文件中

2.2.1 FileOutputStream的构造方法

构造方法1.FileOutputStream(String name)
String name是写入数据的文件路径
构造方法2.FileOutputStream(File file)
File file是写入数据的文件

以上两个构造方法的作用:
1.创建一个FIleOutputStream对象;
2.根据构造方法中的文件/文件路径,创造一个空的文件;
3.把FileOutputStream对象指向文件

构造方法3.FileOutputStream(String name, boolean append)
String name是写入数据的文件路径
构造方法4.FileOutputStream(File file, boolean append)
File file是写入数据的文件

以上两个构造方法的boolean append表示追加写
1.true创建FileOutputStream对象时,不会覆盖原文件,继续在原文件末尾追加写;
2.false创建FileOutputStream对象时,会创建一个新文件,覆盖原文件,不会在原文件追加写;

2.2.2 文件的字节输出流的使用

FileOutputStream的使用步骤
1.创建一个FileOutputStream对象,构造方法的参数是写入数据的目的地;
2.调用FileOutputStream对象的write方法,把数据写入到文件中;
3.释放系统资源(流使用会占用内存,使用完毕要清内存)。

2.2.2.1 一次输出一个字节到文件

举例1
使用FileOutputStream的public abstract void write(int b)方法

public static void mian(String[] args) throw IOException {
// step1.创建一个FileOutputStream对象,构造方法的参数是写入数据的目的地
FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\a.txt");
// step2.调用FileOutputStream对象的write方法,把数据写入到文件中;
// 这里用public abstract void write(int b),将指定的字节写入此输出流
fos.write(97);
// step3.释放系统资源
fos.close();
}

注意:

  1. 任何文本编辑器(txt、notepad++、word)在打开文件的时候,都会查询编码表,把字节按照编码表转成字符表示,方便我们阅读。
  2. 规则:
    0-127,就查询ASCII表。所以这里写入的97,a.txt打开显示的是a
    其他值,查询系统默认编码表
2.2.2.2 一次输出多个字节到文件

使用FileOutputStream的public void write(byte[] b)方法或者public void write(byte[] b, int off, int len)方法,可以一次输出多个字节到文件

举例1
使用public void write(byte[] b)方法

public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b)
byte[] bytes = new byte[]{65, 66, 67, 68, 69};
fos.write(bytes);
fos.close();
}

此时,b.txt打开显示的是ABCDE,因为bytes里的值在0-127之间会查询ASCII表。

举例2
public void write(byte[] b)方法
改变bytes的值,里面有不在0-127范围的值

public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b)
byte[] bytes = new byte[]{-65, 66, -67, 68, 69};
fos.write(bytes);
fos.close();
}

有不在0-127范围内的,此时会查询系统的默认编码表,按照编码表的规则将字节转换为字符。

举例3
使用public void write(byte[] b, int off, int len)方法

public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b, int off, int len)
byte[] bytes = new byte[]{65, 66, 67, 68, 69};
fos.write(bytes,1,2);
fos.close();
}

此时打开b.txt,只有BC

举例4
使用String的getBytes()方法获取字节数组

public static void mian(String[] args) throw IOException {
  FileOutputStream fos = new FileOutputStream(new    File("09_IOAndProperties\\b.txt"));
  byte[] bytes = "您好".getBytes();
  System.out.println(bytes); // [-28, -67, -96, -27, -91, -67] 此时两个字符打印出来有6个字节,是因为IDEA里的编码是UTF-8,一个中文字符对应3个字节,而windows里的默认编码是GBK,一个中文字符对应2个字节。
  fos.write(bytes);
  fos.close();
}

此时b.txt可以看到"您好"

2.2.2.3 追加写

举例
在FileOutputStream的构造方法里,把boolean append设置为true

public static void mian(String[] args) throw IOException {
  FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\b.txt", true);
  byte[] bytes = "您好".getBytes();
  fos.write(bytes);
  fos.close();
}

执行第一次,b.txt里面是“您好”。执行第二次,b.txt里面是“您好您好”。

2.2.2.4 写换行

不同操作系统的换行符不同,windows中是\r\n,linux中是/n,mac中是/r

public static void mian(String[] args) throw IOException {
  FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\b.txt", true);
  for(int i=0; i < 5; i++) {
	 fos.write("您好".getBytes());
	 fos.write("\r\n".getBytes());
	}
  fos.close();
}

此时,b.txt中会出现换行的“您好”

2.3 字节输入流的顶层类InputStream

InputStream是抽象类,是所有字节输入流的超类。
这个类定义了子类共性的成员方法
成员方法
方法1.public int read(),从输入流中,读取数据的下一个字节
方法2.public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中
方法3.public void close(), 关闭输入流,并释放相关系统资源

2.4 文件的字节输入流FileInputStream

java.io.FileInputStream继承了InputStream类
作用:FileInputStream把硬盘文件中的数据,读取到内存中使用

2.4.1 FileInputStream的构造方法

构造方法1. FileInputStream(String name)
参数:String name读取的文件路径
构造方法2. FileInputStream(File file)
参数:File file读取的文件

构造方法的作用

  • 创建一个FileInputStream对象
  • 把FileInputStream对象指向要读取的文件

读取的原理
硬盘–>内存:
java程序 --> JVM --> OS --> 调用OS的读取方法 --> 读取文件

2.4.2 FileInputStream的使用

FIleInputStream的使用步骤:
1.创建FileInputStream对象,构造方法中传要读取的文件
2.使用FileInputStream对象的read方法,读取文件
3.释放资源

2.4.2.1 一次读一个字节

此时a.txt里的内容是abc

public static void main(String[] args) throws IOException{
	// 1.创建FileInputStream对象,构造方法中传要读取的文件
	FileInputStream fis = new FileInputStream("09_IOAndProperties\\a.txt");
	// 2.使用FileInputStream对象的read方法,读取文件
	// 这里使用int read()读取文件中的一个字节并返回,读取到文件的末尾返回-1
	int res = fis.read();
	System.out.println(res); // 打印出来是97,也就是a
	
	res = fis.read();
	System.out.println(res); // 打印出来是98,也就是b

	res = fis.read();
	System.out.println(res); // 打印出来是99,也就是c

	res = fis.read();
	System.out.println(res); // 打印出来是-1
	// 3.释放资源
	fis.close();
}

以上有重复的步骤,使用while循环读,结束条件是读取到-1

public static void main(String[] args) throws IOException{
	FileInputStream fis = new FileInputStream("09_IOAndProperties\\a.txt");
	int res = 0; // 记录读取到的字节
	while((res=fis.read()) != -1) {
		System.out.println(res);
	}
 	fis.close();
}

打印出来是97 98 99
注意

  1. (res=fis.read()) != -1的含义,为什么要用一个变量res来接收读取的数据。
    • fis.read()读取一个字节
    • res = fis.read() 把读取到的字节赋值给res
    • (res=fis.read()) != -1判断变量res不等于-1

2.写成这样就是错的,因为fis.read()每读一次,指针会往后移动

while(fis.read() != -1) {
	System.out.println(fis.read());
}
2.4.2.2 一次读取多个字节

b.txt里的是ABCDE

public static void main(String[] args) throws IOException{
	// 1.创建FileInputStream对象,构造方法中传要读取的文件
	FileInputStream fis = new FileInputStream("09_IOAndProperties\\b.txt");
	// 2.使用FileInputStream对象的read方法,读取文件
	// 这里使用public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中。
	byte[] bytes = new byte[2];
	int len = fis.read(bytes);
	System.out.println(len); // 打印的是2
	System.out.println(Arrays.toString(bytes)); // 打印的是[65,66]
	System.out.println(new String(bytes)); // 打印的是AB

	len = fis.read(bytes);
	System.out.println(len); // 打印的是2
	System.out.println(new String(bytes)); // 打印的是CD

	len = fis.read(bytes);
	System.out.println(len); // 打印的是1
	System.out.println(new String(bytes)); // 打印的是ED

	len = fis.read(bytes);
	System.out.println(len); // 打印的是-1
	System.out.println(new String(bytes)); // 打印的是ED
	// 3.释放资源
	fis.close();
}

注意:
1.String类的构造方法里有:

  • String(byte[] bytes)把字节数组转为字符串
  • String(byte[] bytes, int offset, int length)把字节数组的一部分转为字符串,offset是转换开始的索引,length是转换的字节个数

2.要注意public int read(byte[] b)方法里的int是啥?byte[]是啥?

  • len是读取到的有效字节个数
  • byte[] 起到缓冲的作用,存读取到的多个字节。bytes的长度通常定义为1024或者1024的整数倍

3.读取的原理

  • b.txt里内容是ABCDE,其实在后面还会有一个操作系统的结束标记
  • byte[] bytes = new byte[2]每次可以读两个字节
  • 第一次读取,bytes里的是AB,len = fis.read(bytes)是2
  • 第二次读取,bytes里的是CD, len = fis.read(bytes)是2
  • 第三次读取,只有效读取到E,那么bytes里的是C会被覆盖掉,D没有变, 所以打印出来是ED,len = fis.read(bytes)是1
  • 第四次读取,读到的是结束标记,len此时是-1

以上代码可以使用while来优化,结束条件读取到-1
此时打印出来是ABCDE

public static void main(String[] args) throws IOException{
	// 1.创建FileInputStream对象,构造方法中传要读取的文件
	FileInputStream fis = new FileInputStream("09_IOAndProperties\\b.txt");
	// 2.使用FileInputStream对象的read方法,读取文件
	// 这里使用public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中。
	byte[] bytes = new byte[2];
	int len = 0; // 记录每次读取的有效字节个数
	while((len = fis.reas(bytes)) != -1) {
		System.out.println(new String(bytes, 0, len));
	}
	// 3.释放资源
	fis.close();
}

2.5 例子:文件的复制

将C盘中的1.jpg图片,复制到D盘。
步骤:

  • 创建一个字节输入流对象,构造方法中绑定要读取的数据源
  • 创建一个字节输出流对象,构造方法中绑定要写入的目的地
  • 使用字节输入流对象的read方法读取文件
  • 使用字节输出流的write方法,把读取到的字节写入到目的文件中
  • 释放资源, 先关闭写的流对象,再关闭读的流对象。因为如果写完了,肯定已经读完了。
public static void main(String[] args) throws IOException{
	// 1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
	FileInputStream fis = new FileInputStream("c:\\1.jpg");
	// 2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
	FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
	// 3.使用字节输入流对象的read方法读取文件
	byte[] bytes = new bytes[1024];
	int len = 0;
	while((len = fis.read(bytes)) != -1) {
		// 4.使用字节输出流的write方法,把读取到的字节写入到目的文件中
		fos.write(bytes, 0, len);
	}
	// 5. 释放资源, 先关闭写的流对象,再关闭读的流对象。因为如果写完了,肯定已经读完了。
	fos.close();
	fis.close();
}

2.6 使用字节流的问题

使用字节流读取中文时会遇到问题,因为GBK中,1个中文占用2个字节,UTF-8中,一个中午占用3个字节。
举例
c.txt的内容是"你好",编码格式是UTF-8,也就是"你"占用3个字节,"好"占用3个字节。此时打印出来的是6个数字,因为每次就读取1/3个中文,如果用char强转,打印出来是乱码。但是用字节流读取英文字符是没有问题的。
为了解决这个问题,Java提供了字符流,字符流一次读写一个字符,不管字符是中文、英文还是数字。

public static void main(String[] args) throws IOException{
	FileInputStream fis = new FileInputStream("c.txt");
	int len = 0;
	while((len = fis.read()) != -1) {
		System.out.println(len); // 打印出来的是数字
		System.out.println((char)len); // 打印出来的是乱码
	}
	fis.close();
}

3. 字符流

3.1 字符输入流的顶层类Reader

  • java.io.Reader类是一个抽象类,是字符输入流的顶层父类
  • Reader类里定义了共用的成员方法
    • int read() 一次读取一个字符
    • int read(char[] cbuf) 一次读取多个字符,并将字符放入数组
    • void close() 释放资源

3.2 文件的字符输入流FileReader类

  • 把硬盘文件中的数据以字符的形式读取到内存中
  • FileReader类的顶层父类就是Reader类
  • java.io.FileReader extends InputStreamReader extends Reader
  • InputStreamReader类是转换流

3.2.1 构造方法

构造方法1.FileReader(String fileName)
参数String fileName是要读取的文件路径名
构造方法2.FileReader(File file)
参数File file是要读取的文件

  • 构造方法的作用
    • 创建一个FileReader对象
    • 把FileReader对象指向要读取的文件

3.2.2 FileReader的使用

FileReader的使用步骤

  • 创建FileReader对象,构造方法中传要读取的数据源
  • 使用FileReader对象的read()方法读取文件
  • 释放资源
3.2.2.1 一次读取一个字符

举例
c.txt的内容是"您好abc123"

public static void main(String[] args) throws IOException{
	// 1.创建FileReader对象,构造方法中传要读取的数据源
	FileReader fr = new FileReader("src\\c.txt");
	// 2.使用FileReader对象的read()方法读取文件
	// 使用int read()读取单个字符并返回
	int len = 0;
	while((len = fr.read()) != -1) {
		System.out.println(len); // 此时打印的是数字,因为len就是int型,要转回字符
		System.out.print((char)len); // 此时打印的是: 您好123abc###
 	}
	fis.close();
}
3.2.2.2 一次读取多个字符

c.txt的内容是"您好abc123"

public static void main(String[] args) throws IOException{
	// 1.创建FileReader对象,构造方法中传要读取的数据源
	FileReader fr = new FileReader("src\\c.txt");
	// 2.使用FileReader对象的read()方法读取文件
	// 使用int read(char[] cbuf)一次读取多个字符,并将字符存在数组char[] cbuf中,返回有效读取的字符个数
	int len = 0; // 每次读取到的有效字符个数
	char[] cs = new char[1024];
	while((len = fr.read(cs)) != -1) {
	  System.out.printlnl(new String(cs, offset, count)); //打印出来是:您好abc123###
 	}
	fis.close();
}
  • String类的构造方法
    • String(char[] value) 把字符数组转成字符串
    • String(char[] value, int offset, int count) 把字符数组从offset开始,count个字符转成字符串

3.3 字符输出流的顶层类Writer

  • java.io.Writer是所有字符输出流的顶层父类,是抽象类
  • 定义了字符输出流中共性的成员方法
    • void write(int c) 写入单个字符
    • void write(char[] cbuf)写入字符数组
    • abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分,从off开始,写len个字符
    • void write(String str) 写入字符串
    • void write(String str, int off, int len) 写入字符串的一部分,从off开始,写入len个字符
    • void flush() 刷新该流的缓冲
    • void close() 关闭该流,但要先刷新它

3.4 文件的字符输出流FileWriter

  • java.io.FileWriter extends OutputStreamWriter extends Writer
  • OutputStreamWriter是转换流
  • FileWriter类作用:把内存中的字符数据写入文件

3.4.1 构造方法

构造方法1 FileWriter(String fileName)
参数String fileName是要写入数据的文件路径名
构造方法2 FileWriter(File file)
参数File file是要写入数据的文件

  • 构造方法的作用
    • 创建一个FileWriter对象
    • 根据构造方法中的文件/文件路径,创建要写入的文件
    • 把FileWriter对象指向创建好的文件

构造方法3 FileWriter(String fileName, boolean apend)
参数String fileName是要写入数据的文件路径名
构造方法4 FileWriter(File file, boolean append)
参数File file是要写入数据的文件

以上两个构造函数中的boolean append:

  • true:不会创建新的文件去覆盖原来的文件,可以追加写
  • false:创建新的文件覆盖原来的文件,不可以追加写

3.4.2 FileWriter的使用

  • 使用步骤

    • 创建FileWriter对象,构造方法中绑定要写入数据的目的地
    • 使用FileWriter对象的write方法,把数据写入内存缓冲区中(这里有个字符转换成字节的过程)
    • 使用FileWriter对象的flush方法,把内存缓冲区中的数据,刷新到文件中
    • 释放资源(会先把内存缓冲区中的数据刷新到文件中)
  • 字符输出流与字节输出流的最大区别是,字符输出流不是直接把数据写入到文件中,而是写入到内存缓冲区中。字节输出流是直接把数据写入到硬盘的文件中。

3.4.2.1 一次输出单个字符

此时,c.txt里显示a

public static void main(String[] args) throws IOException{
	// 1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
	FileWriter fw = new FileWriter("src\\c.txt");
	// 2.使用FileWriter对象的write方法,把数据写入内存缓冲区中(这里有个字符转换成字节的过程)
	// 使用void write(int c) 写入单个字符
	fw.write(97); // 如果没有后续的flush或者close操作,数据不会被写入到c.txt中。此时数据还在内存中,停止程序,数据会消失。
	// 3.使用FileWriter对象的flush方法,把内存缓冲区中的数据,刷新到文件中
	fw.flush();
	// 4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
	fw.close();
}
3.4.2.2 flush方法与close方法的区别
+ flush:刷新缓冲区,流对象可以继续使用
+ close:先刷新缓冲区,然后通知系统释放资源,流对象不可以继续使用 
public static void main(String[] args) throws IOException{
	FileWriter fw = new FileWriter("src\\d.txt");
	fw.write(97);
	fw.flush();
	// 刷新后,流对象可以继续使用
	fw.write(98);
	fw.flush();
	fw.close();
	// close方法后,流对象已经关闭,从内存中消失了,不能再使用
	fw.write(99); // 这里会报IOException,告诉Stream Closed
}

d.txt里只有ab,没有c

3.4.2.3 一次输出多个字符/字符串
public static void main(String[] args) throws IOException{
	FileWriter fw = new FileWriter("src\\e.txt");
	char[] charArr = {'a', 'b', 'c', 'd', 'e'};
	// 1.void write(char[] cbuf)写入字符数组
	fw.write(charArr); // abcde

	// 2.abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分,从off开始,写len个字符
	fw.write(charArr, 1, 3); // bcd

	// 3.void write(String str) 写入字符串
	fw.write("我是程序员"); // 我是程序员

	// 4.void write(String str, int off, int len) 写入字符串的一部分,从off开始,写入len个字符
	fw.write("我是程序员", 2, 3); //程序员

	fw.close();
}
3.4.2.4 追加写
public static void main(String[] args) throws IOException{
	// 构造方法里的boolean append设置为true,追加写
	FileWriter fw = new FileWriter("src\\f.txt", true); 
	for(int i=0; i < 10; i++) {
		fw.write("helloworld");
	}
	fw.close();
}

运行一次,f.txt文件内容是10次不带换行的helloworld
运行2次,f.txt文件内容是20次不带换行的helloworld

3.4.2.5 换行写

换行符号

  • windows换行是\r\n
  • linux换行是/n
  • mac换行是/r
public static void main(String[] args) throws IOException{
	// 构造方法里的boolean append设置为true,追加写
	FileWriter fw = new FileWriter("src\\f.txt", true); 
	for(int i=0; i < 10; i++) {
		fw.write("helloworld" + "\r\n");
	}
	fw.close();
}

运行一次,f.txt文件里的是10个带换行的helloworld
运行2次,f.txt文件里的是20个带换行的helloworld,说明是追加写的

4. 流的异常

4.1 jdk1.7之前处理流异常的方式

  • 格式
    try{
    可能产生异常的代码
    }catch(异常类 变量名){
    异常的处理逻辑
    }finally{
    一定要执行的代码,比如释放资源
public static void main(String[] args) {
	// 提高fw变量的作用域,让finally里可以使用fw
	FileWriter fw = null;
	try{
		// 可能会产生异常的代码
	    fw = new FileWriter("src\\c.txt", true);
		for(int i=0; i < 10; i++) {
			fw.write("helloworld" + "\r\n");
		}
		// fw.close(); 关闭资源的代码放在这里有问题,因为一旦上面的代码执行有异常,这里就不会执行
	} catch (IOException e) {
		// 异常的处理
		System.out.println(e);
	} finally {
		// 一定要执行的代码,比如释放资源
		// 如果最上面的fw创建的时候,没有创建成功,fw是null,下面的fw.close()还会抛出空指针异常,所以要先if判断下
		if(fw != null) {
			try {
				// fw.close也会抛出IOException异常,所以要在这里try catch
				fw.close()
		    } catch(IOException e) {
				Syste.out.println(e);
			}
		}
	}
}

以上的实现非常的复杂

4.2 jdk1.7之后处理流异常的方式

  • JDK7的新特性
    在try的后面可以增加一个(),这个括号中可以定义流对象,流对象的作用域是try中有效,try中代码执行完毕,会自动把流对象释放,不用写finally的部分来释放流对象了。

  • 格式
    try(定义流对象1;定义流对象2…){
    可能产生异常的代码
    }catch(异常类 变量名){
    异常的处理逻辑

  • 以复制图片为例

public static void main(String[] args) {
	// 在try的()里定义流对象
	try(
		FileInputStream fis = new FileInputStream(c:\\1.jpg);
		FileOutputStream fos = new FileOutputStream(d:\\2.jpg);
	){
		// 可能会产生异常的代码
	   int len = 0;
	   while((len = fis.read()) != -1) {
			fos.write(len);
		}
	} catch (IOException e) {
		// 异常的处理
		System.out.println(e);
	}
}

5. 缓冲流

  • 之前的字节流和字符流都是IO流的入门,还有几种强大的流

    • 能够高效读写的缓冲流
    • 能够转换编码的转换流
    • 能够持久化对象的序列化流
  • 缓冲流有4种

    • 字节缓冲流:
      • BufferedInputStream缓冲字节输入流
      • BufferedOutputStream缓冲字节输出流
    • 字符缓冲流:
      • BufferedReader缓冲字符输入流
      • BufferedWriter缓冲字符输出流
  • 缓冲流的原理:创建流对象时,会创建一个内置的缓冲区数组,通过缓冲区读写,减少系统的IO次数,从而提高读写的效率。

5.1 缓冲的字节输出流BufferedOutputStream类

  • 继承关系:java.io.BufferedOutputStream extends OutputStream
  • 继承自父类OutputStream的共性成员方法:
    • 方法1.public void close()
      关闭字节输出流,并释放与此流相关的任何系统资源。
    • 方法2.public void flush()
      刷新字节输出流,并强制缓冲中的字节被写出。
    • 方法3.public void write(byte[] b)
      将字节数组b,写入此输出流
    • 方法4.public void write(byte[] b, int off, int len)
      将字节数组b,从off开始的,len长度的字节,写入此输出流。
    • 方法5.public abstract void write(int b)
      将指定的字节写入此输出流

5.1.1 构造方法

  • 构造方法1. BufferedOutputStream(OutputStream out)

    • 功能:创建缓冲的字节输出流,以将数据写入指定的底层输出流
  • 构造方法2.BufferedOutputStream(OutputStream out, int size)

    • 创建缓冲的字节输出流,以将size大小的缓冲区数据,写入指定的底层输出流
  • 以上两个构造方法的参数:

    • OutputStream out:字节输出流。
      • 实际使用时,可以传递FileOutputStream对象,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
    • int size:用于指定缓冲流内部的缓冲区大小,不指定的话,就是默认的大小。

5.1.2 BufferedOutputStream的使用

  • 使用步骤
    • 创建一个FileOutputStream对象,构造方法中绑定要输出的目的地
    • 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FIleOutputStream的效率
    • 使用BufferedOutputStream对象的write方法,把数据写入到缓冲区中
    • 使用BufferedOutputStream对象的flush方法,把缓冲区中的数据,刷新到文件中
    • 释放资源
      • 会先自动调用flush方法刷新数据,所以第4步可以省略
      • 关闭缓冲流就行了,不用手动关闭基本的字节流,因为关闭缓冲流的时候,自动地关闭字节流
5.1.2.1 一次输出多个字节
public static void main(String[] args) throws IOException{
	// 1.创建一个FileOutputStream对象,构造方法中绑定要输出的目的地
	FileOutputStream fos = new FileOutputStream("src\\a.txt");
	// 2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FIleOutputStream的效率
	BufferedOutputStream bos = new BufferedOutputStream(fos);
	// 3.使用BufferedOutputStream对象的write方法,把数据写入到缓冲区中
	bos.write("写入数据到缓冲区".getBytes());
	// 4.使用BufferedOutputStream对象的flush方法,把缓冲区中的数据,刷新到文件中
	bos.flush();
	// 5.释放资源(会先自动调用flush方法刷新数据,所以第4步可以省略)
	bos.close();
}

5.2 缓冲的字节输入流BufferedInputStream

  • 继承关系:java.io.BufferedInputStream extends InputStream
  • 继承自父类的成员方法
    • 方法1.public int read(),从输入流中,读取数据的下一个字节
    • 方法2.public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中
    • 方法3.public void close(), 关闭输入流,并释放相关系统资源

5.2.1 构造方法

  • 构造方法1.BufferedInputStream(InputStream in)
    • 创建一个BufferedInputStream对象,并保存其参数,即输入流in,以便后来使用
  • 构造方法2.BufferedInputStream(InputStream in, int size)
    • 创建一个BufferedInputStream对象,缓冲区的大小是size,同时有个参数是输入流in,以便后来使用

以上两个方法的参数:

  • InputStream in:字节输入流
    • 实际使用的时候,可以传一个FileInputStream对象,缓冲流会给FileInputStream对象增加一个缓冲区,提高FileInputStream对象的读效率
  • int size: 缓冲流内部的缓冲区的大小,不指定的话,就是默认大小

5.2.2 BufferedInputStream的使用

使用步骤

  • 创建FileInputStream对象,构造方法中绑定要读取的数据源
  • 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
  • 使用BufferedInputStream对象的read方法,读取文件中的内容
  • 释放资源
    • 关闭缓冲流就行了,不用手动关闭基本的字节流,因为关闭缓冲流的时候,自动地关闭字节流
5.2.2.1 一次读取多个字节

此时a.txt内容是abcde,打印出来的是abced

public static void main(String[] args) throws IOException{
	// 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
	FileInputStream fis = new FileInputStream("src\\a.txt");
	// 2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
	BufferedInputStream bis = new BufferedInputStream(fis);
	// 3.使用BufferedInputStream对象的read方法,读取文件中的内容
	byte[] arr = new byte[1024]; // 存储每次读取到的数据
	int len = 0; //每次读取的有效字节个数
	while((len = bis.read(arr)) != -1){
		System.out.println(new String(bytes, 0, len));
	}
	// 5. 释放资源(关闭缓冲流就行了,不用手动关闭基本的字节流,因为关闭缓冲流的时候,自动地关闭字节流)
	bis.close();
}
  • 疑问:BufferedInputStream构造方法中的size与new byte[1024]是啥关系

5.3 使用缓冲流复制文件

  • 使用缓冲流复制文件的步骤
    • 创建缓冲的字节输入流对象,构造方法中传递字节输入流
    • 创建缓冲的字节输出流对象,构造方法中传递字节输出流
    • 使用缓冲的字节输入流对象的read方法,读取文件
    • 使用缓冲的字节输出流对象的write方法,把读取的数据写入到内部缓冲区中
    • 释放资源(先关闭输出流,再关闭输入流)
public static void main(String[] args) throws IOException{
	// 1.创建缓冲的字节输入流对象,构造方法中传递字节输入流
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\1.jpg"));
	// 2.创建缓冲的字节输出流对象,构造方法中传递字节输出流
	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d:\\1.jpg"));
	// 3.使用缓冲的字节输入流对象的read方法,读取文件
	byte[] bytes = new byte[1024];
	int len = 0;
	while(len = bis.read(bytes) != -1){
		bos.write(bytes, 0, len);
	}
	bos.close();
	bis.close();
}
  • 使用缓冲流复制文件,会比只用字节流复制文件快很多

5.4 缓冲的字符输出流BufferedWriter

  • BufferedWriter是缓冲的字符输出流,可以提高字符输出流的写入效率
  • 继承关系: java.io.BufferedWriter extends Writer
  • 继承自父类的共性成员方法:
    • void write(int c) 写入单个字符
    • void write(char[] cbuf)写入字符数组
    • abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分,从off开始,写len个字符
    • void write(String str) 写入字符串
    • void write(String str, int off, int len) 写入字符串的一部分,从off开始,写入len个字符
    • void flush() 刷新该流的缓冲
    • void close() 关闭该流,但要先刷新它

5.4.1 BufferedWriter的构造方法

  • 构造方法1. BufferedWriter(Writer out)
    • 功能:根据字符输出流out,创建一个缓冲的字符输出流,缓冲区大小时默认的
    • 参数:具体实现时,Writer out可以传递FileWriter
  • 构造方法2.BufferedWriter(Writer out, int size)
    • 功能:根据字符输出流out,创建一个缓冲的字符输出流,缓冲区的大小是size
    • 参数:具体实现时,Writer out可以传递FileWriter,int size指定缓冲区的大小

5.4.2 BufferedWriter的特殊成员方法

  • void newLine() 写入一个行分隔符
    • 会根据不同的操作系统,获取不同的行分隔符
    • windows换行符是\r\n, linux换行符是/n, mac的换行符是/r

5.4.3 BufferedWriter的使用

  • BufferedWriter的使用步骤
    • 创建缓冲的字符输出流对象,构造方法中传递字符输出流
    • 调用缓冲的字符输出流对象的write()方法,把数据写入到内存缓冲区中
    • 调用缓冲的字符输出流对象的flush()方法,把内存缓冲区中的数据,刷新到文件中
    • 释放资源
public static void main(String[] args) throws IOException{
	// 1.创建缓冲的字符输出流对象,构造方法中传递字符输出流
	BufferedWriter bw = new BufferedWriter(new FileWriter("c:\\aaa.txt"));
	// 2.调用缓冲的字符输出流对象的write()方法,把数据写入到内存缓冲区中
	for(int i = 0; i < 5; i++) {
		bw.write("abc");
		// bw.write("\r\n");  // 换行
		bw.newLine(); // 用新的方式换行
	}
	// 3.调用缓冲的字符输出流对象的flush()方法,把内存缓冲区中的数据,刷新到文件中
	bw.flush();
	// 4.释放资源
	bw.close();
}
  • 此时aaa.txt文件里是
    abc
    abc
    abc
    abc
    abc

5.5 缓冲的字符输入流BufferedReader

  • 继承关系:java.io.BufferedReader extends Reader
  • 继承自父类的共性成员方法:
    • int read() 一次读取一个字符
    • int read(char[] cbuf) 一次读取多个字符,并将字符放入数组
    • void close() 释放资源
5.5.1 BufferedReader的构造方法
  • 构造方法1.BufferedReader(Reader in) 创建一个缓冲的字符输入流,缓冲区的大小是默认的
  • 构造方法2.BufferedReader(Reader in, int size) 创建一个缓冲的字符输入流,缓冲区的大小是size
    • 参数Reader in:是字符输入流,实际使用的时候,可以传一个FileReader对象,缓冲流会给FileReader对象增加一个缓冲区,提高FileReader的读取效率
5.5.2 BufferedReader的特殊成员方法
  • String readline()读取一个文本行
    • 行的终止符:通过下列字符之一,可以认为某行已经终止
      • 换行\n
      • 回车\r
      • 回车后跟着换行\r\n
    • 返回值: 包含改行的内容的字符串,不包含任何终止符,如果已经达到流的末尾,返回null
5.5.3 BufferedReader的使用

使用步骤

  • 创建缓冲的字符输入流对象,构造方法中传递字符输入流对象
  • 使用缓冲的字符输入流对象的read()/readline()方法读取文本
  • 释放资源
public static void main(String[] args) throws IOException{
	// 1.创建缓冲的字符输入流对象,构造方法中传递字符输入流对象
	BufferedReader br = new BufferedReader(new FileReader("c:\\aaa.txt"));
	
	// 2.使用缓冲的字符输入流对象的read()/readline()方法读取文本
	String line;
	while((line = br.readLine()) != null) {
		System.out.println(line);
	}
	
	// 3.释放资源
	br.close();
}
  • c:\aaa.txt里面是
    abc
    abc
    abc
  • 上面的代码,打印出来就是
    abc
    abc
    abc

6. 转换流

6.1 字符编码和字符集

  • 编码和解码
    • 编码:字符(能看懂的) -> 字节(看不懂的)
    • 解码:字节(看不懂的) -> 字符(能看懂的)
  • 字符集:就是编码表,GBK字符集、Unicode字符集都包含ASCII字符集
    • ASCII字符集 — ASCII编码
      • 基本的ASCII字符集有128个字符
      • 扩展的ASCII字符集有256个字符
    • GBK字符集 — GBK编码
      • GB是国标的意思
      • GBK是双字节编码,不管中文、英文、数字,都是两个字节
    • Unicode字符集 — UFT8编码、UTF16编码、UTF32编码
      • unicode字符集也叫万国码
      • UTF8编码的规则
        • 128个ASCII字符,只需要一个字节编码
        • 拉丁文等字符,需要2个字节编码
        • 中文使用3个字节编码

6.2 编码引出的问题

  • IDEA的默认编码格式是UTF8,但是windows的默认编码是GBK
    乱码的例子
    FileReader可以读取IDEA默认编码UTF8的文件
    在windows中创建一个a.txt文件,编码是GBK,然后用FileReader读取,会出现乱码
public static void main(String[] args) throws IOException{
	FileReader fr = new FileReader("D:\\a.txt");
	int len = 0;
	while((len = fr.read()) != -1) {
		System.out.print((char)len); // 打印的是乱码
	}
	fr.close();
 }

6.3 转换流的原理

  • GBK编码,使用2个字节存储一个中文字符,你好-> -55-44,-33-34
  • UFT-8编码,使用3个字节存储一个中文字符,你好->-11-12-23,-19-56-78
    在这里插入图片描述

6.4 转换的字符输出流OutputStreamWriter

  • OutputStreamWriter可以指定charset字符集,按照指定的字符集,将字符编码成字节
  • 继承关系:java.io.OutputStreamWriter extends Writer
  • 继承自父类的共性成员方法:
    • void write(int c) 写入单个字符
    • void write(char[] cbuf)写入字符数组
    • abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分,从off开始,写len个字符
    • void write(String str) 写入字符串
    • void write(String str, int off, int len) 写入字符串的一部分,从off开始,写入len个字符
    • void flush() 刷新该流的缓冲
    • void close() 关闭该流,但要先刷新它

6.4.1 OutputStreamWriter的构造方法

  • 构造方法1OutputStreamWriter(OutputStream out)创建使用默认字符编码的OutputStreamWriter
  • 构造方法2OutputStreamWriter(OutputStream out,String charsetName) 创建使用指定字符集的OutputStreamWriter
    • 参数
      • OutputStream out:字节输出流,缓冲流把字符转成字节后,OutputStream把字节再写入到文件中。实际使用的时候,可以用FileOutputStream
      • String charsetName:指定的编码表,不区分大小写,可以是utf-8、UFT-8、GBK、gbk,不指定默认使用UTF-8

6.4.2 OutputStreamWriter的使用

  • 使用步骤
    • 1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
    • 2.使用OutputStreamWriter对象的write()方法,把字符转为字节(编码),存储在缓冲区中
    • 3.使用OutputStreamWriter对象的flush()方法,把内存缓冲区中的字节刷新到文件中,使用字节流写字节的过程
    • 4.释放资源
public static void main(String[] args) throws IOException{
	// 1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
	OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("a.txt"));	// 不指定字符集,默认用utf-8
	OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("b.txt"), "utf-8");	
	OutputStreamWriter osw3 = new OutputStreamWriter(new FileOutputStream("c.txt"), "gbk");	
	// 2.使用OutputStreamWriter对象的write()方法,把字符转为字节(编码),存储在缓冲区中
	osw1.write("你好");
	osw2.write("你好");
	osw3.write("你好");
	//  3.使用OutputStreamWriter对象的flush()方法,把内存缓冲区中的字节刷新到文件中,使用字节流写字节的过程
	osw1.flush();
	osw2.flush();
	osw3.flush();
	//  4.释放资源
	osw1.close();
	osw2.close();
	osw3.close();	
 }
  • 可以看到a.txt和b.txt有6个字节,要用utf-8的方式打开
  • 可以看到c.txt有4个字节,要用GBK的方式打开

6.5 转换的字符输入流InputStreamReader

  • InputStreamReader可以使用指定的charset字符编码,读取字节并将其解码为字符
  • 继承关系java.io.InputStreamReader extends Reader
  • 继承了父类的共性成员方法
    • int read() 一次读取一个字符
    • int read(char[] cbuf) 一次读取多个字符,并将字符放入数组
    • void close() 释放资源

6.5.1 InputStreamReader的构造方法

  • 构造方法1InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader
  • 构造方法2InputStreamReader(InputStream in, String charsetName)创建一个使用指定字符集的InputStreamReader
    • 参数:
      • InputStream in:字节输入流,用来读取文件中保存的字节,实际使用的时候,可以用FileInputStream
      • String charsetName 指定的编码表名称, 不区分大小写,可以是utf-8、UFT-8、GBK、gbk,不指定默认使用UTF-8

6.5.2 InputStreamReader的使用

  • 使用步骤
    • 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
    • 2.使用InputStreamReader对象的read()方法读取文件
    • 3.释放资源
  • 注意:构造方法中的编码表要和文件的编码表相同,否则会产生乱码
public static void main(String[] args) throws IOException{
	// 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
	// 不指定默认使用UTF-8
	// InputStreamReader isr1 = new InputStreamReader(new FileInputStream("a.txt"));
	InputStreamReader isr2 = new InputStreamReader(new FileInputStream("b.txt"), "UTF-8");
	//  2.使用InputStreamReader对象的read()方法读取文件
	int len = 0;
	while((len = isr2.read()) != -1) {
		System.out.println((char)len); // 打印:你 好
	}
	// 3.释放资源
	isr2.close();
 }
  • 读取UFT-8编码的a.txt和b.txt文件,InputStreamReader()的参数要写utf-8。如果参数写了gbk,则会读出来乱码
  • 读取gbk编码的c.txt文件,InputStreamReader()的参数要写gbk。如果参数写了uft-8,也会读出来乱码

错误例子

public static void main(String[] args) throws IOException{
	InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"), "gbk");
	int len = 0;
	while((len = isr.read()) != -1) {
		System.out.println((char)len); // 打印:乱码,因为b.txt是uft-8编码的
	}
	isr.close();
 }

正确例子

public static void main(String[] args) throws IOException{
	InputStreamReader isr = new InputStreamReader(new FileInputStream("c.txt"), "gbk");
	int len = 0;
	while((len = isr.read()) != -1) {
		System.out.println((char)len); // 打印:你 好, 因为c.txt是gbk编码的
	}
	isr.close();
 }

6.6 转换文件编码

  • 要求:将GBK编码的文件a.txt,转换为UTF-8编码的文件b.txt
  • 步骤
    • 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
    • 2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
    • 3.使用InputStreamReader对象的read()读取文件
    • 4.使用OutputStreamWriter对象的write()方法,把读取的数据写入文件中
    • 5.释放资源
public static void main(String[] args) throws IOException{
	// 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
	InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "GBK");
	// 2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
 	OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"), "UTF-8");
 	// 3.使用InputStreamReader对象的read()读取文件
 	int len = 0;
 	while((len = isr.read()) != -1) {
 		// 4.使用OutputStreamWriter对象的write()方法,把读取的数据写入文件中
 		osw.write(len);
 	}
 	// 5.释放资源
 	osw.close();
 	isr.close();
 }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值