序言
介绍 Java中用于输入和输出的各种 API 学习目标:
- 学习如何访问文件和目录
- 如何以二进制格式和文本格式来读写数据
- 对象序列化机制
- 正则表达式
一.输入/输出流
- 输入流:可以从其中读入一个字节序列的对象
- 输出流:可以从其中写入一个字节序列的对象
来源地/目的地:文件、网络连接、内存块
基础:抽象类 InputStream和 OutPutStream
1.1 读写字节
InputSteam类的 read 方法 与 OutputStream的 write 方法,其实现类只需要覆盖这一个方法。
重点:
- read/write 方法在执c行时都将阻塞,直到字节被读入/写出
- 当完成对输入/输出流的读写时,应该通过调用 close方法关闭,因为操作系统资源十分有限。
- 输出流的字节都会被置于缓冲区,以便用更大的包的形式一次性传递更多字节,在关闭输出流的时候同时会把缓冲区的字节全部冲出。如果不关闭,那么最后一个包可能永远得不到传递。此外,可以通过调用 flush方法认为冲刷输出。
相关方法:
-
java.io.InputStream
- abstract int read()
读入一个字节,并返回该字节,在碰到输入流结尾时返回 -1
- void close()
关闭这个输入流
-
java.io.OutputStream
-
abstract void write(int n)
写出一个字节的数据
-
void close()
冲刷并关闭输出流
-
void flush()
-
冲刷输出流
二. 文本输入与输出
在存储文本字符串时,需要考虑字符编码方式。Java 内别使用的是 UTF-16
2.1 如何写入文本输出
对于文本输出,可以使用 PrintWriter ,这个类拥有以文本格式打印 字符串和数组的方法
PrintWriter out = new PrintWriter("employ.txt","UTF-8");
String name = "风清默";
double salary = 8000;
out.print(name);
out.print(' ');
out.print(salary);
out.flush();
相关方法:
-
java.io.PrintWriter
-
PrintWrite(String filename,String encoding
通过打印一个使用给定的编码方式向给定的文件写出新的PrintWriter
-
void print(Object obj)
通过打印从 toString 产生的字符串打印一个对象
-
2.2 如何读入文本输入
最简单的方法就是使用 Scanner类
Scanner scanner = new Scanner(new FileInputStream("employ.txt"));
while (scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
2.3 以文本格式存储对象
结合以上两小节7
2.4 字符编码方式
输入和输出流都是用于字节序列的,但是在许多情况下,我们希望操作的是文本,即字符序列。于是,字符如何编码成字节就成了问题。
编码方式 | ||
---|---|---|
UTF-8 | 传统的包含了英语中所有字符的ASCII字符集中的每个字符只占一个字节 | |
UTF-16 | Java字符串使用,通常一个字符占用两个字节 | |
ISO 8859-1 | 包含了西欧各种语言中用到的带有重音符号的字符 |
不存在任何可靠的方式可以自动探测出字节流中所使用的字符编码方式。
某些API方法让我们使用 “默认字符集”,即计算机的操作系统首选的字符编码方式。
因此,应该总是明确的指定编码方式。
三. 读写二进制数据
文本格式对于测试和调试而言会显得很方便,因为它是人类可阅读的,但是不像 二进制格式传递数据那样高效。
3.1 DataInput和 DataOutput接口
3.2 随机访问文件
RandomAccessFile 类可以在文件中任何位置查找或写入数据。
3.3 Zip文档
写出zip
- 对于你希望放入到 ZIP 文件的每一项,都应该创建一个 ZIPEntry 对象, 并将文件名传递给 ZipEntry的构造器,它将设置其他诸如文件日期和解压缩方法等参数,如果需要 ,你可以覆盖这些设置。
- 然后,调用 ZipOutputStream的 putNextEntry方法来开始写出新文件,当完成时需要调用 closeEntry。
- 对所有需要存储的文件重复这一过程
public static void zipWriter() throws IOException {
// 替换为你想要压缩的目录路径
String sourceDirectory = "zipDir";
try (FileOutputStream fout = new FileOutputStream("test.zip");
ZipOutputStream zout = new ZipOutputStream(fout)) {
File directory = new File(sourceDirectory);
File[] files = directory.listFiles();
for (File file : files) {
//1.创建 zipEntry .并设置文件名 (在ZIP中的路径)
String name = file.getAbsolutePath().substring(directory.getAbsolutePath().length() + 1);
ZipEntry ze = new ZipEntry(name);
zout.putNextEntry(ze);
//2.将文件内容写入
try (FileInputStream fin = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int length;
while ((length = fin.read(buffer)) > 0) {
zout.write(buffer, 0, length);
}
}
zout.closeEntry();
}
}
}
相关 方法:
java.util.zip.ZipOutputStream
-
ZipOutputStream(OutputStream out)
创建一个将压缩数据写出到指定的OutputStream的 ZipOutputStream
-
void putNextEntry(ZipEntry ze)
将给定的 ZipEntry 中的信息写出到数据流中,并定位用于写出数据的流。如果这些数据可以通过 write()写出到这个数据流中。
-
void closeEntry()
关闭这个 ZIP 文件中当前打开的项。使用 putNextEntry方法可以开始下一项
读入zip
public static void zipReader() throws IOException {
try (FileInputStream fis = new FileInputStream("test.zip"); ZipInputStream zin = new ZipInputStream(fis)) {
ZipEntry entry;
while ((entry = zin.getNextEntry()) != null) {
// 打印条目的名称
System.out.println("Entry: " + entry.getName());
// 读取条目内容
byte[] buffer = new byte[1024];
int length;
while ((length = zin.read(buffer)) > 0) {
// 这里你可以对读取到的数据进行处理
// 例如,写入到文件系统或者进行其他处理
// 为了演示,我们只是简单地打印出来
System.out.write(buffer, 0, length);
}
// 关闭当前条目,准备读取下一个条目
zin.closeEntry();
}
}
}
四. 对象输入/输出流与序列化
4.1 保存和加载序列化对象
对象要实现 Serializable接口
保存序列化对象
public static void saveSerializedObject() throws IOException {
try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employ.dat"))) {
Employee harry = new Employee("harry",50000, LocalDate.of(2005,10,1));
Manager fqm = new Manager("风清默",80000, LocalDate.of(2004,10,1));
out.writeObject(harry);
out.writeObject(fqm);
}
}
读入序列化对象
public static void readSerializedObject() throws IOException, ClassNotFoundException {
try(ObjectInputStream in = new ObjectInputStream(new FileInputStream("employ.dat"))) {
Employee e1 = (Employee) in.readObject();
Manager e2 = (Manager) in.readObject();
System.out.println(e1);
System.out.println(e2);
}
}
每个对象都是用一个序列号(serial number) 保存的,这就是这种机制之所以被称为 对象序列化的原因
- 写出序列化对象算法
- 对你遇到的每一个对象引用对关联一个序列号
- 对于每个对象,当第一次遇到时,保存其对象数据到输出流中
- 如果某个对象之前被保存过,那么只写出”与之前保存过的序列化为 x 的对象相同“
- 读入序列化对象
- 第一次遇到序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联
- c’xv’b当遇到 “与之前保存过的序列号 为 x 的对象相同” 标记时,获取与这个顺序号相关联的对象引用·
序列化的另一种非常重要的应用是通过网络将对象集合传送到另一台计算机上。正如在文件中保存原生的内存地址毫无意义一样,这些地址对于在不同的处理器之间的通信也是毫无意义的。因此序列化用序列号代替了内存地址,所以它允许将对象从一台机器传送到另一台机器 xxxx
五.操作文件
六.内存映射文件
大多数 操作系统都可以利用虚拟内存实现来将一个文件或者文件的一部分 "映射"到内存中。然后,这个文件就可以当作是内存数组一样地访问,这比传统的文件操作快得多
6.1 内存映射文件的性能
在同一台机器上,对 JDK 的 jre/lib目录中的 37MB的 rt.jar 文件用不同的方式计算校验和,记录下来的·时间数据如表
方法 | 时间 |
---|---|
普通输入流 | 110秒 |
带缓冲的输入流 | 9.9秒 |
随机访问文件 | 162秒 |
内存映射文件 | 7.2秒 |
java.nio包使内存映射变得十分简单,下面是我们需要做的
-
首先,从文件中获得一个通道 (channel),通道是用户磁盘文件的一种抽象。它使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性
FileChannel channel = FileChannel.open(path.options);
-
然后,通过调用FileChannel类的 map 方法 从这个通道中获得 一个 ByteBuffer