JavaSE进阶--玩转IO流

前言

📢 大家好,我是程序员Forlan,本篇内容主要分享IO流方面的知识,属于基础内容,一方面是新人必备知识,另一方面也方便自己和大家后续查找相关资料

一、File类介绍

1、概念引入

首先来介绍两个概念

  • 文件:为了便于数据的管理和检索,引入了文件,本质上都是字节组成,0和1,只不过有不同的形态(文本、图片、视频…)
  • 文件夹:为了便于对文件进行管理和使用,引入了文件夹,不同文件放到不同文件夹,分门别类

对于面向对象编程,我们操作的是对象,盘符上的文件/文件夹,需要封装为对象,我们才能进行操作,这个对象就是File,通过File类,我们可以获取文件/文件夹的各种信息,操作文件/文件夹

2、实际应用

2.1 操作文件

File f1 = new File("D:\\text.txt");
File f2 = new File("D:/text.txt");
/**
 * File.separator,本地文件系统的名称分隔符:在UNIX系统上,这个字段的值是'/';在Microsoft Windows系统上,它是'\\'
 * 建议使用这种
 */
File f3 = new File("d:" + File.separator + "text.txt");
if (f1.exists()) {
	// 如果文件存在,将文件删除操作
	f1.delete();
} else {
	// 如果文件不存在,就创建这个文件
	f1.createNewFile();
}
System.out.println("文件是否可读:" + f1.canRead());
System.out.println("文件是否可写:" + f1.canWrite());
System.out.println("文件的名字:" + f1.getName());
System.out.println("上级目录:" + f1.getParent());
System.out.println("是否是一个目录:" + f1.isDirectory());
System.out.println("是否是一个文件:" + f1.isFile());
System.out.println("是否隐藏:" + f1.isHidden());
System.out.println("文件的大小:" + f1.length());
System.out.println("是否存在:" + f1.exists());
System.out.println("绝对路径:" + f1.getAbsolutePath()); // 完整路径
System.out.println("相对路径:" + f1.getPath()); // 相对某个参照物的路径,在junit的测试方法中,相对路径指的就是相对模块,就是模块名之后的路径
System.out.println("toString:" + f1.toString()); // 相对路径
System.out.println(f1 == f2);//比较两个对象的地址
System.out.println(f1.equals(f2));//比较两个对象对应的文件的路径

2.2 操作文件夹

基本文件的命令,它也可以用,这里就不演示重复的了

File f = new File("D:\\forlan");
// 创建单层目录,前提:之前的目录已经存在,不然创建失败
f.mkdir();
// 创建多层目录
f.mkdirs();
// 删除:只会删除一层,前提:这层目录是空的,里面没有内容,如果有内容就不会被删除
f.delete();
// 获取当前文件夹下的文件或文件夹,不包括下一级
// File[] files = f.listFiles();
String[] list = f.list();
for (String s : list) {
	System.out.println(s);
}

二、IO流介绍

前面提到的FIle类,可以获取以及操作文件/文件夹表层信息,但拿不到里面的内容,这时候就出现了I/O流,用于处理设备之间的数据的传输,可以简单理解为一根“管”,连接不同东西进行传输

分类

  • 按照流的方向,分为:输入流、输出流
  • 按照处理数据的单位,分为:字节流、字符流
    注:字节流可以理解细管,字符流可以理解为粗管,传输大小不一样

所以按照不同的方向组合,就出现了4个基类:字节输入流、字节输出流、字符输入流、字节输出流
针对这4个抽象基类,又可以延伸出来不同类型的实现,具体如下图:

在这里插入图片描述

下面我们就针对不同的实现,来看看具体代码如何写

三、字符流

Windows的记事本默认保存的ANSI,也就是GBK,我们要转为UTF-8,它不支持,但我们可以使用转换流,这个后面会讲

1、读文件

1.1 一次读一个

// 读文件,先得到文件对象
File f = new File("D:\\text.txt");
// Windows的记事本默认保存的ANSI,也就是GBK,我们要转出UTF-8
try (FileReader fr = new FileReader(f);) {
	int n;
	// 如果到了文件的结尾处,那么读取的内容为-1
	while ((n = fr.read()) != -1) {
		System.out.print((char) n);
	}
} catch (IOException e) {
	e.printStackTrace();
}

1.2 一次读多个,使用char数组去装

// 读文件,先得到文件对象
File f = new File("D:\\text.txt");
try (FileReader fr = new FileReader(f);) {
	// 使用数组,一次性读10个
	char[] ch = new char[10];
	int len = fr.read(ch);
	// 如果到了文件的结尾处,那么读取的内容为-1
	while (len != -1) {
		System.out.print(new String(ch, 0, len));
		len = fr.read(ch);
	}
} catch (IOException e) {
	e.printStackTrace();
}

代码中的new String(ch, 0, len)可以写成new String(ch, 0, ch.length)?
答案肯定是不行的,会重复读取内容,比如最后len=2,实际上只需要展示2个字符,但还是展示了10个字符,8个是之前的字符

2、写文件

FileWriter说明:

  • 如果目标文件不存在,会自动创建文件
  • 如果目标文件存在:
    • new FileWriter(f),:对原文件覆盖
    • new FileWriter(f,true) : 在原文件末尾追加`

2.1 一次写一个

// 写的文件对象
File f = new File("D:\\text.txt");
try (FileWriter fw = new FileWriter(f, true);) {
	String content = "你好,程序员Forlan";
	for (int i = 0; i < content.length(); i++) {
		fw.write(content.charAt(i));
	}
} catch (IOException e) {
	e.printStackTrace();
}

2.2 一次写完,使用字符数组

// 写的文件对象
File f = new File("D:\\text.txt");
try (FileWriter fw = new FileWriter(f, true);) {
	String content = "你好,程序员Forlan";
	// 转为字符数据,一次性写完
	char[] chars = content.toCharArray();
	fw.write(chars);
} catch (IOException e) {
	e.printStackTrace();
}

3、文件复制

3.1 综合应用

// 源文件
File f1 = new File("D:\\text.txt");
// 目标文件
File f2 = new File("D:\\textCopy.txt");
try (FileReader fr = new FileReader(f1); FileWriter fw = new FileWriter(f2);) {
	char[] ch = new char[10];
	int len = fr.read(ch);
	while (len != -1) {
		fw.write(ch, 0, len);
		len = fr.read(ch);
	}
} catch (IOException e) {
	e.printStackTrace();
}

3.2 使用缓冲流提高效率

缓冲流:BufferedReader、BufferedWriter ,这个后面会讲到

// 源文件
File f1 = new File("D:\\text.txt");
// 目标文件
File f2 = new File("D:\\textCopy.txt");
try (FileReader fr = new FileReader(f1); FileWriter fw = new FileWriter(f2);
	 BufferedReader br = new BufferedReader(fr); BufferedWriter bw = new BufferedWriter(fw);) {
	char[] ch = new char[10];
	int len = br.read(ch);
	while (len != -1) {
		bw.write(ch, 0, len);
		len = br.read(ch);
	}
} catch (IOException e) {
	e.printStackTrace();
}

四、字节流

字节流,一般用来操作非文本文件,因为中文字符,不同编码,所占用的字节不同,文本文件,建议使用字符流操作
文本文件:txt、java、c…
非文本文件:jpg、mp3 、mp4…

1、读文件

1.1 一次读一个

File f = new File("D:\\forlan.png");
try (FileInputStream fis = new FileInputStream(f);) {
	int len = fis.read();
	// 如果到了文件的结尾处,那么读取的内容为-1
	while (len != -1) {
		System.out.print(len);
		len = fis.read();
	}
} catch (IOException e) {
	e.printStackTrace();
}

1.2 一次读多个,使用byte数组去装

File f = new File("D:\\forlan.png");
try (FileInputStream fis = new FileInputStream(f);) {
	byte[] bytes = new byte[1024];
	int len = fis.read(bytes);
	while (len != -1) {
		System.out.print(new String(bytes, 0, len));
		len = fis.read(bytes);
	}
} catch (IOException e) {
	e.printStackTrace();
}

2、写文件

非文本很难写完整,下面我们以复制的例子来演示写操作

2.1 一次写一个

File f1 = new File("D:\\forlan.png");
File f2 = new File("D:\\forlanCopy.png");
try (FileInputStream fis = new FileInputStream(f1); FileOutputStream fos = new FileOutputStream(f2);) {
	int len = fis.read();
	while (len != -1) {
		fos.write(len);
		len = fis.read();
	}
} catch (IOException e) {
	e.printStackTrace();
}

2.2 一次写多个,使用byte数组

File f1 = new File("D:\\forlan.png");
File f2 = new File("D:\\forlanCopy.png");
try (FileInputStream fis = new FileInputStream(f1); FileOutputStream fos = new FileOutputStream(f2);) {
	byte[] bytes = new byte[1024];
	int len = fis.read(bytes);
	while (len != -1) {
		fos.write(bytes, 0, len);
		len = fis.read(bytes);
	}
} catch (IOException e) {
	e.printStackTrace();
}

3、使用缓冲流完成文件复制

File f1 = new File("D:\\forlan.png");
File f2 = new File("D:\\forlanCopy.png");
try (FileInputStream fis = new FileInputStream(f1); FileOutputStream fos = new FileOutputStream(f2);
	 BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos);) {
	byte[] bytes = new byte[1024];
	int len = bis.read(bytes);
	while (len != -1) {
		bos.write(bytes, 0, len);
		len = bis.read(bytes);
	}
} catch (IOException e) {
	e.printStackTrace();
}

五、缓冲流

无论是字符流,还是字节流,为什么要用缓冲区?
假设有500b的文件,我们来看不同写法,具体是怎么操作的
一个个读写,要操作500次
利用byte[10],要操作50次
利用缓冲区,直接一次性全部读到缓冲区,底层会帮我们刷新缓冲区到磁盘
所以,很简单,缓冲区效率高

六、转换流

作用:将字节流和字符流进行转换,属于一个处理流
InputStreamReader :字节输入流 ——》字符的输入流
OutputStreamWriter :字符输出流 ——》字节的输出流

1、读文件

前面我们提到,我们读取的文件内容为乱码,使用转换流可以指定编码,正确展示

File f = new File("D:\\text.txt");
try (FileInputStream fis = new FileInputStream(f);
	 InputStreamReader isr = new InputStreamReader(fis);) {
	// 可以指定一个编码,不知道的话,默认是程序本身的编码utf-8
	// InputStreamReader isr = new InputStreamReader(fis,"utf-8");
	char[] ch = new char[10];
	int len = isr.read(ch);
	while (len != -1) {
		System.out.print(new String(ch, 0, len));
		len = isr.read(ch);
	}
} catch (IOException e) {
	e.printStackTrace();
}

2、复制文件

// 源文件
File f1 = new File("D:\\text.txt");
// 目标文件
File f2 = new File("D:\\textCopy.txt");
try (FileInputStream fis = new FileInputStream(f1); InputStreamReader isr = new InputStreamReader(fis, "utf-8");
	 FileOutputStream fos = new FileOutputStream(f2); OutputStreamWriter osw = new OutputStreamWriter(fos);) {
	char[] ch = new char[10];
	int len = isr.read(ch);
	while (len != -1) {
		osw.write(ch, 0, len);
		len = isr.read(ch);
	}
} catch (IOException e) {
	e.printStackTrace();
}

七、System流

System.in:标准输入流——》默认从键盘录入
System.out:标准输出流

1、System.in

1.1 从键盘录入

读到的东西,会被转为ascall码

InputStream in = System.in;
int n = in.read();

1.2 Scanner

扫描键盘

Scanner sc = new Scanner(System.in);
int num = sc.nextInt();

扫描文件

Scanner sc = new Scanner(new FileInputStream(new File("D:\\text.txt")));
while (sc.hasNext()) {
	System.out.print(sc.next());
}

2、System.out

// 写法1
PrintStream out = System.out;
out.print("程序员forlan");

// 写法2
System.out.println("程序员forlan");

3、从键盘录入内容输出到文件中

// 目标文件
File f = new File("D:\\text.txt");
// 属于字节流
InputStream in = System.in;
try (InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr);
	 FileWriter fw = new FileWriter(f); BufferedWriter bw = new BufferedWriter(fw);) {
	String s = br.readLine();
	while (!s.equals("quit")) {
		bw.write(s);
		bw.newLine();
		s = br.readLine();
	}
} catch (Exception e) {
	e.printStackTrace();
}

八、对象流

对象流:ObjectInputStream,ObjectInputStream
作用:用于存储和读取基本数据类型数据或对象的处理流
亮点:可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来

1、写文件

可以指定写入什么数据类型
在这里插入图片描述

1.1 写String和基本数据类型

File f = new File("D:\\text.txt");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));) {
	oos.writeUTF("程序员forlan");
	oos.writeInt(100);
} catch (IOException e) {
	e.printStackTrace();
}

1.2 写自定义对象

1)自定义对象

public class Forlan implements Serializable {
	private String name;
	private int level;

	public Forlan(String name, int level) {
		this.name = name;
		this.level = level;
	}
}

2)写入文件

File f = new File("D:\\text.txt");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));) {
	Forlan forlan = new Forlan("程序员Forlan", 100);
	oos.writeObject(forlan);
} catch (IOException e) {
	e.printStackTrace();
}

我们注意到一个细节,自定义对象实现了Serializable接口 ,不实现就会报错,如下:

java.io.NotSerializableException: cn.lw.io.Forlan
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at cn.lw.io.ObjectStream.writeObject(ObjectStream.java:31)
	at cn.lw.io.ObjectStream.main(ObjectStream.java:13)

2、读文件

我们写入什么内容,都可以把指定类型的数据读出来
在这里插入图片描述
具体代码

File f = new File("D:\\text.txt");
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));) {
	System.out.println(ois.readUTF());
} catch (Exception e) {
	e.printStackTrace();
}

3、序列化&反序列化

前面我们提到,自定义对象必须实现Serializable接口,否则,去写文件就会报错
这个接口里是空的,是一个标识接口,实现这个接口的类对象才能序列化

public interface Serializable {
}

3.1 什么是序列化?反序列化又是什么?

像ObjectOutputStream类,是把内存中的Java对象转换成二进制数据,然后就可以把数据保存在磁盘上,相反,ObjectInputStream类,是把磁盘上的二进制数据转换成Java对象进行操作

序列化:数据被转换成二进制数据的过程
反序列化:数据从二进制数据恢复成原样的过程

注:写文件的对象需要序列化,网络传输的对象也需要序列化(比如rpc请求)

3.2 序列化是全部对象内容都序列化?

1)被序列化的类,内部的所有属性,必须是可序列化的

基本数据类型、String都是可序列化的,但自定义对象需要实现Serializable

2)static,transient修饰的属性不可以被序列化

不可以被序列化,指的是属性内容不被保存或传输,反序列化出来的内容为空

3.3 认识serialVersionUID

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量,serialVersionUID,用来表明类的不同版本间的兼容性
1)作用
对序列化对象进行版本控制,有关反序加化时会检测是否兼容
2)生成时机
没有显示声明的话,它的值是Java运行时环境根据类的内部细节自动生成的,若类的实例变量做了修改,serialVersionUID 可能发生变化,所以建议还是显式声明

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)

举个栗子:
原来Forlan类不加toString方法,进行序列化,也就是写文件

public class Forlan implements Serializable {
	private String name;
	private int level;

	public Forlan(String name, int level) {
		this.name = name;
		this.level = level;
	}

	// @Override
	// public String toString() {
	// 	return "Forlan{" +
	// 			"name='" + name + '\'' +
	// 			", level=" + level +
	// 			'}';
	// }
}

打开注释,再加上toString方法,进行反序列化会报错

java.io.InvalidClassException: cn.lw.io.Forlan; local class incompatible: stream classdesc serialVersionUID = 113793900596523614, local class serialVersionUID = -7076236456819894490
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
	at cn.lw.io.ObjectStream.readObject(ObjectStream.java:20)
	at cn.lw.io.ObjectStream.main(ObjectStream.java:14)

解决,加serialVersionUID,因为值固定后,无论我们怎么改变类内容,都不会变,也就不会报错了,如果不加,serialVersionUID会隐式加,跟随类内部细节自动生成

IDEA中配置序列化版本号
在这里插入图片描述

九、数据流

数据流:用来操作基本数据类型和字符串的
DataInputStream:将文件中存储的基本数据类型和字符串 写入 内存的变量中
DataOutputStream: 将内存中的基本数据类型和字符串的变量 写出 文件中

写文件

File f = new File("D:\\text.txt");
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(f));) {
	dos.writeUTF("程序员Forlan");
	dos.writeBoolean(false);
	dos.writeInt(100);
} catch (Exception e) {
	e.printStackTrace();
}

读文件

File f = new File("D:\\text.txt");
try (DataInputStream dis = new DataInputStream(new FileInputStream(f));) {
	System.out.println(dis.readUTF());
	System.out.println(dis.readBoolean());
	System.out.println(dis.readInt());
} catch (Exception e) {
	e.printStackTrace();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Forlan

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值