java IO流
代码
文章中用到的代码都在这
https://gitee.com/lai_di_hao/java-io-exercise.git
流
概念:内存与存储设备之间的传输数据的通道。
流的分类
按方向分
- 输入流:将<存储设备>中的内容写入到<内存>中。
- 输出流:将<内存>中的内容写入到<存储设备>中。
按单位分
- 字节流:以字节为单位,可以读写所有数据。
- 字符流:以字符为单位,只能读写文本数据。
按功能分
- 节点流:具有实际传输数据的读写功能。
- 过滤流:在节点流的基础之上增强功能。
字节流
字节流的父类(抽象类)
- InputStream 字节输入流
public int read () {}
public int read (byte[] b) {}
public int read(byte[] b, int off, int len) {} - OutputStream 字节输出流
public void write (int n) {}
public void write (byte[] b) {}
public void write (byte[] b, int off, int len) {}
文件字节流
- FileInputStream :
public int read (byte[] b) // 从流中读取多个字节,将读到内容存入b数组,返回实际读到的字节数;如果达到文件的尾部,则返回-1
在文件读取时,如果不指定输出的类型,则会输出内容对应的字符编码:
要输出字符的应该强制转换输出类型:
还有多个字节的读取方法:
代码:
package com.test5;
import java.io.FileInputStream;
/**
* 文件字节输入流
*/
public class FileInputStreamTest {
public static void main(String[] args) throws Exception {
//创建FileInputStream,并指定文件路径
FileInputStream fis = new FileInputStream("C:\\Users\\LDH\\Desktop\\test.txt");
//读取文件(单个字节读取)
// int data = 0;
// while ((data = fis.read()) != -1){
// System.out.println((char) data);
// }
//读取文件(多个字节)
byte[] buf = new byte[6];
int count = 0;
while ((count =fis.read(buf)) != -1){
System.out.println(new String(buf,0,count));
}
//关闭
fis.close();
System.out.println("关闭");
}
}
- FileOutputStream :
public void write (byte[] b) // 一次写多个字节,将b数组中的所有字节,写入输出流。
单字节写入文件:
多字节写入文件:
如果FileOutputStream对象中的append方法为true,则写入的内容不覆盖,而是接在内容尾部:
代码:
package com.test5;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
/**
* 文件字节输出流
*/
public class FileOutputStreamTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\Users\\LDH\\Desktop\\test.txt",true);
//写入文件
// fos.write(97); //这里写入的是字符编码,'a'
// fos.write('b');
// fos.write('c');
String s = "hhhhhhh";
fos.write(s.getBytes(StandardCharsets.UTF_8));
//关闭
fos.close();
System.out.println("关闭");
}
}
文件复制案例:
package com.test5;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 文件复制
*/
public class FileCopy {
public static void main(String[] args) throws Exception {
//文件字节输入流,被复制的文件
FileInputStream fis = new FileInputStream("C:\\Users\\LDH\\Desktop\\test.txt");
//文件字节输出流,复制后的文件
FileOutputStream fos = new FileOutputStream("C:\\Users\\LDH\\Desktop\\test2.txt");
//一边读,一边写
byte[] buf = new byte[1024];
int count = 0;
while ((count = fis.read(buf)) != -1){
fos.write(buf,0,count);
}
//关闭
fis.close();
fos.close();
}
}
字节缓冲流
- BufferedInputStream / BufferedOutputStream
- 提高IO效率,减少访问磁盘的次数
- 数据存储在缓冲区中,flush是将缓冲区的内容写入文件中,也可以直接close。
BufferedInputStream 用法:
package com.test5;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
/**
* 字节缓冲流
*/
public class BufferedInputStreamTest {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:\\Users\\LDH\\Desktop\\test.txt");
BufferedInputStream bis = new BufferedInputStream(fis); //内部有8k的缓冲区
//读取
int data = 0;
while ((data = bis.read()) != -1){
System.out.println((char) data);
}
//自定义缓冲区读取
byte[] buf = new byte[1024];
int count = 0;
while ((count =fis.read(buf)) != -1){
System.out.println(new String(buf,0,count));
}
bis.close();
}
}
结果:
BufferedOutputStream 使用:
package com.test5;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
/**
* 字节输出流写入文件
*/
public class BufferedOutputStreamTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\Users\\LDH\\Desktop\\test.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
//写入文件
for (int i = 0; i < 10; i++) {
bos.write(("hellow"+i+"\r\n").getBytes(StandardCharsets.UTF_8)); //写入缓冲区,内置有8k缓冲区
bos.flush(); //刷新文件
}
//关闭,内部只用flush方法
bos.close();
}
}
结果:
注意:
写入缓冲区之后一定要刷新,不然内容不会显示。
bos.flush(); //刷新文件
对象流
- ObjectOutputStream / ObjectInputStream
- 增强了缓冲区功能
- 增强了读写8种基本数据类型和字符串功能
- 增强了读写对象功能:
readObject() 从流中读取一个对象 ----反序列化
writeObject(Object obj) 向流中写入一个对象 ----序列化
ObjectOutputStream 使用
package com.test5;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
* 实现对象的序列化
*/
public class ObjectOutputStreamTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\Users\\LDH\\Desktop\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//序列化(写入操作)
Students s1 = new Students("张三",20);
oos.writeObject(s1);
oos.close();
System.out.println("序列化ok");
}
}
这里直接运行会报错:
Exception in thread "main" java.io.NotSerializableException: com.test5.Students
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.test5.ObjectOutputStreamTest.main(ObjectOutputStreamTest.java:15)
因为创建的Students 类没有实现序列化接口,所以需要在类里实现Serializable接口,表示这个类可以序列化:
再运行上面的代码,发现对象写入成功:
ObjectInputStream 使用
package com.test5;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
/**
* 实现反序列化(读取重构成对象)
*/
public class ObjectInputStreamTest {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:\\Users\\LDH\\Desktop\\test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//读取文件
Students s = (Students) ois.readObject();
//关闭
ois.close();
System.out.println("执行完毕");
System.out.println(s.toString());
}
}
结果:
注意
1、序列化的类必须实现Serializable接口,包括类中的属性,例如private Address address;
2、类实现Serializable接口后要添加一个常量private static final long serialVersionUID
,即对象序列化版本号
2020之前的idea要自动对象序列化版本号的设置可以参考这篇文章,
我自己是2020idea,设置有些许改变,可以参考这篇文章。
我现在添加了序列化版本号之后:
再运行反序列化读取之前的内容会报错,因为两个对象的序列化版本号不一致。
Exception in thread "main" java.io.InvalidClassException: com.test5.Students; local class incompatible: stream classdesc serialVersionUID = 100, local class serialVersionUID = 120
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1963)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1829)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2120)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:482)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:440)
at com.test5.ObjectInputStreamTest.main(ObjectInputStreamTest.java:14)
在新的序列化版本号的基础上再序列化一次对象,或者修改对象类内部的版本号后再次执行反序列化,结果恢复正常:
3、在属性前加 transient 可以避免被序列化,如private transient int age;
,那这个age就不会被序列化,例如:
被序列化的对象:
Students s1 = new Students("张三",20);
序列化后,再反序列化读取结果:
执行完毕
Students{name='张三', age=0}
可以看到age不会被存入文件,自然在读取的时候也不会被读取到。
4、静态属性也不能被序列化
例如我在学生类中加一条静态属性:
public static String code = "200";
在进行序列化和反序列化之后得到的结果:
执行完毕
Students{name='张三', age=0}
5、序列化多个对象,可以用集合的形式进行序列化和反序列化
例如,被序列化的对象:
Students s1 = new Students("张三",20);
Students s2 = new Students("李四",20);
序列化集合:
ArrayList<Students> list = new ArrayList<>();
list.add(s1);
list.add(s2);
oos.writeObject(list);
反序列化集合:
//读取文件
// Students s = (Students) ois.readObject();
ArrayList<Students> list = (ArrayList<Students>) ois.readObject();
//关闭
ois.close();
System.out.println("执行完毕");
System.out.println(list.toString());
结果:
执行完毕
[Students{name='张三', age=0}, Students{name='李四', age=0}]
6、
字符流
-
Reader (抽象类):字符输入流:
public int read() {}
public int read(char[] c) {}
public int read(char[] c, int off, int len) {} -
Writer (抽象类):字符输出流:
public void write(int n) {}
public void write(Strint str) {}
public void write(char[] c) {}
小案例
这里两次测试分别用字节流读取英文和中文,英文的正常,中文的乱码。这是因为在txt中默认是UTF-8的形式保存的,在UTF-8中一个汉字三个字节,而字节流是一个字节一个字节的读取,所以当一个汉字被拆成三分来输出的时候肯定会变成乱码。
FileReader 使用
文本内容:
创建FileReader,文件字符输入流:
FileReader fr = new FileReader("C:\\Users\\LDH\\Desktop\\test2.txt");
单个字符读取文件:
int data = 0;
while ((data = fr.read()) != -1){
System.out.print((char) data);
}
结果:
你好你好你好
多个字符读取:
char[] buf = new char[2];
int count = 0;
while ((count = fr.read(buf)) != -1){
System.out.println(new String(buf,0,count));
}
结果:
你好
你好
你好
FileWriter 使用
代码:
package com.test5;
import java.io.FileWriter;
public class FileWriterTest {
public static void main(String[] args) throws Exception {
//创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\LDH\\Desktop\\test2.txt");
//写入
for (int i = 0; i < 10; i++) {
fw.write("java你好\r\n");
fw.flush();
}
//关闭
fw.close();
System.out.println("执行完毕");
}
}
结果:
字符流实现文本文件复制
package com.test5;
import java.io.FileReader;
import java.io.FileWriter;
/**
* 使用FileReader和FileWriter复制文件
* 只能复制文本文件,不能复制图片或者二进制文件
*/
public class FileCopy2 {
public static void main(String[] args) throws Exception {
//创建FileReader和FileWriter
FileReader fr = new FileReader("C:\\Users\\LDH\\Desktop\\test2.txt");
FileWriter fw = new FileWriter("C:\\Users\\LDH\\Desktop\\test.txt");
//读、写
int data = 0;
while ((data = fr.read()) != -1){
fw.write(data);
fw.flush();
}
//关闭
fr.close();
fw.close();
System.out.println("执行完毕");
}
}
结果:
注意:
二进制没有字符编码,所以字符流copy二进制的时候无法进行正确的解析,就会造成乱码,而图片里也是二进制,所以字符流操作无法复制图片。
字符缓冲流
- BufferedReader / BufferedWriter
- 高效读写
- 支持输入换行符
- 可一次写一行、读一行
BufferedReader 使用:
package com.test5;
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedReaderTest {
public static void main(String[] args) throws Exception {
//创建缓冲流
FileReader fr = new FileReader("C:\\Users\\LDH\\Desktop\\test2.txt");
BufferedReader br = new BufferedReader(fr);
//读取
//缓冲区读取
char[] buf = new char[1024];
int count = 0;
while ((count = br.read(buf)) != -1){
System.out.println(new String(buf,0,count));
}
//一行一行的读取
String line = null;
while ((line = br.readLine()) != null){
System.out.println(line);
}
//关闭
br.close();
}
}
两种方式结果一致:
java你好
java你好
java你好
java你好
java你好
java你好
java你好
java你好
java你好
java你好
BufferedWriter 使用:
package com.test5;
import java.io.BufferedWriter;
import java.io.FileWriter;
public class BufferedWriterTest {
public static void main(String[] args) throws Exception {
FileWriter fw = new FileWriter("C:\\Users\\LDH\\Desktop\\test2.txt");
BufferedWriter buw = new BufferedWriter(fw);
//写入
for (int i = 0; i < 10; i++) {
buw.write("BufferedWriter");
buw.newLine(); //写入换行符 Windows \r\n , Linux \n
buw.flush();
}
//关闭
buw.close();
System.out.println("执行完毕");
}
}
结果:
打印流 PrintWriter
- 封装了print () / println () 方法,支持写入后换行
- 支持数据原样打印
示例:
package com.test5;
import java.io.PrintWriter;
public class PrintWriterTest {
public static void main(String[] args) throws Exception {
//创建打印流
PrintWriter pw = new PrintWriter("C:\\Users\\LDH\\Desktop\\test2.txt");
//打印
pw.println(97);
pw.println(true);
pw.println("a");
pw.println(3.14);
//会自动刷新
//关闭
pw.close();
}
}
结果:
转换流
- 桥转换流:InputStreamReader / OutputStreamWriter
- 可将字节流转换为字符流
- 可设置字符的编码方式
InputStreamReader 使用:
package com.test5;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class InputStreamReaderTest {
public static void main(String[] args) throws Exception {
//创建InputStreamReader
FileInputStream fis = new FileInputStream("C:\\Users\\LDH\\Desktop\\test.txt");
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//读取文件
int data = 0;
while ((data = isr.read()) != -1){
System.out.print((char) data);
}
//关闭
isr.close();
}
}
new InputStreamReader(fis,"utf-8");
中的第二个参数是要使用的编码形式,如果设置的编码形式和被读取文件的编码形式不一致,就会导致乱码。
OutStreamWriter 使用:
package com.test5;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class OutputStreamWriterTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\Users\\LDH\\Desktop\\test.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
//写入
for (int i = 0; i < 10; i++) {
osw.write("人生若只如初见\r\n");
osw.flush();
}
//关闭
osw.close();
System.out.println("执行完毕");
}
}
与上面一样,new OutputStreamWriter(fos,"gbk");
的第二个参数是指定的编码。
运行结果:
ANSI就是指各个国家的自己的编码,在中国就是指gbk。
File类
概念:代表物理盘符中的一个文件或文件夹
文件操作
分隔符
System.out.println("路径分隔符:"+ File.pathSeparator);
System.out.println("名称分隔符:"+File.separator);
结果:
路径分隔符:;
名称分隔符:\
创建文件
//创建文件
File file = new File("C:\\Users\\LDH\\Desktop\\test3.txt");
boolean b = file.createNewFile();
System.out.println("创建结果:"+b); //创建结果:true
如果再执行的话,会得到结果:创建结果:false
删除文件
//删除文件
//1、直接删除
System.out.println("删除结果:"+file.delete());
//2、使用jvm退出时删除文件,即程序停止运行时,才会删除指定文件或目录
file.deleteOnExit();
文件的一些基本方法
System.out.println("文件绝对路径:"+file.getAbsolutePath());
System.out.println("获取路径:"+file.getPath()); //这里是你写什么路径就获取什么路径
System.out.println("获取文件名称:"+file.getName());
System.out.println("获取文件父目录:"+file.getParent());
System.out.println("获取文件长度:"+file.length());
System.out.println("获取文件创建时间:"+new Date(file.lastModified()).toLocaleString());
结果:
文件绝对路径:C:\Users\LDH\Desktop\test3.txt
获取路径:C:\Users\LDH\Desktop\test3.txt
获取文件名称:test3.txt
获取文件父目录:C:\Users\LDH\Desktop
获取文件长度:0
获取文件创建时间:2022-3-26 14:56:56
Process finished with exit code 0
判断
System.out.println("是否可写:"+file.canWrite());
System.out.println("是否是文件:"+file.isFile());
System.out.println("是否隐藏:"+file.isHidden());
结果:
是否可写:true
是否是文件:true
是否隐藏:false
文件夹操作
创建目录
File dir = new File("C:\\Users\\LDH\\Desktop\\aaa\\bbb\\ccc");
System.out.println(dir.toString());
if (!dir.exists()){
System.out.println("创建结果:"+dir.mkdirs()); //创建多级目录
}
结果:
C:\Users\LDH\Desktop\aaa\bbb\ccc
创建结果:true
删除文件夹
//删除文件夹
//1、直接删除(必须是空目录,否则无法删除)
System.out.println("删除结果:"+dir.delete());
//jvm退出时删除(无返回值)
dir.deleteOnExit();
结果:只删除了ccc目录,再执行就会删除bbb目录
删除结果:true
获取文件夹信息
//获取文件夹信息
System.out.println("获取结对路径:"+dir.getAbsolutePath());
System.out.println("获取文件夹路径:"+dir.getPath());
System.out.println("获取文件夹父目录:"+dir.getParent());
System.out.println("获取文件夹名称:"+dir.getName());
System.out.println("获取文件创建时间:"+new Date(dir.lastModified()).toLocaleString());
结果:
获取结对路径:C:\Users\LDH\Desktop\aaa\bbb\ccc
获取文件夹路径:C:\Users\LDH\Desktop\aaa\bbb\ccc
获取文件夹父目录:C:\Users\LDH\Desktop\aaa\bbb
获取文件夹名称:ccc
获取文件创建时间:2022-3-26 15:23:09
Process finished with exit code 0
判断
//判断
System.out.println("是否是文件夹:"+dir.isDirectory()); //true
System.out.println("是否是隐藏:"+dir.isHidden()); //false
遍历文件夹
//遍历文件夹
File dir2 = new File("D:\\Pictures\\HZW");
String[] files = dir2.list();
System.out.println("---------------------");
for (String s : files){
System.out.println(s);
}
结果:
FileFilter接口
System.out.println("-------------------------");
File[] file2 = dir2.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.getName().endsWith(".jpg")){
return true;
}
return false;
}
});
for (File file : file2){
System.out.println(file.getName());
}
结果:
遍历文件夹
递归遍历文件夹
public static void listDir(File dir) {
File[] files = dir.listFiles();
System.out.println(dir.getAbsolutePath());
if (files != null && files.length >0) {
for (File file : files) {
if (file.isDirectory()){
listDir(file); // 递归
}
System.out.println(file.getAbsolutePath());
}
}
}
传入参数:
public static void main(String[] args) {
listDir(new File("D:\\Pictures"));
}
结果:
递归删除文件夹
因为只有空目录可以删除,所以要删除一个文件夹的话必须得先删除文件夹内部的所有文件。
public static void deleteDir(File dir){
File[] files = dir.listFiles();
if (files != null && files.length>0){
for (File file : files){
if (file.isDirectory()){
deleteDir(file); //递归
}
System.out.println(file.getAbsolutePath()+"删除:"+file.delete());
}
}
System.out.println(dir.getAbsolutePath()+"删除:"+dir.delete());
}
结果:
C:\Users\LDH\Desktop\aaa\bbb\bbbbbbbb\新建文本文档 (2).txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\bbbbbbbb\新建文本文档 (3).txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\bbbbbbbb\新建文本文档.txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\bbbbbbbb删除:true
C:\Users\LDH\Desktop\aaa\bbb\bbbbbbbb删除:false
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹\新建文本文档 (2).txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹\新建文本文档.txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹删除:false
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹 (2)删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文件夹 (2)删除:false
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文本文档 (2).txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc\新建文本文档.txt删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc删除:true
C:\Users\LDH\Desktop\aaa\bbb\ccc删除:false
C:\Users\LDH\Desktop\aaa\bbb\新建文本文档.txt删除:true
C:\Users\LDH\Desktop\aaa\bbb删除:true
C:\Users\LDH\Desktop\aaa\bbb删除:false
C:\Users\LDH\Desktop\aaa\新建文本文档.txt删除:true
C:\Users\LDH\Desktop\aaa删除:true
Process finished with exit code 0