目录
IO流
IO流的分类
IO流的体系
字节流:
1、Filelnputstream(文件字节输入流)
2、FileOutputStream(文件字节输出流)
字节流非常适合做一切文件的复制操作
复制案例:
try-catch-finally 和 try-with-resource
字符流
1、FileReader(文件字符输入流)
2、FileWriter(文件字符输出流)
字节流、字符流的使用场景小结:
缓冲流
BufferedReader(字符缓冲输入流)
BufferedWriter(字符缓冲输出流)
不同编码读取出现乱码的问题
转换流
InputStreamReader(字符输入转换流)
OutputStreamWriter字符输出转换流
打印流
PrintStream/PrintWriter(打印流)
Printstream提供的打印数据的方案
PrintWriter提供的打印数据的方案
PrintStream和PrintWriter的区别
打印流的一种应用:输出语句的重定向
数据流
DataOutputstream(数据输出流)
Datalnputstream(数据输入流)
序列化流
Objectoutputstream(对象字节输出流)
Objectlnputstream(对象字节输入流)
IO流
- 读写数据的输入输出流
- I指Input,称为输入流:负责把数据读到内存中去
- O指Output,称为输出流:负责写数据出去
IO流的分类
IO流总体看来分为四大流
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流
IO流的体系
字节流:
1、Filelnputstream(文件字节输入流)
- 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
代码演示:
abc.txt内容
一个一个字节读 和 使用循环一个一个字节读:
import java.io.*;
public class IoTest1 {
public static void main(String[] args) throws IOException {
//1、创建文件字节输入流管道,与源文件接通
//InputStream fileInputStream = new FileInputStream(new File("file_io\\src\\com\\zeyu\\abc.txt")); new File可省略
InputStream fileInputStream = new FileInputStream("file_io\\src\\com\\zeyu\\abc.txt");
//2、读取文件字节数据
//public int read() 每次读取一个字节返回,如果没有数据了,返回-1
// int b1 = fileInputStream.read();
// System.out.println((char)b1);
//
// int b2 = fileInputStream.read();
// System.out.println((char)b2);
//使用循环读
int b;
while((b = fileInputStream.read()) != -1){
System.out.print((char)b);
}
//读取数据的性能很差!
//读取汉字输出会乱码!!无法避免的!!
//流使用完毕之后,必须关闭!释放系统资源!
fileInputStream.close();
}
}
运行结果:
为什么会乱码?
因为中文字符在utf-8里面占三个字节,而每次读取一个字节输出显然是解码不出中文的,只会读取中文字符三个字节中的一个字节
用字节数组读 和 结合循环用字节数组读:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class IoTest2 {
public static void main(String[] args) throws IOException {
InputStream fileInputStream = new FileInputStream("file_io\\src\\com\\zeyu\\abc.txt");
//读取文件中的字节数据,每次读取多个字节
// public int read(byte b[])throws IOException
//每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1
// byte[] buffer = new byte[3];
// int len = fileInputStream.read(buffer); //返回读取了多少个字节
// System.out.println(new String(buffer));
// System.out.println(len);
//
// int len2 = fileInputStream.read(buffer);
// //注意:读取多少,倒出多少
// System.out.println(new String(buffer,0,len2)); //倒出0到len2的字节
// System.out.println(len2);
//
// int len3 = fileInputStream.read(buffer);
// System.out.println(len3); //-1
//使用循环改造
int len;
byte[] buffer = new byte[3];
while((len = fileInputStream.read(buffer)) != -1){
System.out.print(new String(buffer, 0, len));
} //性能得到了明显的提升
//这种方案也不能避免中文乱码问题
fileInputStream.close();
}
}
运行结果:
即使每次读取三个字节,依旧无法避免乱码的可能,因为若读取的字符串排列为前面俩个英文接一个中文,就会读取到前面俩个英文的(英文数字在utf-8中占一个字节)字节和中文的其中一个字节
一次性读取全部字节:
package com.zeyu.Io;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class IoTest3 {
public static void main(String[] args) throws IOException {
//一次性读取完文件的全部字节
InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\abc2.txt");
//准备一个字节数组,大小与文件的大小一样大
// File f = new File("file_io\\src\\com\\zeyu\\abc2.txt");
// long size = f.length();
// byte[] buffer = new byte[(int)size];
//
// int len = is.read(buffer);
//
// System.out.println(new String(buffer));
// System.out.println(len);
// System.out.println(size);
byte[] buffer = is.readAllBytes(); //readAllBytes() 直接把文件的全部字节读取到一个字节数组中返回
System.out.println(new String(buffer));
}
}
运行结果:
一次读取完文件的所有字节,当然就不会出现乱码问题了,但是文件过大依旧可能出现内存溢出问题
2、FileOutputStream(文件字节输出流)
- 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
代码演示:
import java.io.FileOutputStream;
import java.io.IOException;
public class IoTest4 {
public static void main(String[] args) throws IOException {
//创建一个字节输出流管道与目标文件接通
FileOutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");
//写字节出去
os.write(97); //97 为英文字母a的ASCII码
os.write('b');
//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节
byte[] bytes = "重返蓝鲸岛abc".getBytes();
os.write(bytes); //直接写入字节数组就没有问题
os.write("\r\n".getBytes()); //\r\n 换行
os.write(bytes,0,15); //写入指定长度
os.close();
FileOutputStream os2 = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt",true); //后面写true,追加数据,不覆盖
os2.write(bytes);
os2.close();
}
}
abc3.txt内容:
字节流非常适合做一切文件的复制操作
- 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
复制案例:
将c盘的一张图片复制到d盘
package com.zeyu.exercise;
import java.io.*;
public class copyFile {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("C:\\Users\\dzy\\Pictures\\Camera Roll\\六花.jpg"); //原位置
OutputStream os = new FileOutputStream("D:\\File\\六花\\六花.jpg"); //目标位置
byte[] buffer = new byte[1024]; //每次1kb
int len; //记录读出多少
while((len = is.read(buffer)) != -1){
os.write(buffer,0,len); //取多少倒多少
}
is.close();
os.close();
}
}
try-catch-finally 和 try-with-resource
如果在使用完IO流之后不关闭(close),可能会导致资源泄漏和引起文件锁定等问题。所以要在使用完后关闭流
假如在执行close语句之前有语句出现异常,那么自然也不会执行close语句,也就不会关闭流了
例如:
运行结果:
可以看到System.out.println(10/0) 这句语句显然是有问题的,而代码执行到这句语句时就直接终止了,后面的语句都没有执行,也就没有关闭流。鉴于这种情况,我们可以用try-catch-finally 或 try-with-resource来解决:
try-catch-finally:
package com.zeyu.Io;
import java.io.FileOutputStream;
import java.io.IOException;
public class try_catch_finally {
public static void main(String[] args) {
FileOutputStream os = null;
try {
//创建一个字节输出流管道与目标文件接通
os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");
//写字节出去
os.write(97);
os.write('b');
//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节
byte[] bytes = "重返蓝鲸岛abc".getBytes();
os.write(bytes); //直接写入字节数组就没有问题
os.write("\r\n".getBytes()); //\r\n 换行
os.write(bytes,0,15); //写入指定长度
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源的操作
try {
if(os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在try-catch-finally中无论代码是否报错,它都会在结束之前执行finally里面的语句,除非在其它地方使用System.exit()关闭了jvm,所以使用try-catch-finally方式是肯定会释放资源执行close的
注意:finally中不要写return语句,不然它先执行finally的return从而忽略真正的return语句
try-with-resource:
package com.zeyu.Io;
import java.io.FileOutputStream;
import java.io.IOException;
public class try_with_resource {
public static void main(String[] args) {
try( //自动关闭在此创建的资源对象,调用其close方法
//创建一个字节输出流管道与目标文件接通
FileOutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\abc3.txt");
//注意:这里只能放资源对象
//资源对象就是会实现AutoCloseable接口的对象
) {
//写字节出去
os.write(97);
os.write('b');
//os.write('泽'); 会乱码,中文在utf-8中占3个字节,默认只能写出去一个字节
byte[] bytes = "重返蓝鲸岛abc".getBytes();
os.write(bytes); //直接写入字节数组就没有问题
os.write("\r\n".getBytes()); //\r\n 换行
os.write(bytes,0,15); //写入指定长度
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
显然,这是一种比try-catch-finally更方便的方法,只要在try后面加个括号,里面写上需要释放的资源的定义语句,那么它就会自动释放其中的资源
字符流
1、FileReader(文件字符输入流)
- 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去
代码演示:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
public class IO_charTest1 {
public static void main(String[] args) {
try ( // 1、创建一个文件字符输入流管道与源文件接通
Reader r = new FileReader("file_io\\src\\com\\zeyu\\z.txt");
){
//读取文本文件内容
//每次读取一个字符(性能较差,每次读取都要进行系统调用)
// int c;
// while((c = r.read()) != -1){
// System.out.print((char)c);
// }
//每次读取多个字符
//性能不错(每次读取多个,进行系统调用的次数就少了)
int len;
char[] buffer = new char[3];
while((len = r.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
2、FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去
字特输出流使用时的注意事项
- 字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
代码演示:
import java.io.FileWriter;
import java.io.Writer;
public class IO_charTest2 {
public static void main(String[] args) {
try ( // 0、创建一个文件字符输出流管道与目标文件接通。
Writer w = new FileWriter("file_io\\src\\com\\zeyu\\z2.txt"/*,true*/); //后面加true写入由覆盖变为追加
){
// 1、public void write(int c):写一个字符出去
w.write('l');
// 2、public void write(string c)写一个字符串出去
w.write("ove");
w.write("\r\n"); //换行
// 3、public void write(string c,int pos ,int len):写字符串的一部分出去
w.write("hate you",5,3);
w.write("\r\n");
// 4、public void write(char[] buffer):写一个字符数组出去
char[] buffer = {'当','花','叶','呢','喃','之','时',',','请','呼','唤','我','的','名','字'};
w.write(buffer);
w.write("\r\n");
//5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
w.write(buffer,8,7);
w.flush();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
字节流、字符流的使用场景小结:
- 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)
缓冲流
- 对原始流进行包装,以提高原始流读写数据的性能的流
- 提高字节流读写数据的性能
- 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池
构造器:
将字节流包装成字节缓冲流,再使用字节缓冲流做操作效率会更快
代码演示:
package com.zeyu.IO_byte_buffer;
import java.io.*;
public class IO_byte_bufferTest1 {
public static void main(String[] args) {
try (
OutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\b.txt");
OutputStream bos = new BufferedOutputStream(os/*,8192*2*/); //字节缓冲输出流,可以在后面自定义缓冲池大小
InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\b.txt");
InputStream bis = new BufferedInputStream(is/*,8192*/); //字节缓冲输入流,可以在后面自定义缓冲池大小
){
bos.write("由此开启那飘零于时间里的故事".getBytes());
bos.flush();
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
System.out.println(new String(buffer,0,len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
BufferedReader(字符缓冲输入流)
- 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能
- 字符缓冲输入流新增的功能:按照行读取字符
代码演示:
package com.zeyu.IO_char;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
public class IO_charTest1 {
public static void main(String[] args) {
try ( // 1、创建一个文件字符输入流管道与源文件接通
Reader r = new FileReader("file_io\\src\\com\\zeyu\\z.txt");
){
//读取文本文件内容
//每次读取一个字符(性能较差,每次读取都要进行系统调用)
// int c;
// while((c = r.read()) != -1){
// System.out.print((char)c);
// }
//每次读取多个字符
//性能不错(每次读取多个,进行系统调用的次数就少了)
int len;
char[] buffer = new char[3];
while((len = r.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
BufferedWriter(字符缓冲输出流)
- 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能
- 字符缓冲输出流新增的功能:换行
代码演示:
package com.zeyu.IO_char;
import java.io.FileWriter;
import java.io.Writer;
public class IO_charTest2 {
public static void main(String[] args) {
try ( // 0、创建一个文件字符输出流管道与目标文件接通。
Writer w = new FileWriter("file_io\\src\\com\\zeyu\\z2.txt"/*,true*/); //后面加true写入由覆盖变为追加
){
// 1、public void write(int c):写一个字符出去
w.write('l');
// 2、public void write(string c)写一个字符串出去
w.write("ove");
w.write("\r\n"); //换行
// 3、public void write(string c,int pos ,int len):写字符串的一部分出去
w.write("hate you",5,3);
w.write("\r\n");
// 4、public void write(char[] buffer):写一个字符数组出去
char[] buffer = {'当','花','叶','呢','喃','之','时',',','请','呼','唤','我','的','名','字'};
w.write(buffer);
w.write("\r\n");
//5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
w.write(buffer,8,7);
w.flush();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
不同编码读取出现乱码的问题
- 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
- 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
转换流
InputStreamReader(字符输入转换流)
- 解决不同编码时,字符流读取文本内容乱码的问题
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
代码演示:
c.txt为GBK编码
import java.io.*;
public class IO_transFromTest1 {
public static void main(String[] args) {
try (
//1、得到文件的原始字节流(GBK的字节流形式)
InputStream is = new FileInputStream("file_io\\src\\com\\zeyu\\c.txt");
//2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
Reader isr = new InputStreamReader(is,"GBK");
//把字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
OutputStreamWriter字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
代码演示:
package com.zeyu.transfrom;
import java.io.*;
public class IO_transFromTest2 {
public static void main(String[] args) {
try (
//1、得到文件的原始字节流(GBK的字节流形式)
OutputStream os = new FileOutputStream("file_io\\src\\com\\zeyu\\c2.txt");
//2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
Writer osr = new OutputStreamWriter(os,"GBK");
//把字符输入流包装成缓冲字符输入流
BufferedWriter bw = new BufferedWriter(osr);
){
osr.write("I’m a little used to calling outside your name\n" +
"我有些习惯在外面大喊你的名字");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
c2.txt为GBK编码
也可调用String提供的getBytes方法控制写出去的字符使用什么字符集编码
打印流
PrintStream/PrintWriter(打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去
Printstream提供的打印数据的方案
PrintWriter提供的打印数据的方案
演示代码:
package com.zeyu.print;
import java.io.PrintStream;
public class IO_printTest1 {
public static void main(String[] args) {
try (
PrintStream ps = new PrintStream("file_io\\src\\com\\zeyu\\print.txt"/*, Charset.forName("GBK")*/); //指定字符集编码写入
// PrintWriter ps = new PrintWriter("file_io\\src\\com\\zeyu\\print.txt"/*, Charset.forName("GBK")*/);
//打印与PrintStream基本一致,PrintStream支持写字节数据,PrintWriter支持写字符数据
// PrintWriter ps = new PrintWriter(new FileWriter("file_io\\src\\com\\zeyu\\print.txt",true)); //追加写法
){
ps.println("如果下摆湿掉了的话");
ps.println("等待干就行了");
ps.println("水滴飞溅 发出声音");
ps.println("是你教会了我 不再去害怕");
ps.println("紧握住你的手 不放开的话");
ps.println(97);
ps.println(false);
ps.println(3.14);
ps.write(97); //'a'
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去
打印流的一种应用:输出语句的重定向
- 可以把输出语句的打印位置改到某个文件中去
代码演示:
package com.zeyu.print;
import java.io.PrintStream;
public class IO_printTest2 {
public static void main(String[] args) {
System.out.println("如果下摆湿掉了的话");
System.out.println("等待干就行了");
try (
PrintStream ps = new PrintStream("file_io\\src\\com\\zeyu\\print2.txt");
){
System.setOut(ps); //把系统默认的打印流对象改成自己的打印流对象
System.out.println("水滴飞溅 发出声音");
System.out.println("是你教会了我 不再去害怕");
System.out.println("紧握住你的手 不放开的话");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
数据流
DataOutputstream(数据输出流)
- 允许把数据和其类型一并写出去
代码演示:
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class IO_DataOutputTest1 {
public static void main(String[] args) {
try ( //创建一个数据输出流包装低级的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("file_io\\src\\com\\zeyu\\data.txt"));
){
dos.writeBoolean(true);
dos.writeInt(777);
dos.writeDouble(3.14);
dos.writeUTF("缠住吻住春风吹住我吗");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
Datalnputstream(数据输入流)
- 用于读取数据输出流写出去的数据
代码演示:
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class IO_DataInputTest2 {
public static void main(String[] args) {
try ( //创建一个数据输入流包装低级的字节输入流
DataInputStream dis = new DataInputStream(new FileInputStream("file_io\\src\\com\\zeyu\\data.txt"));
){
//读取的类型顺序要和写入的类型顺序一一对应,不然会出bug
System.out.println(dis.readBoolean());
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
序列化流
- 对象序列化:把Java对象写入到文件中去
- 对象反序列化:把文件里的Java对象读出来
Objectoutputstream(对象字节输出流)
- 可以把Java对象进行序列化:把Java对象存入到文件中去
注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)
代码演示:
package com.zeyu.object;
import java.io.*;
public class IO_objectOutputTest1 {
public static void main(String[] args) {
try ( //2、创建一个对象字节输出流包装原始的字节输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file_io\\src\\com\\zeyu\\Object.txt"));
){
//1、创建一个java对象
User u1 = new User("admin","小白","123456",22);
//3、序列化对象到文件中
oos.writeObject(u1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
Objectlnputstream(对象字节输入流)
- 可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来
代码演示:
package com.zeyu.object;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class IO_ObjectInputTest2 {
public static void main(String[] args) {
try (
//创建一个对象字节输入流管道,包装低级的字节输入流与源文件接通
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file_io\\src\\com\\zeyu\\Object.txt"));
){
User u = (User) ois.readObject();
System.out.println(u);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
如果想让对象某个成员变量不参与序列化,可以使用transient
如果想要一次性序列化多个对象,可以用集合包装这些对象,然后直接序列化集合对象,ArrayList集合已经实现了序列化接口!