压缩(ZIP文档)
Java IO类库是支持读写压缩格式的数据流的。我们可以把一个或一批文件压缩成一个zip文档。这些压缩相关的流类是按字节处理的。
先看下设计压缩解压缩的相关流类。
压缩类 | 功能 |
---|---|
CheckedInputStream | getCheckSum()可以为任何InputStream产生校验和(不仅是解压缩) |
CheckedOutputStream | getCheckSum()可以为任何OutputStream产生校验和(不仅是压缩) |
DeflaterOutputStream | 压缩类的基类 |
ZipOutputStream | 继承自DeflaterOutputStream,将数据压缩成Zip文件格式 |
GZIPOutputStream | 继承自DeflaterOutputStream,将数据压缩成GZIP文件格式 |
InflaterInputStream | 解压缩类的基类 |
ZipInputStream | 继承自InflaterInputStream,解压缩Zip文件格式的数据 |
GZIPInputStream | 继承自InflaterInputStream,解压缩GZIP文件格式的数据 |
表格中CheckedInputStream 和 CheckedOutputStream 一般会和Zip压缩解压过程配合使用,主要是为了保证我们压缩和解压过程数据包的正确性,得到的是中间没有被篡改过的数据。
我们以CheckedInputStream 为例,它的构造器需要传入一个Checksum类型:
1 public CheckedInputStream(InputStream in, Checksum cksum) {
2 super(in);
3 this.cksum = cksum;
4 }
而Checksum 是一个接口,可以看到这里又用到了策略模式
,具体的校验算法是可以选择的。Java类库给我提供了两种校验和算法:Adler32 和 CRC32
,性能方面可能Adler32 会更好一些,不过CRC32可能更准确。各有优劣吧。
好了,接下来看下压缩/解压缩流的具体使用。
将多个文件压缩成zip包
1public class ZipFileUtils {
2 public static void compressFiles(File[] files, String zipPath) throws IOException {
3
4 // 定义文件输出流,表明是要压缩成zip文件的
5 FileOutputStream f = new FileOutputStream(zipPath);
6
7 // 给输出流增加校验功能
8 CheckedOutputStream checkedOs = new CheckedOutputStream(f,new Adler32());
9
10 // 定义zip格式的输出流,这里要明白一直在使用装饰器模式在给流添加功能
11 // ZipOutputStream 也是从FilterOutputStream 继承下来的
12 ZipOutputStream zipOut = new ZipOutputStream(checkedOs);
13
14 // 增加缓冲功能,提高性能
15 BufferedOutputStream buffOut = new BufferedOutputStream(zipOut);
16
17 //对于压缩输出流我们可以设置个注释
18 zipOut.setComment("zip test");
19
20 // 下面就是从Files[] 数组中读入一批文件,然后写入zip包的过程
21 for (File file : files){
22
23 // 建立读取文件的缓冲流,同样是装饰器模式使用BufferedReader
24 // 包装了FileReader
25 BufferedReader bfReadr = new BufferedReader(new FileReader(file));
26
27 // 一个文件对象在zip流中用一个ZipEntry表示,使用putNextEntry添加到zip流中
28 zipOut.putNextEntry(new ZipEntry(file.getName()));
29
30 int c;
31 while ((c = bfReadr.read()) != -1){
32 buffOut.write(c);
33 }
34
35 // 注意这里要关闭
36 bfReadr.close();
37 buffOut.flush();
38 }
39 buffOut.close();
40 }
41
42 public static void main(String[] args) throws IOException {
43 String dir = "d:";
44 String zipPath = "d:/test.zip";
45 File[] files = Directory.getLocalFiles(dir,".*\\.txt");
46 ZipFileUtils.compressFiles(files, zipPath);
47 }
48}
在main函数中我们使用了本文中 File其实是个工具类 章节里的Directory工具类。
解压缩zip包到目标文件夹
1 public static void unConpressZip(String zipPath, String destPath) throws IOException {
2 if(!destPath.endsWith(File.separator)){
3 destPath = destPath + File.separator;
4 File file = new File(destPath);
5 if(!file.exists()){
6 file.mkdirs();
7 }
8 }
9 // 新建文件输入流类,
10 FileInputStream fis = new FileInputStream(zipPath);
11
12 // 给输入流增加检验功能
13 CheckedInputStream checkedIns = new CheckedInputStream(fis,new Adler32());
14
15 // 新建zip输出流,因为读取的zip格式的文件嘛
16 ZipInputStream zipIn = new ZipInputStream(checkedIns);
17
18 // 增加缓冲流功能,提高性能
19 BufferedInputStream buffIn = new BufferedInputStream(zipIn);
20
21 // 从zip输入流中读入每个ZipEntry对象
22 ZipEntry zipEntry;
23 while ((zipEntry = zipIn.getNextEntry()) != null){
24 System.out.println("解压中" + zipEntry);
25
26 // 将解压的文件写入到目标文件夹下
27 int size;
28 byte[] buffer = new byte[1024];
29 FileOutputStream fos = new FileOutputStream(destPath + zipEntry.getName());
30 BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
31 while ((size = buffIn.read(buffer, 0, buffer.length)) != -1) {
32 bos.write(buffer, 0, size);
33 }
34 bos.flush();
35 bos.close();
36 }
37 buffIn.close();
38
39 // 输出校验和
40 System.out.println("校验和:" + checkedIns.getChecksum().getValue());
41 }
42
43 // 在main函数中直接调用
44 public static void main(String[] args) throws IOException {
45 String dir = "d:";
46 String zipPath = "d:/test.zip";
47// File[] files = Directory.getLocalFiles(dir,".*\\.txt");
48// ZipFileUtils.compressFiles(files, zipPath);
49
50 ZipFileUtils.unConpressZip(zipPath,"F:/ziptest");
51 }
这里解压zip包还有一种更加简便的方法,使用ZipFile对象
。该对象的entries()方法直接返回ZipEntry类型的枚举。看下代码片段:
1 ZipFile zipFile = new ZipFile("test.zip");
2 Enumeration e = zipFile.entries();
3 while (e.hasMoreElements()){
4 ZipEntry zipEntry = (ZipEntry) e.nextElement();
5 System.out.println("file:" + zipEntry);
6 }
对象序列化
什么是序列化和反序列化呢?
序列化就是将对象转成字节序列的过程,反序列化就是将字节序列重组成对象的过程。
在这里插入图片描述
为什么要有对象序列化机制
程序中的对象,其实是存在有内存中,当我们JVM关闭时,无论如何它都不会继续存在了。那有没有一种机制能让对象具有“持久性”呢?序列化机制提供了一种方法,你可以将对象序列化的字节流输入到文件保存在磁盘上。
序列化机制的另外一种意义便是我们可以通过网络传输对象了,Java中的 远程方法调用(RMI)
,底层就需要序列化机制的保证。
在Java中怎么实现序列化和反序列化
首先要序列化的对象必须实现一个Serializable接口(这是一个标识接口,不包括任何方法)
1public interface Serializable {
2}
其次需要是用两个对象流类:ObjectInputStream 和ObjectOutputStream
。主要使用ObjectInputStream对象的readObject方法读入对象、ObjectOutputStream的writeObject方法写入对象到流中
下面我们通过序列化机制将一个简单的pojo对象写入到文件,并再次读入到程序内存。
1public class User implements Serializable {
2 private String name;
3 private int age;
4
5 public User(String name, int age) {
6 this.name = name;
7 this.age = age;
8 }
9
10 @Override
11 public String toString() {
12 return "User{" +
13 "name='" + name + '\'' +
14 ", age='" + age + '\'' +
15 '}';
16 }
17
18 public static void main(String[] args) throws IOException, ClassNotFoundException {
19 User user = new User("二营长",18);
20 ObjectOutputStream objectOps = new ObjectOutputStream(new FileOutputStream("f:/user.out"));
21 objectOps.writeObject(user);
22 objectOps.close();
23
24 // 再从文件中取出对象
25 ObjectInputStream objectIns = new ObjectInputStream(new FileInputStream("f:/user.out"));
26
27 // 这里要做一次强转
28 User user1 = (User) objectIns.readObject();
29 System.out.println(user1);
30 objectIns.close();
31 }
32}
33
程序运行结果:
1User{name='二营长', age='18'}
不想序列化的数据使用transient(瞬时)关键字屏蔽
如果我们上面的user对象有一个password字段,属于敏感信息,这种是不能走序列化的方式的,但是实现了Serializable 接口的对象会自动序列化所有的数据域,怎么办呢?在password字段上加上关键字transient就好了。
1 private transient String password;
序列化机制就简单介绍到这里吧。这是Java原生的序列化,现在市面上有好多序列化协议可以选择,比如Json、FastJson、Thrift、Hessian 、protobuf等
。
I/O流的典型使用方式
IO流种类繁多,可以通过不同的方式组合I/O流类,但平时我们常用的也就几种组合。下盘通过示例的方式盘点几种I/O流的典型用法。
缓冲输入文件
1public class BufferedInutFile {
2 public static String readFile(String fileName) throws IOException {
3 BufferedReader bf = new BufferedReader(new FileReader(fileName));
4 String s;
5
6 // 这里读取的内容存在了StringBuilder,当然也可以做其他处理
7 StringBuilder sb = new StringBuilder();
8 while ((s = bf.readLine()) != null){
9 sb.append(s + "\n");
10 }
11 bf.close();
12 return sb.toString();
13 }
14
15 public static void main(String[] args) throws IOException {
16 System.out.println(BufferedInutFile.readFile("d:/1.txt"));
17 }
18}
格式化内存输入
要读取格式化的数据,可以使用DataInputStream。
1public class FormattedMemoryInput {
2 public static void main(String[] args) throws IOException {
3 try {
4 DataInputStream dataIns = new DataInputStream(
5 new ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes()));
6 while (true){
7 System.out.print((char) dataIns.readByte());
8 }
9 } catch (EOFException e) {
10 System.err.println("End of stream");
11 }
12 }
13}
上面程序会在控制台输出当前类本身的所有代码,并且会抛出一个EOFException异常
。抛出异常的原因是已经到留的结尾了还在读数据。这里可以使用available()做判断还有多少可以的字符。
1package com.herp.pattern.strategy;
2
3import java.io.ByteArrayInputStream;
4import java.io.DataInputStream;
5import java.io.IOException;
6
7public class FormattedMemoryInput {
8 public static void main(String[] args) throws IOException {
9 DataInputStream dataIns = new DataInputStream(
10 new ByteArrayInputStream(BufferedInutFile.readFile("FormattedMemoryInput.java").getBytes()));
11 while (true){
12 System.out.println((char) dataIns.readByte());
13 }
14 }
15}
基本的文件输出
FileWriter
对象可以向文件写入数据。首先创建一个FileWriter和指定的文件关联,然后使用BufferedWriter
将其包装提供缓冲功能,为了提供格式化机制,它又被装饰成为PrintWriter
。
1public class BasicFileOutput {
2 static String file = "BasicFileOutput.out";
3
4 public static void main(String[] args) throws IOException {
5 BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java")));
6 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
7
8 int lineCount = 1;
9 String s;
10 while ((s = in.readLine()) != null){
11 out.println(lineCount ++ + ": " + s);
12 }
13 out.close();
14 in.close();
15 }
16}
下面是我们写出的BasicFileOutput.out文件
,可以看到我们通过代码字节加上了行号
11: package com.herp.pattern.strategy;
22:
33: import java.io.*;
44:
55: public class BasicFileOutput {
66: static String file = "BasicFileOutput.out";
77:
88: public static void main(String[] args) throws IOException {
99: BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput")));
1010: PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
1111:
1212: int lineCount = 1;
1313: String s;
1414: while ((s = in.readLine()) != null){
1515: out.println(lineCount ++ + ": " + s);
1616: }
1717: out.close();
1818: in.close();
1919: }
2020: }
数据的存储和恢复
为了输出可供另一个“流”恢复的数据,我们需要使用DataOutputStream
写入数据,然后使用DataInputStream恢复数据。当然这些流可以是任何形式(这里的形式其实就是我们前面说过的流的两端的类型),比如文件。
1public class StoringAndRecoveringData {
2 public static void main(String[] args) throws IOException {
3 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
4 out.writeDouble(3.1415926);
5 out.writeUTF("三连走起");
6 out.writeInt(125);
7 out.writeUTF("点赞加关注");
8 out.close();
9
10 DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
11 System.out.println(in.readDouble());
12 System.out.println(in.readUTF());
13 System.out.println(in.readInt());
14 System.out.println(in.readUTF());
15 in.close();
16 }
17}
输出结果:
13.1415926
2三连走起
3125
4点赞加关注
需要注意的是我们使用writeUTF()和readUTF()来写入和读取字符串。