# io
# 1. IO流概述
# 1.1 IO流与File类的区别
- File类: 只能对文件本身进行操作,不能读写文件里面存储的数据。点击阅读:Java —— File 详解
- IO流 : 存储和读取数据的解决方案。用于读写文件中的数据(可以读写文件,或网络中的数据...)
# 1.2 IO流的分类
- 按
流的方向分:输入流(读取)和输出流(写出) - 按操作
文件类型分: 字节流(可操作所有类型文件)和字符流(只操作纯文本文件)
纯文本文件:Windows自带记事本打开能读懂的文件。
# 1.3 IO流体系
注:这里我们要明确一个概念,输入输出都是针对内存而言,输入代表本地(磁盘)输入到内存。输出代表从内存输出到本地。
# 2. 字节流
# 2.1 FileOutputStream
操作本地文件的字节输出流,可以把程序(内存)中的数据写到本地文件中。
# 2.1.1 使用步骤
FileOutputStream 的使用步骤:
- 创建字节输出流对象(创建通道)
FileOutputStream fos = new FileOutputStream("F:\\学习\\java 练习");
参数是字符串路径,也可以是File对象。
若文件不存在则会创建一个新的文件,但需要保证父级路径是存在的,如果文件已经存在,则会清空文件。
- 写数据
fos.write(18);
参数是整数,实际上写到本地文件中的是整数在ASCII上对应的字符
- 释放资源(关闭通道)
fos.close();
每次使用完之后都要释放资源,解除资源的占用。
- Q:为什么要自行关闭流呢?
- A:关闭流是一种资源释放机制,避免资源长时间占用。当我们用了虚拟机以外的资源时,比如端口、显存、文件的时候(访问本地资源),超出虚拟机能够释放资源的极限,这时虚拟机不能通过GC对占用资源进行释放。如果再次对未关闭流的文件进行读写,会报错,告诉你这个文件被占用。
举例:如果你再堆区new一个对象的时候,如果等号左边是对端口、显存、文件进行操作的时候,虚拟机就无法利用垃圾回收机制对堆区占用的资源进行释放。
public void output() throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("F:\学习\java 练习");
}
//FileOutputStream构造方法,抛出FileNotFoundException异常
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
public void output() throws IOException {
//第一步:创建字节输出流对象(创建通道)。
FileOutputStream fos = new FileOutputStream("F:\学习\java 练习");
//第二步:写数据。
fos.write(18);
//第三步:释放资源(关闭通道)。
fos.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.1.2 写数据的三种方式
FileOutputStream 写数据的三种方式:
| 方法名称 | 说明 |
|---|---|
write(int b) | 一次写 一个字节 数据 |
write(byte[] b) | 一次写 一个字节数组 数据 |
write(byte[] b, int off, int len) | 一次写 一个字节数组的部分 数据 |
//byte[] bytes = {97, 98, 99};
String str = "abc";
byte[] bytes = str.getBytes();
fos.write(bytes);
//每次写出从off索引开始,len个字节
fos.write(bytes, 0, 1);
2
3
4
5
6
- 换行写
- 写出一个换行符就行。
Windows: \r\n(回车换行)
Linux: \n
Mac: \r
2
3
4
5
注: 在Windows中,Java对回车进行了优化,写\r和\n其中一个就可以了,Java在底层会补全。(最好不要省略)。
- 续写
FileOutputStream fos = new FileOutputStream("F:\学习\java 练习", true);
# 2.2 FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。
# 2.2.1 使用步骤
FileInputStream 的使用步骤:
//ab
//如果文件不存在直接报错
FileInputStream fis = new FileInputStream("F:\学习\java 练习\a.txt");
//一次读一个字节,读出来的是数据在ASCII上对应的数字。
//读到文件末尾返回-1
int b1 = fis.read();//97 -> a,如果想打印a,强转成char
int b2 = fis.read();//98 -> b
int b3 = fis.read();//-1
fis.close();
2
3
4
5
6
7
8
9
# 2.2.2 循环读取
FileInputStream 进行循环读取:
read()方法读到文件末尾返回-1
FileInputStream fis = new FileInputStream("F:\学习\java 练习\a.txt");
int a;
while (( a = fis.read()) != -1){
System.out.println(a);
}
fis.close();
2
3
4
5
6
# 2.3 字节流应用 —— 文件拷贝
核心思想:边读边写
# 2.3.1 一次读一个字节
//数据源
FileInputStream fis = new FileInputStream("D:\test.mp4");
//目的地
FileOutputStream fos = new FileOutputStream("test_copy.mp4");
//核心思想:边读边写
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 关闭资源,先开的后关闭
fos.close();
fis.close();
2
3
4
5
6
7
8
9
10
11
12
# 2.3.2 一次读多个字节
// 数据源路径
FileInputStream fis = new FileInputStream("D:\test.jpg");
// 目的地路径
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 定义数组
byte[] b = new byte[1024];
// 定义长度
int len;
// 进行循环读取,读取到的数据写入数组b中
while ((len = fis.read(b))!=-1) {
// 写出数据
fos.write(b, 0 , len);//改进,为了最后一次
}
// 关闭资源
fos.close();
fis.close();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为什么第15行要使用write(byte[] b, int off, int len)这个方法,而不使用fos.write(b)方法呢?
- 让我们通过下面这张图看一下究竟是为什么:
- 为了
防止写入之前残留的数据,因此使用了write(byte[] b, int off, int len)。
- 为了
# 3. 字符流
字符流: 字符流的底层就是字节流。
字符流 = 字节流 + 字符集
- 字符流特点:
-输入流: 一次读一个字节,遇到中文时,一次读多个字节(与字符集有关)。
- 输出流: 底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。
- 使用场景: 对于纯文本文件进行读写操作。
# 3.1 FileReader
# 3.1.1 构造方法
创建字符输入流 FileReader 对象
| 构造方法 | 说明 |
|---|---|
FileReader(File file) | 创建字符输入流关联本地文件 |
FileReader(String fileName) | 创建字符输入流关联本地文件 |
若读取文件不存在,直接报错。
# 3.1.2 成员方法
- FileReader 读取数据方法
| 成员方法 | 说明 |
|---|---|
public int read() | 读取数据,读取到末尾,返回-1 |
public int read(char[] cbuf) | 读取多个数据,读取到末尾,返回-1 |
注:按字节进行读取,遇到中文,一次读取多个字节。
- FileReader 释放资源方法
| 成员方法 | 说明 |
|---|---|
Public int close() | 释放资源/关流 |
# 3.1.3 使用步骤
FileReader 的使用步骤:
// 创建字符输入流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存数据
int b ;
// 进行循环读取
// 在读取之后,方法的底层会进行解码并转成十进制,将十进制数据作为返回值。
// 如果想看到汉字,进行强转就可以了。
while ((b = fr.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fr.close();
// 创建字符输入流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 进行循环读取
// 读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中。
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 3.1.4 字符输入流原理
创建字符输入流对象时,关联文件,并创建缓冲区(长度为8192的字节数组)。
- 读取数据时:
- 判断缓冲区是否有数据可以读取。
- 缓冲区有数据:从缓冲区中读取。
- 若缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件中没有数据了,就返回 -1。
# 3.2 FileWriter
# 3.2.1 构造方法
FileWriter 的构造方法:
| 构造方法 | 说明 |
|---|---|
FileWriter(File file) | 创建字符输出流关联本地文件 |
FileWriter(String fileName) | 创建字符输出流关联本地文件 |
FileWriter(File file, boolean append) | 创建字符输出流关联本地文件,续写 |
FileWriter(String fileName, boolean append) | 创建字符输出流关联本地文件,续写 |
# 3.2.2 成员方法
FileWriter 的成员方法:
| 成员方法 | 说明 |
|---|---|
void write(int c) | 写出一个字符 |
void write(char[] cbuf) | 写出一个字符串 |
abstract void write(char[] cbuf, int off, int len) | 写出一个字符串的一部分 |
void write(String str) | 写出一个字符数组 |
void write(String str, int off, int len) | 写出字符数组的一部分 |
# 3.2.3 使用步骤
FileWriter 的使用步骤:
// 创建字符输出流对象
FileWriter fw = new FileWriter("a.txt");
// 写出数据
fw.write(25105);// 我
fw.write("你好");// 你好
fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
fw.close();
2
3
4
5
6
7
8
9
10
11
12
3 注:关闭资源时,与 FileOutputStream 不同。如果不关闭,数据只是保存到缓冲区,并未保存到文件。
3.2.4 字符输出流原理 创建字符输出流对象时,关联文件,并创建缓冲区(长度为8192的字节数组)。
写数据先写到缓冲区。
从缓冲区写到文件中的几种情况:
缓冲区装满了,直接写出到文件。 手动刷新。flush()方法,刷新之后,还可以继续往文件里写出数据。 释放资源,关流。close()方法。断开通道,无法继续写出。 4. 缓冲流 缓冲流提高了性能:缓冲区自带长度8192的缓冲区。
由于字符流自带缓冲区,所以对于字符流提升不明显。
对于字符流而言关键点是:字符缓冲流中有两个特有的方法,这是字符流不具有的。
4.1 字节缓冲流 原理:底层自带了长度为8192的缓冲区提高性能。
字节缓冲流提高效率的原理:节省与硬盘打交道的时间,内存操作比磁盘操作快很多。
构造方法:
方法名称|说明 public BufferedInputStream(InputStream in)|把基本流包装成高级流,提高读取数据的性能。 public BufferedOutputStream(OutputStream out)|把基本流包装成高级流,提高写出数据的性能。 使用步骤:以拷贝文件为例
一次一个字节:
public static void main(String[] args) throws IOException { // 创建缓冲流对象 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\a.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.txt")); int b; while ((b = bis.read()) != -1){ bos.write(b); } // 释放资源,直接调用缓冲流close(),会先关闭基本流。 bos.close(); bis.close(); } 一次多个字节:
// 创建缓冲流对象 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\a.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.txt")); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } // 释放资源,直接调用缓冲流close(),会先关闭基本流。 bos.close(); bis.close(); 4.2 字符缓冲流 原理:底层自带缓冲区提高性能
构造方法:
方法名称|说明 public BufferedReader(Reader in)|把基本流变成高级流 public BufferedWriter(Writer out)|把基本流变成高级流 字符缓冲流的特有方法(字符流不具有):
方法名称|说明 public String readLine()|输入流方法,读取一行数据,如果没有数据可读了,会返回null public void newLine()|输出流方法,跨平台换行 5. 转换流 转换流:字符流和字节流之间的桥梁。
使用场景:字节流想使用字符流中的方法。
- 序列化流
6.1 序列化流 序列化流/对象操作输出流:可以把Java中的对象写到本地文件中。
构造方法:public ObjectOutputStream(OutputStream out),把基本流包装成高级流。
成员方法:public final void writeObject (Object obj),把对象序列化(写出)到文件中去。
public static void test() throws IOException {
Student student = new Student("zhang", 18);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
oos.writeObject(student);
oos.close();
} 使用对象输出流将对象保存到文件时可能会出现java.io.NotSerializableException异常,需要实现序列化接口:
//让对象实现接口,使其可以序列化 public class Student implements Serializable { private static final long serialVersionUID = 1L;//版本号 private String name; private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
} 瞬态关键字 transient:private transient String name;,不会将当前属性序列化到本地文件中
6.2 反序列化流 反序列化流/对象操作输入流:可以把序列化到本地文件中的对象,读取到程序中来。
构造方法:public ObjectInputStream(InputStream in),把基本流包装成高级流
成员方法:public final Object readObject (),将之前使用 ObjectOutputStream 序列化的原始数据恢复为对象。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
Object o = ois.readObject();
System.out.println(o);//Student{name='zhang', age=18}
ois.close(); 6.3 序列化流可能发生的问题 序列化/反序列化版本号问题:对象类实现序列化接口,这个对象可以被序列化,根据这个类中的内容计算出一个序列号(可以理解为版本号),创建对象时会将版本号一同写入本地文件中,若此时修改类中代码,会重新计算版本号。
这时,反序列化将对象读取到内存中,两个版本号不同,代码报错。文件中版本号和javabean的版本号不匹配。
解决方法:在类中添加版本号 serialVersionUID,这样编译器就不会自动为其设置版本号,而是在编译时直接写入指定的序列化版本号。
- 打印流 打印流:不能输入,只能写出。
打印流的几个特点:
打印流只能操作文件目的地,不能操作数据源。
特有的写出方法可以实现数据原样写出。(98 -> 98)
特有的写出方法,可以实现自动刷新,自动换行。
打印一次数据 = 写出 + 换行 + 刷新
7.1 字节打印流 7.1.1 构造方法 构造方法|说明 public PrintStream(OutputStream/File/String)|关联字节输出流/文件/文件路径(底层都实现字节流) public PrintStream(String fileName, CharSet charSet)|指定字符编码 public PrintStream(OutputStream out, boolean autoFlush)|自动刷新(字节流底层无缓存区,开不开都一样) public PrintStream(OutputStream out, boolean autoFlush, String encoding)|指定字符编码且自动刷新 7.1.2 成员方法 成员方法|说明 public void write(int b)|常规方法:规则跟之前一样,将指定的字节读出。(97 -> a) public void println(XXX xx)|特有方法:打印任意数据,自动刷新,自动换行。(原样输出) public void print(XXX xx)|特有方法:打印任意数据,不换行。(原样输出) public void print(String format, Object...args)|特有方法:带有占位符的打印语句,不换行。(原样输出) 7.2 字符打印流 字符流,底层有缓冲区,想要自动刷新需要开启。
7.2.1 构造方法 构造方法|说明 public PrintWriter(Writer/File/String)|关联字节输出流/文件/文件路径 public PrintWriter(String fileName, CharSet charSet)|指定字符编码 public PrintWriter(Writer out, boolean autoFlush)|自动刷新 public PrintWriter(Writer out, boolean autoFlush, String encoding)|指定字符编码且自动刷新 7.2.2 成员方法 成员方法|说明 public void write(int b)|常规方法:规则跟之前一样,将指定的字节读出。(97 -> a) public void println(XXX xx)|特有方法:打印任意数据,自动刷新,自动换行。(原样输出) public void print(XXX xx)|特有方法:打印任意数据,不换行。(原样输出) public void print(String format, Object...args)|特有方法:带有占位符的打印语句,不换行。(原样输出) 获取打印流对象,此打印流在虚拟机启动时,由虚拟机创建,默认指向控制台。
特殊的打印流,系统中的标准输出流,是不能关闭的,在系统中是唯一的:
PrintStream ps = System.out; ps.println("123"); 8. 压缩流/解压缩流
压缩包里面每一个文件都是一个ZipEntry。
8.1 解压缩流 解压本质:把每一个ZipEntry按照层级拷贝到本地另一个文件夹中。
直接上代码:
获取解压的源路径和目标路径:
public static void main(String[] args) throws IOException { //待解压的压缩包路径 File src = new File("C:\Users\Administrator\Desktop\a.zip"); //解压目的地 File dest = new File("C:\Users\Administrator\Desktop"); unzip(src, dest);
}
传入地址,进行解压:
public static void unzip(File src, File dest) throws IOException { //创建一个解压缩流。 ZipInputStream zis = new ZipInputStream(new FileInputStream(src)); //获取压缩包中每一个zipEntry对象 ZipEntry entry; while((entry = zis.getNextEntry()) != null){ //打印测试 System.out.println(entry); if(entry.isDirectory()){ //文件夹,在目的地dest处创建一个同样的文件夹 File file = new File(dest, entry.toString()); file.mkdirs(); }else { //文件,读取压缩包中的文件,并把它存到目的地dest文件夹中 FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString())); int b; while ((b = zis.read()) != -1){ fos.write(b); } fos.close(); //表示在压缩包中的一个文件处理完了 zis.closeEntry(); } } zis.close(); } 8.2 压缩流 将文件D:\a.txt压缩成一个压缩包: public static void main(String[] args) throws IOException { //需压缩文件的路径 File src = new File("D:\a.txt"); //压缩包的路径 File dest = new File("D:\ "); //压缩 toZip(src,dest); }
public static void toZip(File src, File dest) throws IOException {
// 创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
// 创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
// 压缩包里面的路径
ZipEntry entry = new ZipEntry("aaa\bbb\a.txt");
// 把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
// 把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
zos.closeEntry();
zos.close();
}
将 D:\aaa文件夹 压缩成一个压缩包:
public static void main(String[] args) throws IOException {
// 要压缩的文件夹路径
File src = new File("D:\aaa");
// 压缩包的父级路径
File destParent = src.getParentFile();//D:
// 压缩包的路径
File dest = new File(destParent, src.getName() + ".zip");
// 创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
// 获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src, zos, src.getName());//aaa
// 释放资源
zos.close();
}
public static void toZip(File src, ZipOutputStream zos, String name) throws IOException { // 进入src文件夹 File[] files = src.listFiles(); // 遍历 for (File file : files) { if(file.isFile()){ // 文件,变成ZipEntry对象,放入到压缩包当中 ZipEntry entry = new ZipEntry(name + "\ " + file.getName());//aaa\a\a.txt zos.putNextEntry(entry); //读取文件中的数据,写到压缩包 FileInputStream fis = new FileInputStream(file); int b; while((b = fis.read()) != -1){ zos.write(b); } fis.close(); zos.closeEntry(); }else{ // 文件夹,递归 toZip(file, zos, name + "\ " + file.getName()); // a aaa \ a } } }