计算机I/O体系
- 输入设备,就是用相关的硬件把信息输入到电脑中,比如有键盘,鼠标,麦克风等,标准输入设备是键盘(将现实中的物理信号,比如光信号,电信号,波信号,变为数字,然后存储到内存中)
- 输出设备,比如音响,打印机等,标准的输出设备是屏幕(将数字变为物理信号)
- 内存,也称运行内存,比如我们买手机时的8+256G,其中8G就是内存,而程序中处理的信息都是要放在内存中的,它的容量较小,但是运行速度是很快的,而且它如果断电,那么数据就会消失,如果想做到关机之后数据不消失,就得看外存了
- 外存,比如硬盘,U盘这些都是外部存储器,手机的256G就是外存的容量,外存的大小比较大,但是运行速度比较慢,断电后数据不会消失,操作系统的文件都是在硬盘中存储
- CPU,是电脑的中央处理器,类似人的大脑,它是来控制计算机的,比如运行什么程序,和该如何运行,它是跟内存来打交道的,是从内存中去读取和输出数据的,不能直接从硬盘读取数据
常见的I/O设备
- 显示器(O),触屏显示器(I/O),鼠标(I),键盘(I),摄像头(I),扬声器(O),硬盘(I/O)
计算机的存储体系
易失存储
- 断电后不存在
- 跨进程管理,被抽象出的变量和对象(本质是编程语言对硬件中内存的抽象)
持久化存储
- 断电后一般仍旧能保存
- 通常可以跨进程读写
存储速度的差异
- 自上而下,存储速度递减,并且读写速度越快,价格越高,所以弄出专门的存储体系
- 内存(Memory)又被称为RAM,支持以O(1)时间复杂度,根据下标(内存地址)访问的存储
硬盘的实现
-
磁盘(利用磁性实现的一种存储方式)
-
固态硬盘(Soild State Disk SSD)
-
闪存(Flash Memory)
-
主要硬盘都是指磁盘
软件方面认识
- 软件方面不去考虑硬盘本身,只考虑硬盘中的数据(有数据存储的,没有数据存储的)
- 文件是对硬盘中数据的抽象概念
- 所以硬盘的读写问题变成文件的读写问题
OS+文件系统(FileSystem)统一管理文件
- 文件以树形结构进行管理(不是二叉树)
- 文件分为两种
- 存储数据的文件——普通文件(俗称的文件),在Window OS 下,以文件后缀(file suffiex)来标记这个文件存储的内容是什么内容(*.txt 普通文本 *.docx Word 文档)
- 管理树形结构组织数据的文件——目录/文件夹(directory/dir),以/结束,代表这个结点是目录
这个文件树只是一个逻辑结构,而不是硬盘上的物理结构
文件的路径
- 关于文件的路径(Path):根据一个规则,从文件树上唯一确定一个位置这个位置一定对应某个结点,但是这个结点可以不存在
路径的分类
- 绝对路径(absolute path):从盘符开始的路径,这是一个完整的路径。
- 相对路径(relative path) :从我们所在的位置出发,所描述的路径(我们的位置一定是一个目录,不能处在一个文件上)
- 每个进程都有一个当前工作目录,一般一个进程的启动目录就是当前的工作目录
File类
1.1概述
-
File:它是文件和目录路径名的抽象表示
-
文件和目录是可以通过File封装成对象的
-
对于File而言,
其封装的并不是一个真正存在的文件,仅仅是一个路径名而已
。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的。
-
小贴士:
- 一个File对象代表硬盘中实际存在的一个文件或者目录。
- 无论该路径下是否存在文件或者目录,都不影响File对象的创建。
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的File实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的File实例 |
public class Demo1 {
public static void main(String[] args) {
//1相对路径字符串
File file = new File("a.txt");
System.out.println(file.getAbsoluteFile());
//2绝对路径字符串
File file1 = new File("C:\\JavaCode\\FileAndIO\\b.txt");
System.out.println(file1.getAbsoluteFile());
//3父路径名字符串+子路径字符串
File file2 = new File("C:\\JavaCode\\FileAndIO", "c.txt");
System.out.println(file2.getAbsoluteFile());
//4父抽象路径名+子路径字符串
File file3 = new File("C:\\JavaCode\\FileAndIO");
File file4 = new File(file3, "d.txt");
System.out.println(file4.getAbsoluteFile());
}
}
C:\JavaCode\FileAndIO\a.txt
C:\JavaCode\FileAndIO\b.txt
C:\JavaCode\FileAndIO\c.txt
C:\JavaCode\FileAndIO\d.txt
- 在JAVA字符串中\不能直接写\,因为在字符串中反斜杠,表示转义的意思,所以在JAVA中\应该写成
\\
比如"E:\\JAVA代码
" - Windows中使用\作为路径分隔符,Linux使用/作为路基分隔符
- JAVA是跨平台的语言,所以我们在代码中写/或者\都是可以的,比较推荐使用/ E:/JAVA代码"
- 因为我们写的代码在Windows上编写,但是可能在Linux上运行,所以我们不推荐用+这种方式去进行字符串拼接,而是直接调用父+子的的构造方法去生产File
- 其中.表示当前位置(目录),…表示回到当前位置的父节点(目录)
- 根节点的父节点还是自己
- 在相对路径中:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
- – IDEA 中, main 中的文件的相对路径,是相对于"当前工程"
- – IDEA 中,单元测试方法中的文件的相对路径,是相对于"当前 module"
在介绍文件操作的方法,先来了解文件数据的组成
1.2判断功能的方法
对于判断功能,无非就是判断文件的数据信息和元信息

public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File("test\\a.txt");
//1判断file对象对应的文件是否存在
System.out.println(file.exists()); //输出false
//2对于不存在的文件进行判断是否是文件/文件夹
System.out.println(file.isFile());//输出false
System.out.println(file.isDirectory());//输出false
//3利用file的方法进行创建文件
boolean newFile = file.createNewFile();//输出true
//4对于存在的文件进行判断是否是文件/文件夹
System.out.println(file.isFile());//输出true
System.out.println(file.isDirectory());//输出false
//5对存在的文件进行相关信息的查看
System.out.println(file.getAbsoluteFile());//查看绝对路径
// C:\JavaCode\FileAndIO\test\a.txt
System.out.println(file.getPath());//获得创建file对象时的路径
// test\a.txt
System.out.println(file.getName());//获得文件名带后缀
// a.txt
System.out.println(file.length());//获得文件的内容数据的长度,单位是字节数
// 0
System.out.println(file.lastModified());//返回文件的最后修改时间
}
}
1.3创建删除文件\文件夹方法

public class Demo3 {
public static void main(String[] args) throws IOException {
//前提条件C:\JavaCode\FileAndIO\test 这个目录存在
File file1 = new File("test\\b.txt");
File file3 = new File("text\\aaa\\c.txt");
//1创建一个新的空的文件 此时路径是一个文件路径
System.out.println(file1.getName()+"父路径是否存在"+new File("test").exists());
System.out.println(file1.getName()+"是否存在"+file1.exists());
System.out.println(file1.createNewFile());
System.out.println(file1.getName()+"是否存在"+file1.exists());
//3创建一个父路径不存在的file对象
System.out.println(file3.getName()+"父路径是否存在"+new File("test\\aaa").exists());
System.out.println(file3.getName()+"是否存在"+file3.exists());
System.out.println(file3.createNewFile());
System.out.println(file3.getName()+"是否存在"+file3.exists());
}
}
b.txt父路径是否存在true
b.txt是否存在true
false
b.txt是否存在true
c.txt父路径是否存在false
c.txt是否存在false
Exception in thread "main" java.io.IOException: 系统找不到指定的路径。
at java.base/java.io.WinNTFileSystem.createFileExclusively(Native Method)
at java.base/java.io.File.createNewFile(File.java:1043)
at Demo3.main(Demo3.java:26)
- 如果父路径不存在,创建文件会报异常
public class Demo3 {
public static void main(String[] args) throws IOException {
//前提条件C:\JavaCode\FileAndIO\test 这个目录存在
//4 mkdir创建一个新的空的文件目录 父路径存在
File file4 = new File("test\\bbb");
System.out.println(file4.getName()+"父路径是否存在"+new File("test").exists());
System.out.println(file4.getName()+"是否存在"+file4.exists());
System.out.println(file4.mkdir());
System.out.println(file4.getName()+"是否存在"+file4.exists());
//5 mkdir创建一个新的空的文件目录 父路径不存在
File file5 = new File("test\\ccc\\ddd");
System.out.println(file5.getName()+"父路径是否存在"+new File("test\\ccc").exists());
System.out.println(file5.getName()+"是否存在"+file5.exists());
System.out.println(file5.mkdir());
System.out.println(file5.getName()+"是否存在"+file5.exists());
}
}
bbb父路径是否存在true
bbb是否存在false
true
bbb是否存在true
ddd父路径是否存在false
ddd是否存在false
false
ddd是否存在false
- 由此可知,我们的mkdir只能创建单级目录
public class Demo3 {
public static void main(String[] args) throws IOException {
//4 mkdirs创建一个新的空的文件目录 父路径存在
File file5 = new File("test\\bbb");
System.out.println(file5.getName()+"父路径是否存在"+new File("test").exists());
System.out.println(file5.getName()+"是否存在"+file5.exists());
System.out.println(file5.mkdirs());
System.out.println(file5.getName()+"是否存在"+file5.exists());
//5 mkdirs创建一个新的空的文件目录 父路径不存在
File file6 = new File("test\\ccc\\ddd");
System.out.println(file6.getName()+"父路径是否存在"+new File("test\\ccc").exists());
System.out.println(file6.getName()+"是否存在"+file6.exists());
System.out.println(file6.mkdirs());
System.out.println(file6.getName()+"是否存在"+file6.exists());
}
}
bbb父路径是否存在true
bbb是否存在true
false
bbb是否存在true
ddd父路径是否存在false
ddd是否存在false
true
ddd是否存在true
- mkdirs可以创建多级目录
//删除功能
public class Demo4 {
public static void main(String[] args) throws IOException {
File f1 = new File("java1.txt"); //创建在项目文件下
System.out.println(f1.createNewFile());
//删除
System.out.println(f1.delete());
//创建和删除目录
File f2 = new File("itcast");
System.out.println(f2.mkdir());
System.out.println(f2.delete());
System.out.println("----------------------");
//创建cast目录,接着在目录下创建java.txt文件
File f3 = new File("cast");
System.out.println(f3.mkdir());
File f4 = new File("cast\\java.txt");
System.out.println(f4.createNewFile());
System.out.println("----------------------------");
//再次删除:注意得先删除路径下的内容,才能删除文件夹
System.out.println(f3.delete());
System.out.println(f4.delete());
System.out.println(f3.delete());
}
}
true
true
true
true
----------------------
true
true
----------------------------
false
true
true
- 如果目录中有其他文件,那么就不能直接删除这个目录,只能删除空目录
- 返回true,删除成功
- 返回false,对应文件不存在导致的删除失败,非空目录的删除
- IOException其他情况失败(比如其他进程打开文件)
1.4目录的遍历

public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。
public class Demo5 {
public static void main(String[] args) {
File file1 = new File("test");
File[] files = file1.listFiles();
for (File file: files ) {
System.out.println(file.getAbsolutePath());
}
}
}
C:\JavaCode\FileAndIO\test\a.txt
C:\JavaCode\FileAndIO\test\bbb
C:\JavaCode\FileAndIO\test\ccc
小贴士:
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
遍历一个目录下所有文件(包括子目录中的文件)
public class Demo6 {
public static void main(String[] args) {
File file = new File("test");
getAllFilePath(file);
}
public static void getAllFilePath(File file){
//说明当前是个文件夹
File[] files = file.listFiles();
for (File one: files) {
if (one.isDirectory()){
//当前文件是目录
System.out.println(one.getAbsolutePath());
getAllFilePath(one);
}else {
//当前文件是一个文件
System.out.println(one.getAbsolutePath());
}
}
}
}
1.5 综合练习
在当前模块下的aaa文件夹中创建一个a.txt文件
public class Practice1 {
public static void main(String[] args) throws IOException {
//在当前模块下的aaa文件夹中创建一个a.txt文件
File parent = new File("aaa");
//如果没有父路径 则创建 如果有 则不创建
parent.mkdirs();
File file = new File(parent, "a.txt");
boolean newFile = file.createNewFile();
if (newFile){
System.out.println("创建成功");
}else {
System.out.println("创建失败");
}
}
}
找到电脑中所有以avi结尾的电影。(需要考虑子文件夹)
public class Practice1 {
public static void main(String[] args) throws IOException {
//获取该主机下的所有盘符
File[] files = File.listRoots();
for (File file : files) {
findEndWithAVI(file);
}
}
private static void findEndWithAVI(File file){
//获得该目录下所有的文件信息
File[] files = file.listFiles();
for (File one: files) {
//如果是目录,则进目录继续寻找
if(one.isDirectory()){
findEndWithAVI(one);
}else {
//说明当前文件是普通文件 进行判断
if (one.getName().endsWith(".avi")){
System.out.println(one.getAbsolutePath());
}
}
}
}
}
删除多级文件夹
需求: 如果我们要删除一个有内容的文件夹
- 先删除文件夹里面所有的内容
- 再删除自己
public class Test4 {
public static void main(String[] args) {
/*
删除一个多级文件夹
如果我们要删除一个有内容的文件夹
1.先删除文件夹里面所有的内容
2.再删除自己
*/
File file = new File("D:\\aaa\\src");
delete(file);
}
/*
* 作用:删除src文件夹
* 参数:要删除的文件夹
* */
public static void delete(File src){
//1.先删除文件夹里面所有的内容
//进入src
File[] files = src.listFiles();
//遍历
for (File file : files) {
//判断,如果是文件,删除
if(file.isFile()){
file.delete();
}else {
//判断,如果是文件夹,就递归
delete(file);
}
}
//2.再删除自己
src.delete();
}
}
需求:统计一个文件夹中每种文件的个数并打印。(考虑子文件夹)
public class Practice1 {
public static void main(String[] args) throws IOException {
// 需求:统计一个文件夹中每种文件的个数并打印。(考虑子文件夹)
// 打印格式如下:
// txt:3个
// doc:4个
// jpg:6个
HashMap<String, Integer> map = new HashMap<>();
File file = new File("test");
map = getCount(file);
}
public static HashMap<String,Integer> getCount(File src){
File[] files = src.listFiles();
HashMap<String, Integer> map = new HashMap<>();
for (File file: files) {
if(file.isFile()){
//说明当前是一个文件 进行统计
String name = file.getName();
String[] arr = name.split("\\.");
if (arr.length>=2){
String endName = arr[arr.length - 1];
if (map.containsKey(endName)){
//说明已经存在这个类型
map.put(endName,map.get(endName)+1);
}else {
//说明还不存在这个类型
map.put(endName,1);
}
}
}else{
//5.判断,如果是文件夹,递归
//sonMap里面是子文件中每一种文件的个数
HashMap<String, Integer> sonMap = getCount(file);
//hm: txt=1 jpg=2 doc=3
//sonMap: txt=3 jpg=1
//遍历sonMap把里面的值累加到hm当中
Set<Map.Entry<String, Integer>> entries = sonMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
if(map.containsKey(key)){
//存在
int count = map.get(key);
count = count + value;
map.put(key,count);
}else{
//不存在
map.put(key,value);
}
}
}
}
return map;
}
I/O流
2.1 什么是IO
生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s
,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input
和输出output
,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
- 这里对于Java如何去划分输入输出呢?我们划分输入输出是要确定一个主体,对于Java的IO来说主体是程序
- 程序读取本地文件叫输入
- 程序将数据放入本地文件叫输出
2.2 IO的分类
根据数据的流向分为:输入流和输出流。
- 输入流 :把数据从
其他设备
上读取到内存
中的流。 - 输出流 :把数据从
内存
中写出到其他设备
上的流。
格局数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 对于字节流可以操作任意类型的数据文件
- 字符流 :以字符为单位,读写数据的流。
- 字符流只能操作纯文本文件
这里纯文本文件的区分——如果能够通过Windows自带的记事本打开的文件,能够看懂其中的内容就是纯文本文件
2.3 顶级父类们
字节流
3.1 一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
3.2 字节输出流【OutputStream】
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
3.2.1 FileOutputStream类
OutputStream
有很多子类,我们从最简单的一个子类开始。
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
- File表示操作什么,File表示本地文件
- Output表示是输出操作
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
File file = new File("test\\a.txt");
//1创建输出流对象
FileOutputStream fileOutputStream = new FileOutputStream(file);
//2进行输出操作
fileOutputStream.write(97);
//3资源关闭
fileOutputStream.close();
}
}
- 如果对应的文件不存在,会创建这个文件,但是必须保证这个文件的父路径必须存在
- 如果对应的文件存在了,会把这个文件内容清空,再进行写入
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
3.2.2 字节流写数据的3种方式
public class WriteDemo {
public static void main(String[] args) throws IOException {
File file = new File("test\\a.txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(97);//写入数据为a
byte [] arr = {97,98,99,100};
fileOutputStream.write(arr);//写入数据abcd
fileOutputStream.write(arr,1,2);//写入数据bc
fileOutputStream.close();
}
}
- 对应write来说,如果参数是整型,对应的是ASCII对应的字符数据
3.2.3 字节流写数据的两个小问题
- 如何解决续写
- 在我们之前是练习中,如果文件存在,就会清空文件再写入
- 如果想续写,在创建输出流对象的时候给构造方法传入一个true,开启续写模式
- 如何解决换行写入
- 对于不同的操作系统会有不同的换行符,对于Java是跨平台,后面会学习跨平台的操作
- windows的换行符是/r/n
- linux是/n
- mac是/r
public class WriteDemo {
public static void main(String[] args) throws IOException {
File file = new File("test\\a.txt");
//开启续写模式
FileOutputStream fileOutputStream = new FileOutputStream(file, true);
String str = "abcdefge";
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(bytes));//[97, 98, 99, 100, 101, 102, 103, 101]
fileOutputStream.write(bytes);
String str1 = "\r\n";
byte[] bytes1 = str1.getBytes();
fileOutputStream.write(bytes1);//写入换行符
fileOutputStream.write(bytes);
fileOutputStream.close();
}
}
- 总结
- FileOutputStream是可以将程序的数据写入到本地文件上,是字节流的基本流
- 基本步骤
- 创建对象
- 文件存在(覆盖),文件不存在(创建),追加写入
- 写出数据
- 写出整数,写成字节数组,换行写
- 释放资源
- 关闭通道
- 创建对象
3.3文件字节输入流【InputStream】
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read()
: 从输入流读取数据的下一个字节。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
3.3.1 FileInputStream
- File表示操作本地文件
- Input表示输入,是从本地文件输入到程序中
构造方法
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
- 构造举例,代码如下:
public class FileInputStreamConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
}
}
3.3.2字节流读数据的两种方式
- 读取字节:
read
方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
,代码使用演示 - 使用字节数组读取:
read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时
,返回-1
,代码使用演示:
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
File file = new File("a.txt");//文件内容是abcdefg
FileInputStream fileInputStream = new FileInputStream(file);
int b;
while ((b=fileInputStream.read())!=-1){
//fileInputStream.read()每次读取一个字节的数据,读取到末尾返回-1
System.out.print((char) b);
}
fileInputStream.close();
}
}
小贴士:
- 虽然读取了一个字节,但是会自动提升为int类型。
- 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组变成字符串打印
System.out.println(new String(b));
}
// 关闭资源
fis.close();
}
}
输出结果:
ab
cd
ed
- 按理说最后一次读取的数据应该是e,而不是ed
- 因为我们的字节数组中存的是上一次读取的cd,而最后一次只读取了应该e,只是将c覆盖了成了e,所以数组内数据是ed
3.3.3利用字节流复制照片
public class JDPCopy {
public static void main(String[] args) throws IOException {
File oldFile = new File("R-C.jpg");
File newFile = new File("new.jpg");
FileInputStream fileInputStream = new FileInputStream(oldFile);
FileOutputStream fileOutputStream = new FileOutputStream(newFile);
byte[] bytes = new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,len);
}
fileInputStream.close();
fileOutputStream.close();
}
}
- 因为可能数组最后一次读取并没有装满,所以需要每次读取字节的长度
小贴士:
流的关闭原则:先开后关,后开先关。
3.4关于字节流异常处理
public class ExceptionHandle {
public static void main(String[] args) {
File file = new File("a.txt");
//1普通写法
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//2 jdk7语法糖写法
try (FileInputStream fileInputStream = new FileInputStream(file)){
}catch (IOException e) {
e.printStackTrace();
}
//3 jdk9语法糖写法
FileInputStream fileInputStream1 = new FileInputStream(file);
try (fileInputStream1){
}catch (IOException e) {
e.printStackTrace();
}
}
}
字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
- 字符流底层就是字节流,字符流其实就是字节流+字符集
4.1 字符输入流【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
4.1.1 FileReader类
java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
小贴士:
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
public class FileReaderConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);
// 使用文件名称创建流对象
FileReader fr = new FileReader("b.txt");
}
}
4.1.2 利用字符流读取数据
- 按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
- 读到文件末尾,read方法返回-1
public class CharStreamDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("a.txt");
int ch;
//read()默认也是一个一个字节进行读取,如果遇到中文就会一次读取多个
//在读取之后,会进行解码,将二进制的字节解码为对应的码点,也就是二进制
//这个整数也就是字符集上对应的数字
//英文: 文件乱码二进制数据 0110 0001
// read方法进行读取,解码为十进制的97
//中文: 文件里面的二进制数据 11100110 10110001 10001001
// read方法进行读取,解码为十进制的27721
while ((ch=fileReader.read())!=-1){
System.out.println((char) ch);
}
fileReader.close();
}
}
public class CharStreamDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("a.txt");
char []arr =new char[2];//一个char是2个字节 代表一个代码单元(Code Unit)
int len;
while ((len=fileReader.read(arr))!=-1){
for (char c: arr) {
System.out.print(c);
}
}
fileReader.close();
}
- read(char []buffer)其实就是空参的read+强制类型转换
4.2字符输出流【Writer】
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。void write(String str)
写入字符串。void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。
4.2.1 FileWriter类
java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。- 跟我们的字节流差不多,文件不存在则创建一个新的文件,但是要保证父级路径存在
- 如果文件已经存在,则会清空文件,如果不想清空可以打开续写开发
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
- 构造举例,代码如下:
public class FileWriterConstructor {
public static void main(String[] args) throws IOException {
// 使用File对象创建流对象
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("b.txt");
}
}
4.2.2利用字符流写出数据

public class WriteDemo {
public static void main(String[] args) throws IOException {
FileWriter fileWriter = new FileWriter("b.txt");
FileOutputStream fileOutputStream = new FileOutputStream("a.txt");
//1写入一个整数
fileWriter.write(25105);//25105表示的是"我"的码点 写入一个字母文件中显示 我
fileOutputStream.write(25105);//如果通过字节流写入,则写入的是一个字节 文件中则会乱码
//2写入一个字符串
fileWriter.write("abcd");
fileWriter.write("abcd",0,2);
//3写入一个字符数组
fileWriter.write(new char[]{'你','好','啊'});
fileWriter.write(new char[]{'你','好','啊'},0,2);
fileWriter.close();
}
}
4.2.3 关闭和刷新

- 对于我们的FileReader,我们去读取数据的时候,会一次最多读取8192字节的数据放入内存,如果没有这么多数据,就读取最大数据,下次read,如果缓冲区中有数据,就直接从缓冲区中取

- 对于我们的FileWriter写入数据,先写入缓冲区,有两种情况会真正写入文件
- 缓冲区满了
- 手动进行刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
代码使用演示:
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();
// 写出数据,通过close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.close();
}
}
小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
缓冲流
5.1概述
缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
5.2字节缓冲流
5.2.1构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
- 为什么构造方法需要的是字节流,因为字节缓冲流仅仅提供缓冲取,而真正的读写数据还是依靠基本的字节流对象进行操作
普通字节流
public class BufferedDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try(FileInputStream fileInputStream = new FileInputStream("video\\test.mp4");
FileOutputStream fileOutputStream = new FileOutputStream("video\\test1.mp4")) {
int b;
while ((b=fileInputStream.read())!=-1){
fileOutputStream.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
}
}
- 过了很久都没有响应
缓冲流
public class BufferedDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try( BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("video\\test.mp4"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("video\\test1.mp4"))) {
int b;
while ((b=bufferedInputStream.read())!=-1){
bufferedOutputStream.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("复制时间:"+(end - start)+" 毫秒");
}
}
- 耗时3326毫秒
利用数组更快
public class BufferedDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try( BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("video\\test.mp4"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("video\\test2.mp4"))) {
int len;
byte[] bytes = new byte[8*1024];
while ((len=bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("复制时间:"+(end - start)+" 毫秒");
}
}
- 耗时1125毫秒
5.3字符缓冲流
5.3.1 构造方法
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
5.3.2 特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
- BufferedReader:
public String readLine()
: 读一行文字。 - BufferedWriter:
public void newLine()
: 写一行行分隔符,由系统属性定义符号。
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {
System.out.print(line);
System.out.println("------");
}
// 释放资源
br.close();
}
}
public class BufferedWriterDemo throws IOException {
public static void main(String[] args) throws IOException {
// 创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
// 写出数据
bw.write("黑马");
// 写出换行
bw.newLine();
bw.write("程序");
bw.newLine();
bw.write("员");
bw.newLine();
// 释放资源
bw.close();
}
}
输出效果:
黑马
程序
员
总结
- 为什么缓冲流能够提高性能
- 缓冲流自带长度为8192的缓冲区
- 对于字符流来说是8192单位的char缓冲区
- 对于字节流来说是8192字节的缓冲区
- 可以显著提高字节流的读写性能
- 对于字符流提升不明显,对于字符缓冲流而言关键是两个特有的方法
- 缓冲流自带长度为8192的缓冲区
I/O练习
6.1 文件夹的拷贝
public class Test1 {
public static void main(String[] args) throws IOException {
//文件夹的拷贝
File src = new File("test");
File dest = new File("demo");
copyDir(src,dest);
}
private static void copyDir(File src, File dest) throws IOException {
dest.mkdirs();
File[] files = src.listFiles();
for (File file : files) {
if (file.isFile()){
//是文件 进行拷贝
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(new File(dest,file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,len);
}
fileInputStream.close();
fileOutputStream.close();
}else{
//是文件夹 进行递归
copyDir(file,new File(dest,file.getName()));
}
}
}
}
- 对于拷贝这种,我们想到的应该是字节流,因为字节流能操作任何数据,而字符流只能操作文本文件
6.2对文件内容进行排序
public class Test2 {
public static void main(String[] args) throws IOException {
/*
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9
*/
FileInputStream fileInputStream = new FileInputStream("a.txt");
StringBuilder sb = new StringBuilder();
int b;
while ((b=fileInputStream.read())!=-1){
sb.append((char) b);
}
String s = sb.toString();
System.out.println(s);
String[] split = s.split("-");
FileOutputStream fileOutputStream = new FileOutputStream("a.txt");
List<String> collect = Arrays.stream(split)
.map(str -> Integer.parseInt(str))
.sorted((o1, o2) -> o1 - o2)
.map(integer -> String.valueOf(integer))
.collect(Collectors.toList());
for (int i = 0; i < collect.size(); i++) {
if(i==collect.size()-1){
fileOutputStream.write(collect.get(i).getBytes());
}else {
fileOutputStream.write((collect.get(i)+"-").getBytes());
}
}
fileInputStream.close();
fileOutputStream.close();
}
}
6.3 练习:文本排序
请将文本信息恢复顺序。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
案例分析
- 逐行读取文本信息。
- 把读取到的文本存储到集合中
- 对集合中的文本进行排序
- 遍历集合,按顺序,写出文本信息。
public class Test3 {
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("b.txt"));
ArrayList<String> list = new ArrayList<>();
String line;
while ((line=bufferedReader.readLine())!=null){
list.add(line);
}
Collections.sort(list, (o1, o2) -> o1.charAt(0)-o2.charAt(0));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("b.txt"));
for (String s : list) {
bufferedWriter.write(s);
bufferedWriter.newLine();
}
bufferedReader.close();
bufferedWriter.close();
}
}
转换流
- 指定字符集读写——淘汰了
- 字节流想要使用字符流中的方法
在IDEA中,使用FileReader
读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8
编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
public class Demo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("gbkfile.txt");
int b;
while ((b= fileReader.read())!=-1){
System.out.print((char) b);
}
fileReader.close();
}
}
输出结果:
����Ϊ��һ�������긲������
�ݻ�л����һ��
�������£�ϷԺ�������廨Ū��
那么如何读取GBK编码的文件呢?
7.1 InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
指定编码读取
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
创建流对象,指定GBK编码
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("gbkfile.txt"), "GBK");
int b;
while ((b=inputStreamReader.read())!=-1){
System.out.print((char) b);
}
inputStreamReader.close();
}
}
- 但是这种在JDK11时就被废用了,采用FileReader原生的构造方法来指定编码方式
7.2 OutputStreamWriter类
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
指定编码写出
public class OutputDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "E:\\out.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
osw.close();
// 定义文件路径
String FileName2 = "E:\\out2.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}
练习:转换文件编码
将GBK编码的文本文件,转换为UTF-8编码的文本文件。
案例分析
- 指定GBK编码的转换流,读取文本文件。
- 使用UTF-8编码的转换流,写出文本文件。
案例实现
public class TransDemo {
public static void main(String[] args) {
// 1.定义文件路径
String srcFile = "file_gbk.txt";
String destFile = "file_utf8.txt";
// 2.创建流对象
// 2.1 转换输入流,指定GBK编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
// 2.2 转换输出流,默认utf8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
// 3.读写数据
// 3.1 定义数组
char[] cbuf = new char[1024];
// 3.2 定义长度
int len;
// 3.3 循环读取
while ((len = isr.read(cbuf))!=-1) {
// 循环写出
osw.write(cbuf,0,len);
}
// 4.释放资源
osw.close();
isr.close();
}
}
序列流
对象序列化:就是把对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息。
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
要实现序列化和反序列化就要使用对象序列化流和反序列化流:
- 对象序列化流:ObjectOutputStream
- 对象反序列化流:ObjectInputStream
8.1ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
构造举例,代码如下:
FileOutputStream fileOut = new FileOutputStream("student.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
序列化操作
- 一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。 - 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。
public class Student implements Serializable {
public String name;
public int age;
public transient String address;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
2.写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
private static void readStduent() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));
Student student = (Student)objectInputStream.readObject();
System.out.println(student);
}
Serialized data is saved
8.2 ObjectInputStream类
ObjectInputStream类ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));
反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream
读取对象的方法:
public final Object readObject ()
: 读取一个对象。
private static void readStduent() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));
Student student = (Student)objectInputStream.readObject();
System.out.println(student);
}
Student{name='lsc', age=23, address='null'}
- 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个
ClassNotFoundException
异常。
反序列化操作2
**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。**发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Student implements Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public int age;
public transient String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int sex;
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
练习:序列化集合
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。 - 反序列化
list.txt
,并遍历集合,打印对象信息。
案例分析
- 把若干学生对象 ,保存到集合中。
- 把集合序列化。
- 反序列化读取时,只需要读取一次,转换为集合类型。
- 遍历集合,可以打印所有的学生信息
public class ObjectListStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student("lsc", 23, "武汉");
Student student1 = new Student("lsc1", 23, "武汉1");
Student student2 = new Student("lsc2", 23, "武汉2");
ArrayList<Student> students = new ArrayList<>();
students.add(student);
students.add(student1);
students.add(student2);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("list.txt"));
objectOutputStream.writeObject(students);
objectOutputStream.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
// 读取对象,强转为ArrayList类型
ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
for (int i = 0; i < list.size(); i++ ){
Student s = list.get(i);
System.out.println(student);
}
}
}
打印流
- 字节打印流:PrintStream
- 字符打印流:PrintWriter
打印流特点:只负责输出数据,不负责读取数据
字节打印流
构造方法

例:字节打印流
//字节打印流
public class Demo03 {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("ps.txt");
//写数据
//字节输出流有的方法
ps.write(97); //a
//使用特有方法写数据
ps.print(97); //97
ps.println(98);
//释放文件
ps.close();
}
}
字符打印流
构造方法
//字符打印流
public class Demo04 {
public static void main(String[] args) throws IOException {
// PrintWriter pw = new PrintWriter("pw.txt");
// pw.write("hello");
// pw.write("\r\n");
// pw.flush();
// pw.write("world");
// pw.flush();
// pw.println("hello");
// pw.flush();
// pw.println("world");
// pw.flush();
PrintWriter pw = new PrintWriter(new FileWriter("pw.txt"), true); //true实现自动刷新
pw.println("hello");
pw.println("world");
pw.close();
}
}
- 因为字符流底层存在缓冲区,所以需要打开自动刷新
标准输入输出流
System类中有两个静态的成员变量
- public static final InputStream in 标准输入流,通常该流对于键盘输入或者主机环境或者用户指定的另一个输入源
- public static final PrintStream out 标准输出流,通常该流对应显示输出或者主机环境或用户指定的另一个输出目标
标准输入流
public class Demo1 {
public static void main(String[] args) throws IOException {
//标准输入流,数据来自键盘输入
InputStream is = System.in;
//字节读数据
int by;
while ((by = is.read()) != -1) {
System.out.println((char) by);
}
//如何把字节流转换为字符流?:转换流
InputStreamReader isr = new InputStreamReader(is);
//使用字符流能不能够实现一次读取一行数据?:这是字符缓冲输入流的特有方法
BufferedReader br = new BufferedReader(isr);
String ch;
while ((ch=br.readLine())!=null){
System.out.println(ch);
}
// //自己实现键盘录入数据太麻烦了,所以java就提供了一个类供我们使用
// Scanner sc = new Scanner(System.in);
}
}
标准输出流
package myOtherStream;
import java.io.PrintStream;
//标准输出流
public class Demo02 {
public static void main(String[] args) {
//public static final PrintStream out:标准输出流
PrintStream ps = System.out;
//能够方便地打印各种数据值
// ps.print("hello");
// ps.print(100);
// ps.println("world");
// ps.println(200);
//System.out的本质是一个字节输出流
System.out.println("hello");
System.out.println(100);
}
}
Properties
- 是一个Map体系的集合类
- Properties可以保存在流中或者从流中加载
package myOtherStream;
import java.util.Properties;
import java.util.Set;
//Properties作为Map集合的使用
public class Demo09 {
public static void main(String[] args) {
//创建集合对象
// Properties<String,String> prop = new Properties();
Properties prop = new Properties();
//存储元素
prop.put("01", "林青霞");
prop.put("02", "张曼玉");
prop.put("03", "王祖贤");
//遍历集合
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
Object value = prop.get(key);
System.out.println(key + "," + value);
}
}
}
Properties特有方法

package myOtherStream;
import java.util.Properties;
import java.util.Set;
//Properties特有方法
public class Demo10 {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
prop.setProperty("01", "林青霞"); //接收String类型 ,put是Object类型
prop.setProperty("02", "张曼玉");
prop.setProperty("03", "王祖贤");
//根据键获取值
// System.out.println(prop.getProperty("01"));
// System.out.println(prop.getProperty("04"));
// System.out.println(prop);
//获取键
Set<String> names = prop.stringPropertyNames();
for (String key : names) { //String类型,上个例子是Object类型
// System.out.println(key);
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}
}
}
Properties和IO流相结合的方法

//集合中的数据保存到文件
public class Demo11 {
public static void main(String[] args) throws IOException {
//把集合在中的数据保存到文件
myStore();
//把文件中的数据加载到集合
myLoad();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
//load加载
FileReader fr = new FileReader("D:\\itcast\\fw.txt");
prop.load(fr);
fr.close();
System.out.println(prop);
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.setProperty("01", "林青霞");
prop.setProperty("02", "张曼玉");
prop.setProperty("03", "王祖贤");
FileWriter fw = new FileWriter("D:\\itcast\\fw.txt");
prop.store(fw, null); //null指的是描述信息
fw.close();
}
}
va
package myOtherStream;
import java.util.Properties;
import java.util.Set;
//Properties特有方法
public class Demo10 {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
prop.setProperty(“01”, “林青霞”); //接收String类型 ,put是Object类型
prop.setProperty(“02”, “张曼玉”);
prop.setProperty(“03”, “王祖贤”);
//根据键获取值
// System.out.println(prop.getProperty(“01”));
// System.out.println(prop.getProperty(“04”));
// System.out.println(prop);
//获取键
Set<String> names = prop.stringPropertyNames();
for (String key : names) { //String类型,上个例子是Object类型
// System.out.println(key);
String value = prop.getProperty(key);
System.out.println(key + “,” + value);
}
}
}
## Properties和IO流相结合的方法
<img src="https://i-blog.csdnimg.cn/blog_migrate/cef72b09a5ad3486eeae7c7661ed4bce.png" alt="在这里插入图片描述" style="zoom:67%;" />
```java
//集合中的数据保存到文件
public class Demo11 {
public static void main(String[] args) throws IOException {
//把集合在中的数据保存到文件
myStore();
//把文件中的数据加载到集合
myLoad();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
//load加载
FileReader fr = new FileReader("D:\\itcast\\fw.txt");
prop.load(fr);
fr.close();
System.out.println(prop);
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.setProperty("01", "林青霞");
prop.setProperty("02", "张曼玉");
prop.setProperty("03", "王祖贤");
FileWriter fw = new FileWriter("D:\\itcast\\fw.txt");
prop.store(fw, null); //null指的是描述信息
fw.close();
}
}