文章目录
1、java.io
通过数据流,序列化和文件系统提供系统输入和输出。
2、File
File 类是文件和目录路径名的抽象表示。
类名: public class File extends Object implements Serializable, Comparable<File>
构造器:
常用字段:
separator 用于分隔一个路径字符串中多个目录和文件的,pathSeparator 用于分隔多个路径字符串的。
一般为方便路径字符串拼接主要使用 pathSeparator 和 separator 。Windows系统中默认路径分隔符是 “ ; ” 默认名称分隔符是 “ \ ” 。在使用时一般使用 “ \\ ” ,因为一个反斜杠在 Java 中表示转义符,所以需要使用两个反斜杠来代表 “ \ ”。
Linux 系统使用 “ / ”,Windows 系统使用 “ \ ”,巧记方法:Windows 的首字母 W 第一笔画 \ ,Linux 系统则相反。
字段一般都是大写,这里的字段名小写属于 Java 发展的历史遗留问题,起初在命名时命名规范还并未完善。
常用方法:
变量和类型 | 方法名 | 描述 |
---|---|---|
int | compareTo(File pathname) | 按字典顺序比较两个抽象路径名 |
boolean | createNewFile() | 当路径名指向的文件不存在时创建此文件,创建成功返回true,文件已存在返回false。 |
boolean | delete() | 删除该路径表示的文件或目录,删除成功返回true,失败返回false。删除的是目录时只有保证目录为空才能成功删除。 |
boolean | exists() | 判断该路径表示的文件或目录是否存在,存在返回true,不存在返回false。 |
String | getAbsolutePath() | 返回该路径的绝对路径名字符串。 |
String | getName() | 返回该路径表示的文件或目录的名称。 |
String | getParent() | 返回该路径表示的文件或目录的上级目录名称,如果该上级目录不存在则返回null。 |
File | getParentFile() | 返回该路径表示的文件或目录的上级目录的抽象类,如果该上级目录不存在则返回null。 |
String | getPath() | 将该抽象路径名转换为字符串并返回。 |
boolean | isAbsolute() | 判断该抽象路径名是否为绝对路径。是则返回true,不是返回false。 |
boolean | isDirectory() | 判断该抽象路径表示的是否是目录,是则返回true,不是返回false。 |
boolean | isFile() | 判断该抽象路径表示的是否是文件,是则返回true,不是返回false。 |
long | length() | 返回该路径表示的文件的长度(以字节为单位)。文件不存在或路径表示目录则返回0。 |
String[] | list() | 返回一个字符串数组,用于表示该路径表示的目录下的所有文件和目录的名称,仅表示当前目录,不会进入到子目录里,若该路径表示的是文件或目录不存在则返回null。 |
String[] | list(FilenameFilter filter) | 返回一个字符串数组,用于表示该路径表示的目录下的所有使用过滤器过滤后的文件和目录的名称,仅表示当前目录,不会进入到子目录里,若该路径表示的是文件或目录不存在则返回null。 |
File[] | listFiles() | 返回一个抽象路径数组,用于表示该路径表示的目录下的所有文件和目录的抽象类,仅表示当前目录,不会进入到子目录里,若该路径表示的是文件或目录不存在则返回null。 |
File[] | listFiles(FileFilter filter) | 返回一个抽象路径数组,用于表示该路径表示的目录下的所有使用过滤器过滤后的文件和目录的抽象类,仅表示当前目录,不会进入到子目录里,若该路径表示的是文件或目录不存在则返回null。 |
boolean | mkdir() | 创建该抽象路径表示的目录,成功返回true,失败返回false,若该目录的父目录不存在则创建失败。 |
boolean | mkdirs() | 创建该抽象路径表示的目录,包括该目录任何需要但不存在的父目录,成功返回true,失败返回false。 |
boolean | renameTo(File dest) | 重名该抽象路径表示的文件,成功返回true,失败返回false,为了确保操作的成功,一定要检测放方法的返回值。 |
使用案例:
package work.java.xzk10301006.io;
import java.io.File;
import java.io.IOException;
/**
*@ClassName: FileTest
*@Description: File类的使用
*
*/
public class FileTest {
public static void main(String[] args) {
File file = new File("d:" + File.separator + "test.txt");
System.out.print("系统默认名称分隔符:");
System.out.println(File.separator);
System.out.print("系统默认路径分隔符:");
System.out.println(File.pathSeparator);
// 判断文件是否存在
if (!file.exists()) {
try {
// 文件不存在则创建文件,文件父目录必须存在,否则创建失败
if (file.createNewFile()) {
System.out.println(file.getPath() + "创建成功!");
}
} catch (IOException e) {
e.printStackTrace();
}
} else
System.out.println("文件已存在!");
// 获取文件长度
System.out.println(file.getName() + "文件长度:" + file.length());
File file2 = new File("d://test2.txt");
// 文件重命名
System.out.println("文件重命名:" + file.renameTo(file2));
}
}
2.1、文件遍历案例
使用案例:
package work.java.xzk10301006.io;
import java.io.File;
/**
*@ClassName: FileTraverseTest
*@Description: 遍历文件夹下文件及目录案例
*
*/
public class FileTraverseTest {
public static void main(String[] args) {
File file = new File("E:");
if (!file.isDirectory()) {
System.out.println("该目录不存在!");
}
traverseFile(file.listFiles());
}
public static void traverseFile(File[] files) {
// 路径数组不为空则继续执行
if (files != null && files.length > 0) {
// 遍历路径数组
for (File file : files) {
// 判断当前路径指向的是不是文件
if (file.isFile()) {
// 是文件则进入更进一步的条件判断
// file.length()>100*1024可用于判断文件是否大于100KB
// 判断文件是否以.txt为尾缀
if (file.getName().endsWith(".txt")) {
// 找到一个符合文件就打印其绝对路径
System.out.println(file.getAbsolutePath());
}
} else {
// 是文件夹则递归调用方法深入目录里继续遍历
traverseFile(file.listFiles());
}
}
}
}
}
2.2、文件过滤器
文件过滤器 FileFilter 是一个接口,使用该接口需要重写 accept 方法,可以从文件列表中过滤得到想要的结果。(该接口只有一个抽象方法,使用 Lambda 表达式更加方便)
接口定义: public interface FileFilter
使用案例:
package work.java.xzk10301006.io;
import java.io.File;
/**
*@ClassName: FileFilterTest
*@Description: 文件过滤器使用案例
*
*/
public class FileFilterTest {
public static void main(String[] args) {
File file = new File("E:");
if (!file.isDirectory()) {
System.out.println("该目录不存在!");
} else
traverseFile(file);
}
public static void traverseFile(File file) {
// 使用Lambda表达式快速实现FileFilter接口并重写方法
File[] files = file.listFiles((pathname) -> {
// 过滤掉所有不是TXT文件或目录的路径
if (pathname.getName().endsWith(".txt") || pathname.isDirectory()) {
// 满足条件返回true保留
return true;
}
// 不满足条件返回false过滤
return false;
});
// 路径集合不为空则继续
if (files != null && files.length > 0) {
for (File f : files) {
// 是文件夹就递归调用
if (f.isDirectory()) {
traverseFile(f);
} else {
System.out.println("发现一个TXT文件:" + f.getPath());
}
}
}
}
}
2.3、相对路径和绝对路径
绝对路径: 一般从盘符开始,是一个完整的路径,例如:d://1.txt
相对路径: 在Java代码中是相对于项目目录路径,是一个不完整的便捷路径,在Java开发中很常用。
使用案例:
package work.java.xzk10301006.io;
import java.io.File;
/**
*@ClassName: FilePathTest
*@Description: 相对路径和绝对路径案例
*
*/
public class FilePathTest {
public static void main(String[] args) {
// 没有盘符,表示不是绝对路径,不以/开头,表示不是盘符根目录,这里路径直接为文件名,表示相对于项目目录的相对路径
File file1 = new File("1.txt");
// 有盘符,表示一个绝对路径
File file2 = new File("d:/1.txt");
System.out.println("file1的路径:" + file1.getAbsolutePath());
System.out.println("file2的路径:" + file2.getAbsolutePath());
}
}
3、IO流
IO流: 数据的传输操作可以看做一种数据的流动,按照流动的方向分为输入 Input 和输出 Output。
Java 中的 IO 操作指的是 java.io 包下的一些常用类库的使用,通过这些类对数据进行读取(输入 Input)和写出(输出 Output)。
IO 流的分类:
按照流向可以分为:输入流和输出流。
按照流动的数据类型可以分为:字节流和字符流。
一切皆字节:
计算机中的任何数据(文本,图片,视屏,音乐)都是以二进制的形式存储的。1 字节 byte = 8 位 bit,每一位都是一个二进制 0 / 1。
数据传输时也是以二进制的形式存储,任何的流在传输时底层都是二进制。
3.1、字节输出流
OutputStream 为所有字节输出流类的超类,这是一个抽象类。
类名: public abstract class OutputStream extends Object implements Closeable, Flushable
常用方法:
关于 write(int b) 方法:所有 int 数据都是四个字节,此方法会将前三个高位字节去除,把最后一个低位字节写入输出流。
关于 close() 方法:使用完流一定要手动使用该方法释放资源,输入和输出流资源是不会被垃圾回收器回收的,推荐在 finally 块中关闭。
FileOutputStream 是字节输出流最常用的一个类。
类名: public class FileOutputStream extends OutputStream
构造方法:
构造器 | 描述 |
---|---|
FileOutputStream(File file) | 创建一个字节输出流用于写入传入的指定文件 file,文件不存在则创建文件,文件存在则清空文件内容。 |
FileOutputStream(File file, boolean append) | 创建一个字节输出流用于写入传入的指定文件 file,文件不存在则创建文件,文件存在则在文件末尾追加写入内容。 |
FileOutputStream(String name) | 创建一个字节输出流用于写入指定文件名name的文件,文件不存在则创建文件,文件存在则清空文件内容。 |
FileOutputStream(String name, boolean append) | 创建一个字节输出流用于写入指定文件名name的文件,文件不存在则创建文件,文件存在则在文件末尾追加写入内容。 |
使用案例:
package work.java.xzk10301006.io;
import java.io.FileOutputStream;
import java.io.IOException;
/**
*@ClassName: FileOutputStreamTest
*@Description: FileOutputStream类的使用
*
*/
public class FileOutputStreamTest {
public static void main(String[] args) throws IOException {
// 由于权限问题可能无法创建文件,或创建的不是文件而是目录,或文件无法打开都会抛出异常
FileOutputStream fos = new FileOutputStream("d://1.txt");
// 数字65的二进制形式低八位对应编码表的字符A
fos.write(65);
// 写入\r\n的二进制形式可以实现换行,这两个符号占两个字节
fos.write("\r\n".getBytes());
// b数组二进制表示字符ABCDE
byte[] b = {65, 66, 67, 68, 69};
// 将整个byte数组写入文件
fos.write(b);
fos.write("\r\n".getBytes());
// 从byte数组第二个下标开始写入两个字节,即CD
fos.write(b, 2, 2);
// 关闭流!关闭流!关闭流!
fos.close();
System.out.println("写出完成!");
}
}
3.2、字节输入流
InputStream 为所有字节输入流的超类,这是一个抽象类。
类名: public abstract class InputStream extends Object implements Closeable
常用方法:
变量和类型 | 方法名 | 描述 |
---|---|---|
void | close() | 关闭输入流并释放所有与此相关的系统资源。 |
abstract int | read() | 从输入流读取下一个字节数据并返回,如果读取到文件末尾则返回-1。 |
int | read(byte[] b) | 从输入流读取一组字节数据到缓冲区byte数组b中,返回读取到的字节个数,如果已到文件末尾没有读取到数据则返回-1。 |
关于 read(byte[] b): 在读取数据时一般都是用此方法,因为一次可以读一组数据,所以相比一次读取一个字节数据更加快,效率更高。使用此方法还需要注意,每一次读取的数据会覆盖 b 数组,当最后一次的读取的数据个数小于数组长度时会出现数组数据没有被完全覆盖的情况,导致出现额外的重复读取,此时可以使用该方法的返回值来确定读取的数据的个数,以保证读取的准确性。
FileInputStream 是字节输入流最常用的一个类。
构造方法:
构造器 | 描述 |
---|---|
FileInputStream(File file) | 通过打开与一个实际文件的链接来创建输入流,该文件是文件系统中指定的 file 文件。 |
FileInputStream(String name) | 通过打开与一个实际文件的链接来创建输入流,该文件是文件系统中指定的名为 name 的文件。 |
使用案例:
package work.java.xzk10301006.io;
import java.io.FileInputStream;
import java.io.IOException;
/**
*@ClassName: FileInputStreamTest
*@Description: FileInputStream使用案例
*
*/
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
// 这里使用字节输出流案例得到的1.txt文件
FileInputStream fis = new FileInputStream("d://1.txt");
int read;
System.out.println("一次读取一个字节:");
while (true) {
// 每次读取一个字节,读到-1表示文件读取完毕,退出循环
if ((read = fis.read()) == -1) {
break;
}
// 得到的是二进制字节数据的十进制int数据形式,需要转换成char型字符打印
System.out.print((char) read);
}
fis.close();
System.out.println();
System.out.println("一次读取一组字节:");
FileInputStream fis2 = new FileInputStream("d://1.txt");
byte[] bs = new byte[3];
while (true) {
// 一次读取一组字节,这里是三个字节,读到-1退出循环
if ((read = fis2.read(bs)) == -1) {
break;
}
// 使用String的构造器将读到的read个字节转换成字符打印
System.out.print(new String(bs, 0, read));
}
fis2.close();
}
}
3.3、字符输出流
使用字节流读取文字时可能会出现乱码问题,字符流可以解决部分问题。字符流的底层仍是字节流,但是它会将字节封装为字符,以字符为单位进行输入或输出。由于字符的字节数不定,为了保证输出完整的字符,在输出方与输入方之间有一个缓冲区存在,所有写方法写出的字符都保存在其中,通过刷新流一次性把缓冲区中所有的字符写入到输入方,关闭流之前也会自动调用一次刷新流方法。
Writer 字符写入流的抽象类,是字符写入流的顶级父类。
类名: public abstract class Writer extends Object implements Appendable, Closeable, Flushable
常用方法:
关于 write(int c) 方法:所有 int 数据都是四个字节即32位,此方法会将前16个高中位去除,把后16个中低位写入字符输出流。
FileWriter 常用的字符输出类。
类名: public class FileWriter extends OutputStreamWriter
构造方法:
构造器 | 描述 |
---|---|
FileWriter(File file) | 为指定的文件 file 创建一个字符输出流,使用平台默认的字符集。文件不存在则新建文件,存在则清空文件。 |
FileWriter(File file, boolean append) | 为指定的文件 file 创建一个字符输出流,使用平台默认的字符集。文件不存在则新建文件,存在则在文件末尾追加写入。 |
FileWriter(String fileName) | 为指定文件名为 name 的文件创建一个字符输出流,使用平台默认的字符集。文件不存在则新建文件,存在则清空文件。 |
FileWriter(String fileName, boolean append) | 为指定文件名为 name 的文件创建一个字符输出流,使用平台默认的字符集。文件不存在则新建文件,存在则在文件末尾追加写入。 |
使用案例:
package work.java.xzk10301006.io;
import java.io.FileWriter;
import java.io.IOException;
/**
*@ClassName: FileWriterTest
*@Description: FileWriter的使用
*
*/
public class FileWriterTest {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("d://a.txt");
// 写入一个int数据的中低16位
fw.write(65);
// 写入一个字符
fw.write('b');
// 写入一个字符数组
fw.write(new char[]{'c', 'd'});
// 写入一个字符串
fw.write("锄禾日当午,");
// 使用append方法可以多次追加写入字符串
fw.append("汗滴禾下土。").append("谁知盘中餐,").append("粒粒皆辛苦。");
// 关闭流,关闭前会自动调用flush()方法刷新缓冲区
fw.close();
}
}
3.4、字符输入流
Reader 字符输入流抽象类,所有字符输入流的顶级父类。
类名: public abstract class Reader extends Object implements Readable, Closeable
常用方法:
变量和类型 | 方法名 | 描述 |
---|---|---|
abstract void | close() | 关闭流并释放与流相关的所有系统资源。 |
int | read() | 一次读一个字符,并返回字符的十进制数值。 |
int | read(char[] cbuf) | 一次读一组字符到cbuf数组中,返回读到的字符数量。 |
FileReader 使用默认缓冲区大小从字符文件中读取文本,字符输入流常用类。
类名: public class FileReader extends InputStreamReader
构造方法:
构造器 | 描述 |
---|---|
FileReader(File file) | 为要读取的 file 文件创建一个新的字符输入流,使用平台默认的字符集。 |
FileReader(String fileName) | 为要读取的名为 fileName 的文件创建一个新的字符输入流,使用平台默认的字符集。 |
使用案例:
package work.java.xzk10301006.io;
import java.io.FileReader;
import java.io.IOException;
/**
*@ClassName: FileReaderTest
*@Description: FileReader类的使用
*
*/
public class FileReaderTest {
public static void main(String[] args) throws IOException {
// 此处使用字符输出流案例中生成的a.txt文件
FileReader fr = new FileReader("d://a.txt");
int read;
System.out.println("一次读一个字符:");
while (true) {
// 一次读一个字符,读到-1即文件末尾时停止
if ((read = fr.read()) == -1) {
break;
}
System.out.print((char) read);
}
fr.close();
System.out.println();
System.out.println("一次读一组字符:");
FileReader fr2 = new FileReader("d://a.txt");
char[] cbuf = new char[5];
while (true) {
// 一次读一组字符,读到-1即文件末尾时停止,这里一次读五个字符
if ((read = fr2.read(cbuf)) == -1) {
break;
}
System.out.print(new String(cbuf, 0, read));
}
fr2.close();
}
}
3.5、转换流
转换流:将字节流转换为字符流,使用装饰者设计模式。
InputStreamReader 将字节输入流转换为字符输入流。
OutputStreamWriter 将字节输出流转换为字符输出流。
使用案例:
package work.java.xzk10301006.io;
import java.io.*;
/**
*@ClassName: ConversionFlowTest
*@Description: 转换流使用案例
*
*/
public class ConversionFlowTest {
public static void main(String[] args) throws IOException {
// 获取字节输出流,这里测试方便直接创建
FileOutputStream fos = new FileOutputStream("d://b.txt");
// 将字节输出流fos装饰为字符输出流
OutputStreamWriter osw = new OutputStreamWriter(fos);
// 使用字符输出流的方式写出字符
osw.append("锄禾日当午,").append("汗滴禾下土。").append("谁知盘中餐,").append("粒粒皆辛苦。");
// 刷新流
osw.flush();
// 关闭流
osw.close();
// 获取字节输入流,这里测试方便直接创建
FileInputStream fis = new FileInputStream("d://b.txt");
// 将字节输入流fis装饰为字符输入流
InputStreamReader isr = new InputStreamReader(fis);
// 使用字符输入流的方式读取字符
char[] cbuf = new char[5];
int len;
while (true) {
if ((len = isr.read(cbuf)) == -1) {
break;
}
System.out.print(new String(cbuf, 0, len));
}
isr.close();
}
}
3.6、打印流
在 Java 开发中使用 System.out.println()
可以在控制台打印信息,这里的 out 其实就是一个字符输出打印流。
PrintStream :字符输出打印流,可以向其他输出流添加一些功能。此流不同于其他输出流,打印时不会抛出异常。使用 print 方法打印即相当于输出流的 write 方法写入,但是打印流每次写入都会自动调用 flush 方法刷新流。
PrintWriter :作用与 printStream 类似,区别在于每次写入不会调用 flush 方法刷新流,需要自己手动调用。
上述两个字符输出打印流也可以实现把字节流转换为字符打印流的功能,类似转换流,传入字节流参数即可。
使用案例:
package work.java.xzk10301006.io;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
*@ClassName: PrintTest
*@Description: 打印流使用案例
*
*/
public class PrintTest {
public static void main(String[] args) throws FileNotFoundException {
// 指定信息要打印的位置
PrintStream ps = new PrintStream("d://c.txt");
ps.println("锄禾日当午,");
ps.println("汗滴禾下土。");
ps.println("谁知盘中餐,");
ps.println("粒粒皆辛苦。");
ps.close();
/* PrintWriter pw = new PrintWriter("d://c.txt");
pw.println("锄禾日当午,");
pw.println("汗滴禾下土。");
pw.println("谁知盘中餐,");
pw.println("粒粒皆辛苦。");
// 需要手动刷新,或close时自动刷新
pw.flush();
pw.close();*/
}
}
3.7、缓存读取流
BufferedReader 缓存读取流,可以将字符输入流转换为带有缓存的,一次可以读取一行的缓存读取流。
使用案例:
package work.java.xzk10301006.io;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
*@ClassName: BufferedReaderTest
*@Description: 缓存读取流使用案例
*
*/
public class BufferedReaderTest {
public static void main(String[] args) throws IOException {
// 创建字符输入流,这里使用打印流案例创建的c.txt文件
FileReader fr = new FileReader("d://c.txt");
// 将字符输入流fr转换为缓存读取流
BufferedReader br = new BufferedReader(fr);
String text;
// readLine方法返回读到的一行字符串,当读到文件末尾时返回null
while ((text = br.readLine()) != null) {
System.out.println(text);
}
}
}
3.8、Properties
Properties 即涉及到了集合也涉及到了流,可以将内存中的集合数据通过流存储到文件中,也可以将文件中集合数据加载到内存中。
使用案例:
package work.java.xzk10301006.io;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
/**
*@ClassName: PropertiesTest
*@Description: Properties与流的使用案例
*
*/
public class PropertiesTest {
public static void main(String[] args) throws IOException {
// 创建Properties集合
Properties ppt = new Properties();
ppt.put("name", "java");
ppt.put("info", "java开发实战讲解");
// 创建字符输出流,使用Properties时需要注意文件命名规范,必须以.properties结尾
FileWriter fw = new FileWriter("d://book.properties");
// 使用store方法通过字符输出流fw将集合数据输出到指定文件中,comments为添加的注释,中文注释将被转为Unicode码
ppt.store(fw, "图书信息-bookMessage");
// 关闭流
fw.close();
Properties ppt2 = new Properties();
// 创建字符输入流
FileReader fr = new FileReader("d://book.properties");
// 使用load方法将指定输入流的集合数据加载到内存中
ppt2.load(fr);
// 通过键获取值
System.out.println(ppt2.getProperty("name"));
System.out.println(ppt2.getProperty("info"));
fr.close();
}
}
4、序列化与反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程,Java 反序列化是指把字节序列恢复为 Java 对象的过程。通过序列化和反序列化实现网络传输、本地存储的目的。
4.1、Serializable实现Java序列化
未实现此接口的类将不会将其任何状态序列化或反序列化。 可序列化类的所有子类型本身都是可序列化的。 序列化接口没有方法或字段,仅用于标识可序列化的语义。
接口定义: public interface Serializable
使用案例:
package work.java.xzk10301006.io;
import java.io.*;
/**
*@ClassName: SerializableTest
*@Description: Serializable实现序列化
*
*/
public class SerializableTest {
// 实现Serializable接口,仅用于标记次类可以被序列化
static class Book implements Serializable {
private String title;
private String author;
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
'}';
}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建Book对象
Book book = new Book("java", "Han");
// 创建字节输出流
FileOutputStream fos = new FileOutputStream("d://book.txt");
// 通过字节输出流fos创建ObjectOutputStream,使用它可以实现序列化
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 使用writeObject方法将对象写入流
oos.writeObject(book);
// 关闭流
oos.close();
System.out.println("序列化完毕!");
// 创建字节输入流
FileInputStream fis = new FileInputStream("d://book.txt");
// 通过字节输入流创建ObjectInputStream,使用它可以实现反序列化
ObjectInputStream ois = new ObjectInputStream(fis);
// 使用readObject方法将输入流里的对象读到内存中
Book book1 = (Book) ois.readObject();
System.out.println("反序列化完毕!");
System.out.println(book1);
ois.close();
}
}
4.2、部分序列化
使用 static 和 transient 修饰的字段序列化时将被忽视。
添加私有方法(必须是 private void 否则不生效) writeObject 和 readObject 自定义序列化的字段, 在使用 ObjectOutputStream 进行序列化时,会先检查被序列化的类是否有私有的,无返回值的 writeObject 方法,有的话将委托该方法进行对象序列化。
使用案例:
package work.java.xzk10301006.io;
import java.io.*;
/**
*@ClassName: SerializableTest2
*@Description: Serializable实现部分序列化
*
*/
public class SerializableTest2 {
// 实现Serializable接口,仅用于标记次类可以被序列化
static class Book implements Serializable {
private String title;
private String author;
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
'}';
}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("部分序列化...");
// 只对书名进行序列化
oos.writeObject(title);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("部分反序列化...");
title = (String) ois.readObject();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建Book对象
Book book = new Book("java", "Han");
// 创建字节输出流
FileOutputStream fos = new FileOutputStream("d://book.txt");
// 通过字节输出流fos创建ObjectOutputStream,使用它可以实现序列化
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 使用writeObject方法将对象写入流
oos.writeObject(book);
// 关闭流
oos.close();
// 创建字节输入流
FileInputStream fis = new FileInputStream("d://book.txt");
// 通过字节输入流创建ObjectInputStream,使用它可以实现反序列化
ObjectInputStream ois = new ObjectInputStream(fis);
// 使用readObject方法将输入流里的对象读到内存中
Book book1 = (Book) ois.readObject();
System.out.println(book1);
ois.close();
}
}
4.3、Externalizable实现Java序列化
Externalizable 继承自 Serializable,使用 Externalizable 接口需要实现 readExternal 方法和 writeExternal 方法来实现序列化和反序列化。 就像在类中私有writeObject 和 readObject 方法自定义序列化字段一样,它通过直接重写方法达到自定义序列化的功能。
接口定义: public interface Externalizable extends Serializable
抽象方法:
使用案例:
package work.java.xzk10301006.io;
import java.io.*;
/**
*@ClassName: ExternalizableTest
*@Description: Externalizable实现序列化案例
*
*/
public class ExternalizableTest {
static class Book implements Externalizable {
private String name;
private String info;
public Book() {
}
public Book(String name, String info) {
this.name = name;
this.info = info;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", info='" + info + '\'' +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("自定义序列化...");
out.writeObject(name);
out.writeObject(info);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("自定义反序列化...");
name = (String) in.readObject();
info = (String) in.readObject();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建Book对象
Book book = new Book("java", "Han");
// 创建字节输出流
FileOutputStream fos = new FileOutputStream("d://book.txt");
// 通过字节输出流fos创建ObjectOutputStream,使用它可以实现序列化
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 使用writeObject方法将对象写入流
oos.writeObject(book);
// 关闭流
oos.close();
System.out.println("序列化完毕!");
// 创建字节输入流
FileInputStream fis = new FileInputStream("d://book.txt");
// 通过字节输入流创建ObjectInputStream,使用它可以实现反序列化
ObjectInputStream ois = new ObjectInputStream(fis);
// 使用readObject方法将输入流里的对象读到内存中
Book book1 = (Book) ois.readObject();
System.out.println("反序列化完毕!");
System.out.println(book1);
ois.close();
}
}
4.4、Serializable和Externalizable的区别
一个类在使用 Externalizable 实现序列化时必须存在一个无参构造器,因为在反序列化时会用到无参构造器实例化对象,没有无参构造器将会抛出异常。在重写的反序列化方法中接收反序列化对象字段时必须按照字段定义的顺序来接收才能保证对象信息的正确性。
区别 | Serializable | Externalizable |
---|---|---|
实现复杂度 | 实现简单,Java对其有内建支持 | 实现复杂,由开发人员自己完成 |
执行效率 | 所有对象由Java统一保存,性能 较低 | 开发人员决定哪个对象保存,可能造成速度 提升 |
保存信息 | 保存时占用空间大 | 部分存储,可能造成空间减少 |
使用频率 | 高 | 低 |
5、try-with-resources
在使用流时可能会有异常发生,可以抛出异常或使用 try-catch 处理异常,开发中一般都是需要处理异常的,这时一般将流的关闭方法 close 放在 finally 块中以保证它一定执行,try-with-resources 的出现简化了这一操作。
只有实现了 Closeable 或 AutoCloseable 接口的类才能使用 try-with-resources 自动调用关闭方法 close。而 IO 流的类基本都实现了。
使用方式:
// JDK7时的特性
// 在try()的括号里创建的对象会在try-catch执行完毕后自动调用close方法
try (Reader reader = new FileReader("XXX")) {
} catch (IOException e) {
e.printStackTrace();
}
// JDK9时进行优化
// 在try()的括号里的一个或多个对象会在try-catch执行完毕后自动调用close方法,多个对象使用;隔开
Reader reader = new FileReader("XXX");
Writer writer = new FileWriter("XXX");
try (reader; writer) {
} catch (IOException e) {
e.printStackTrace();
}
使用案例:
package work.java.xzk10301006.io;
import java.io.*;
/**
*@ClassName: TryWithResourcesTest
*@Description: 自定义类实现try-with-resources使用案例
*
*/
public class TryWithResourcesTest {
static class CloseDemo implements Closeable {
@Override
public void close() throws IOException {
System.out.println("关闭方法被调用了...");
}
}
public static void main(String[] args) {
CloseDemo closeDemo = new CloseDemo();
try (closeDemo) {
System.out.println("try块执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}