Java IO流介绍目录
前言
最近入职两个月了,感觉每天都在看业务相关的东西,技术上没任何进步,这种感觉非常慌张。因为现在疫情肆虐,经济下行,大环境如此不好,随时都有面临失业的风险,如果不能进步,肯定会被优化掉,所以需要好好学习技术来提升自己。在这里给自己定个目标,至少要两周产出一篇博客,算是学习过程中的总结吧。
Java IO流是非常常用的一种技术,我之前对IO不怎么了解,但每次使用的时候总是在网上搜搜博客来实现自己的功能,虽然可以实现相应功能,但下次使用的时候还是需要在继续在网上搜索,每次搜索还是非常浪费时间的,遂打算系统学习一下进行总结。我先翻看了《Java 编程思想》中对IO流的介绍,可能自己基础比较薄弱的原因,看完后感觉看的云里雾里。然后在B站上看到韩顺平对Java IO流的介绍,感觉非常不错,介绍非常详细,非常推荐基础不好的同学进行观看学习,链接为:韩顺平讲Java IO流专题
一、什么是Java IO流
IO流是Java中的一个重要构成部分。IO,即in
和out
,也就是输入和输出,指java应用程序和外部设备之间进行数据传递,常见的外部设备包括文件、管道、网络连接等,本文介绍主要依托文件进行。
Java中通过流(Stream
)来处理IO,流是一个抽象的概念,是指一连串的数据(字符或字节)以先进先出的方式发送信息的通道。
一般来说流具有一下特点:
- 先进先出:最先写入流中的数据也最先读取。
- 顺序存取:流要按照顺序进行存读,不能随机访问中间的数据。
- 只读或只写: 每个流只能是输入流或输出流中的一种,不能同时具备两个功能,输入流只能进行读操作,输出流只能进行写操作。在一个数据传输过程中,如果既要读取数据,又要写入数据,则要分别提供两个流。
1.1 流的分类
- 按操作数据单位不同分为:字节流(8 bit)二进制文件,字符流(按字符)文本文件
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
(1) Java的IO流共设计40多个类,实际上非常规则,都是从如上四个抽象基类派生的
(2)由这四个类派生出来的子类名称都是以其父类名称作为子类名后缀
1.2 认识文件
想学习Java IO流,首先要认识文件。
文件对我们来说并不陌生,文件是保存数据的地方,比如我们经常使用的word文件,txt文件,excel文件等,还有一张图片,一个视频,一段音乐等也都是文件。
在Java中操作文件有专门的对象,即文件对象。
1.2.1 构建文件对象相关构造器和方法
相关方法
- new File(String pathname) //根据路径构建一个File对象
- new File(File parent, String child) //根据父目录文件+子路径创建
- new FIle(String Parent, String child) //根据父目录+子路径创建
- createNewFile() 创建新文件
代码实例:创建文件news1.txt、news2.txt、news3.txt,用三种不同的方式创建
public class FileDemo01 {
// 方法1创建
@Test
public void test01() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/news1.txt";
File file = new File(filePath);
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件创建失败");
}
}
// 方法2创建
@Test
public void test02() throws IOException {
String parentPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/";
File parentFile = new File(parentPath);
String fileName = "news2.txt";
File file = new File(parentFile, fileName);
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件创建失败");
}
}
// 方法3创建
@Test
public void test03() throws IOException {
String parentPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/";
String fileName = "news3.txt";
File file = new File(parentPath, fileName);
if (file.createNewFile()) {
System.out.println("文件创建成功");
} else {
System.out.println("文件创建失败");
}
}
}
创建文件的结果:
1.2.2 获取文件的相关信息
- getName() :获取文件名字
- getAbsolutePath():获取绝对地址
- getParent():获取父目录地址
- length():获取文件长度,单位是字节
- exists():文件是否存在
- isFile():是否是文件
- isDirectory():是否是目录
代码示例
@Test
public void getFileInfo() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/file.txt";
File file = new File(filePath);
// 1.获取文件名字
String name = file.getName();
System.out.println(name);
// 2.获取绝对地址
String absolutePath = file.getAbsolutePath();
System.out.println(absolutePath);
// 3.获取父目录地址
String parent = file.getParent();
System.out.println(parent);
// 4.获取文件长度,单位是字节
long length = file.length();
System.out.println(length);
// 5.判断文件是否存在
boolean exists = file.exists();
System.out.println(exists);
// 6.判断是否是文件
boolean file1 = file.isFile();
System.out.println(file1);
// 7.判断是否是目录
boolean directory = file.isDirectory();
System.out.println(directory);
}
1.2.3 目录操作和文件删除
- mkdir():创建一级目录
- mkdirs():创建多层目录
- delete():删除空目录或文件
代码示例
@Test
public void test05() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/a/b/c";
File file = new File(filePath);
// 目录不存在时进行创建
if (!file.exists()) {
file.mkdirs();
System.out.println("目录创建成功");
}
// 目录存在时进行删除
if (file.exists()) {
file.delete();
System.out.println("目录删除成功");
}
}
二、节点流
2.1 Java IO流体系
2.2 FileInputStream介绍
FileInputStream是常用的字节输入流
代码示例:使用FileInputStream读取文件hello.txt文件,并将文件内容显示到控制台
hello.txt内容中不包含中文,当文件中有中文时会出现乱码现象
public class FileStreamDemo01 {
//1.单个字节读取
@Test
public void test01() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
FileInputStream fileInputStream = null;
try {
// 由文件路径来构建文件输入流
fileInputStream = new FileInputStream(filePath);
// 字符和int类型可以相互转换
int readData = 0;
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//2.多个字节读取,提高效率
@Test
public void test02() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
FileInputStream fileInputStream = null;
int len = 0;
byte[] buf = new byte[8];
try {
// 由文件路径来构建文件输入流
fileInputStream = new FileInputStream(filePath);
while ((len = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, len));
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
2.3 FileOutputStream介绍
FileOutputStream是字节输出流
代码演示:使用FileOutputStream在hello.txt文件中写入“Hello, world”,如果文件不存在,会创建文件
// 使用FileOutputStream写入文件
@Test
public void test03() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
// 字节输出流,默认使用覆盖文件的方式,FileOutputStream(filePath, true)则为追加模式
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write("hello world".getBytes());
// 使用完毕后一定要记得关闭文件流
fileOutputStream.close();
}
2.4 应用实例:文件拷贝
使用FileInputStream
和FileOutputStream
实现文件拷配
代码
// 进行文件拷贝
public class FileStreamDemo02 {
public static void main(String[] args) {
// 源目标文件路径
String originPath = "/Users/liumingchao/Documents/Java IO.xmind";
// 目标路径
String destPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/Java IO.xmind";
// 创建输入流和输出流
FileInputStream originStream = null;
FileOutputStream destStream = null;
// 读取长度
int len = 0;
// 缓冲字节数组
byte[] buf = new byte[1024];
try {
originStream = new FileInputStream(originPath);
destStream = new FileOutputStream(destPath);
// 当文件复制到末尾时为-1
while ((len = originStream.read(buf)) != -1) {
destStream.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
originStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
destStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.println("文件复制成功");
}
}
2.5 FileReader和FileWriter介绍
FileReader
相关方法
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
- read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
FileWriter
相关方法
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String, true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- writer(char[], off, len):写入字符串的指定部分
FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
代码示例1:使用FileReader从文件中读取内容并显示,文件中有中文字符
public class FileStreamDemo03 {
// 用FileReader读取文件内容并显示
// 每次读入一个字符
@Test
public void test01() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
FileReader fileReader = null;
int data = 0;
try {
// 创建FileReader
fileReader = new FileReader(filePath);
// 使用while循环读入
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fileReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 用FileReader读取文件内容并显示
// 每次读入一个字符数组,提高效率
@Test
public void test02() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
FileReader fileReader = null;
int len = 0;
char[] buf = new char[8];
try {
// 创建FileReader
fileReader = new FileReader(filePath);
// 使用while循环读入
while ((len = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, len));
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fileReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
代码示例2:使用FileWriter
将“好好学习,天天向上”写入到文件中,注意细节
// 使用FileWriter讲"好好学习,天天向上"写入文件
@Test
public void test03() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello2.txt";
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath);
fileWriter.write("好好学习,天天向上".toCharArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 这里一定要close,否则没有真正写入文件中
try {
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
三、处理流
上面介绍的都是节点流,节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter等
处理流(也叫包装流)是“连接”在已存在的流(节点流or处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReader、BufferedWriter、ObjectInputStream、ObjectOutputStream等
节点流和处理流的区别和联系
- 节点流是底层流/低级流,直接跟数据源相接
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出
- 处理流(包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的功能主要体现在两个方面
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
3.1 处理流BufferedReader和BufferedWriter介绍
BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的,关闭处理流时,只需要关闭外层流即可
代码示例1:使用BufferedReader读取文本文件,并显示在控制台中
// 使用BufferedReader读取文本文件
@Test
public void test01() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
// 先构建一个FileReader节点流
FileReader fileReader = new FileReader(filePath);
// 将节点流封装成处理流
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line = null;
// 循环按行读入
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 只关闭外层流即可
bufferedReader.close();
}
代码示例2:使用BufferedWriter
将“好好学习,天天向上”写入到文件中
// 使用BufferedWriter将“好好学习,天天向上”写入到文件中
@Test
public void test02() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello2.txt";
// 先构建一个FileWriter节点流
FileWriter fileWriter = new FileWriter(filePath);
// 将节点流封装成处理流
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("好好学习,天天向上");
bufferedWriter.close();
}
代码示例3:使用BufferedReader
和BufferedWriter
完成文本文件拷贝,要注意文件编码
// 使用BufferedReader和BufferedWriter完成文本文件拷贝,要注意文件编码
@Test
public void test03() {
String originPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
String destPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello3.txt";
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
String line = null;
try {
// 输入流
bufferedReader = new BufferedReader(new FileReader(originPath));
// 输出流
bufferedWriter = new BufferedWriter(new FileWriter(destPath));
// 循环读入
while ((line = bufferedReader.readLine()) != null) {
// 写入文件
bufferedWriter.write(line);
// 添加一个换行
bufferedWriter.newLine();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
bufferedReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
bufferedWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.2 处理流BufferedInputStream和BufferedOutputStream介绍
BufferedInputStream
是字节流,在创建该字节流时,会创建一个内部缓冲区数组
BufferedOutputStream
是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入时就调用底层系统
代码示例:编程完成视频的拷贝
// 使用BufferedInputStream和BufferedOutputStream完成视频拷贝
@Test
public void test04() {
// 源文件目录
String originPath = "/Users/liumingchao/Downloads/chrome/870272070_nb3-1-30064.mp4";
// 目标文件路径
String destPath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/test01.mp4";
//创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
int len = 0;
byte[] buf = new byte[1024];
try {
// 组装输入流和输出流
bis = new BufferedInputStream(new FileInputStream(originPath));
bos = new BufferedOutputStream(new FileOutputStream(destPath));
// 循环读入
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
bis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.3 对象流ObjectInputStream和ObjectOutputStream介绍
看一个需求
- 将int num = 100 这个int保存到文件中,注意不是100数字,而是int 100,并且能够从文件中直接恢复 int 100
- 将Dog dog = new Dog(“小黄”, 3)这个dog对象 保存到 文件中,并且能够从文件恢复
- 上面的要求,就是 能够将 基本数据类型 或者 对象进行 序列化 和 反序列化 操作
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型过
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须要实现如下两个接口之一
Serializable // 这是一个标记接口,没有方法
Externalizable // 该接口有方法需要实现,因此我们一般实现上面的Serializable接口
// ObjectOutputStream测试
@Test
public void test05() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/tmp.dat";
// 对象输出流
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
// 1.写入数字
objectOutputStream.writeInt(100);
// 2.写入boolean
objectOutputStream.writeBoolean(true);
// 3.写入double
objectOutputStream.writeDouble(1.0);
// 4.写入String
objectOutputStream.writeUTF("你好 Java");
// 5.写入对象
objectOutputStream.writeObject(new Dog("小刘", 3, "red"));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
objectOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.println("data save success");
}
// ObjectInputStream测试
@Test
public void test06() {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/tmp.dat";
// 对象输入流
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
// 1.读int
System.out.println(objectInputStream.readInt());
// 2.读boolean
System.out.println(objectInputStream.readBoolean());
// 3.读double
System.out.println(objectInputStream.readDouble());
// 4.读String
System.out.println(objectInputStream.readUTF());
// 5.读对象
Object dog = objectInputStream.readObject();
System.out.println(dog.getClass());
System.out.println(dog);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
objectInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.4 转换流InputStreamReader和OutputStreamWriter介绍
可以用来解决文件乱码问题
InputStreamReader
:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)OutputStreamWriter
:Writer的子类,可以将OutputStream(字节流)包装成Writer(字符流)- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8,gbk等)
代码示例:编程将字节流FileInputStream包装成字符流InputStreamReader,对文件进行读取(utf-8/gbk格式),进而包装成BufferedReader
@Test
public void test() throws IOException {
String filePath = "/Users/liumingchao/Workspace/IdeaWorkspace/temp/hello.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
BufferedReader bufferedReader = new BufferedReader(isr);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}