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();
}
注意:
- 任何文本编辑器(txt、notepad++、word)在打开文件的时候,都会查询编码表,把字节按照编码表转成字符表示,方便我们阅读。
- 规则:
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
注意
- (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)
将指定的字节写入此输出流
- 方法1.public void close()
5.1.1 构造方法
-
构造方法1. BufferedOutputStream(OutputStream out)
- 功能:创建缓冲的字节输出流,以将数据写入指定的底层输出流
-
构造方法2.BufferedOutputStream(OutputStream out, int size)
- 创建缓冲的字节输出流,以将size大小的缓冲区数据,写入指定的底层输出流
-
以上两个构造方法的参数:
- OutputStream out:字节输出流。
- 实际使用时,可以传递FileOutputStream对象,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
- int size:用于指定缓冲流内部的缓冲区大小,不指定的话,就是默认的大小。
- OutputStream out:字节输出流。
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个字节编码
- ASCII字符集 — ASCII编码
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();
}