Java压缩、序列化学习笔记

Java压缩、序列化

Java I/O类库中的类支持读写压缩格式的数据流。你可以用它们对其他的I/O类进行封装,以提供压缩功能。

这些类不是从Reader个Writer类派生而来的,而是属于InputStream和OutputStream继承层次结构的一部分。这样做是因为压缩类库是按字节方式而不是字符方式处理的。不过有时我们可能会被迫要混合使用两种类型的数据流(注意我们可以通过使用InputStreamReader和OutputStreamReader在两种类型见方便的切换)。

接下来会介绍压缩的流程和细节。

用GZIP进行简单压缩

GZIP接口很简单,因此如果我们只是想对单个数据流进行压缩,那么它可能会是比较合适的选择,下面是一个简单的例子:


/**
 * Created by Mr.W on 2017/11/12.
 */
public class GZIPTestClass {
    public static void main(String[] args) {
        BufferedReader reader = null;
        DataOutputStream out = null;
        DataInputStream in = null;
        try {
            reader = new BufferedReader(new FileReader("./src/com/stupidzhe/jdklearning/io/file-test/file2.c"));
            out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream("./src/com/stupidzhe/jdklearning/io/file-test/file2.gzip")));

            String s;
            while (null != (s = reader.readLine())) {
                out.writeUTF(s);
                out.writeUTF("\n");
                out.flush();
            }
            reader.close();
            out.close();

            in = new DataInputStream((new GZIPInputStream(new FileInputStream("./src/com/stupidzhe/jdklearning/io/file-test/file2.gzip"))));
            try {
            while(null != (s = in.readUTF())) {
                System.out.print(s);
            }
            } catch (EOFException ignored) {}

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != reader) {
                    reader.close();
                }
                if (null != out) {
                    out.close();
                }
                if (null != in) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
----------output----------
你好,世界!
我爱你!
--------------------------

压缩类的使用非常直观:直接将输出流封装到GZIPOutputStream或ZIPOutputStream中,将输入流封装到GZIPInputStream或ZIPInputStream中。其他操作跟通常的IO读写流程相同。

通过Zip多文件保存

通过ZipInputStream与ZipOutputStream实现文件夹压缩,代码如下:

/**
 * Created by Mr.W on 2017/11/12.
 */
public class ZIPTestClass {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.exit(-1);
            return;
        }
        final String zipName = args[0];
        try {
            File directFile = new File(zipName);
            if (directFile.exists()) {
                zip(directFile, directFile.getParent() + "/" + directFile.getName() + ".zip");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void zip(File directFile, String sourceFileName) throws IOException {
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(sourceFileName));

        zip(out, directFile, directFile.getName());
        out.close();
    }

    private static void zip(ZipOutputStream out, File sourceFile, String base) throws IOException {
        if (sourceFile.isDirectory()) {
            File[] files = sourceFile.listFiles();
            if (null != files && files.length > 0) {
                out.putNextEntry(new ZipEntry(base + "/"));
                for (File cFile : files) {
                    if (cFile.getName().equals(".DS_Store")) {
                        continue;
                    }
                    zip(out, cFile, base + "/" + cFile.getName());
                }
            }
        } else if (sourceFile.isFile()) {
            out.putNextEntry(new ZipEntry(base));
            FileInputStream fileInputStream = new FileInputStream(sourceFile);
            int content;
            while (-1 != (content = fileInputStream.read())) {
                out.write(content);
            }
            fileInputStream.close();
        }
    }
}

Java档案文件

Zip格式也被应用于JAR(Java ARchive)文件格式中。这种文件格式也就像Zip一样,可以将一组文件压缩到一个文件中。同Java中其他东西一样,JAR文件也是跨平台的,所以不必担心跨平台的问题。声音和图像文件可以像类文件一样被包含在其中。

JAR文件非常有用,尤其是在涉及因特网应用中,如果不采用JAR文件,Web浏览器在下载构成一个应用的所有文件时必须重复多次Web请求;而且所有这些文件都是未经压缩的。 如果把所有文件压缩成一个文件,那么Web浏览器就只需要向远程服务器发送一次请求即可。同时,由于采用了压缩技术,可以使传输时间缩短。另外,处于安全的考虑,JAR文件中的每个条目都可以加上数字化签名。

一个JAR文件由一组压缩文件构成,同时还有一张描述了所有这些文件的“文件清单”(可自行创建文件清单,也可由jar程序自动生成)。在JDK文档中,可以找到与jar文件清单相关的更多资料。

jar 命令语法如下:

jar {ctxu}[vfm0M] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...
其中 {ctxu} 是 jar 命令的子命令,每次 jar 命令只能包含 ctxu 中的一个,它们分别表示:

-c 创建新的 JAR 文件包

-t 列出 JAR 文件包的内容列表

-x 展开 JAR 文件包的指定文件或者所有文件

-u 更新已存在的 JAR 文件包 (添加文件到 JAR 文件包中)

[vfm0M] 中的选项可以任选,也可以不选,它们是 jar 命令的选项参数

-v 生成详细报告并打印到标准输出

-f 指定 JAR 文件名,通常这个参数是必须的

-m 指定需要包含的 MANIFEST 清单文件

-0 只存储,不压缩,这样产生的 JAR 文件包会比不用该参数产生的体积大,但速度更快

-M 不产生所有项的清单(MANIFEST〕文件,此参数会忽略 -m 参数
[jar-文件] 即需要生成、查看、更新或者解开的 JAR 文件包,它是 -f 参数的附属参数

[manifest-文件] 即 MANIFEST 清单文件,它是 -m 参数的附属参数

[-C 目录] 表示转到指定目录下去执行这个 jar 命令的操作。它相当于先使用 cd 命令转该目录下再执行不带 -C 参数的 jar 命令,它只能在创建和更新 JAR 文件包的时候可用。在jdk1.6中,我实验该命令失败。

文件名 ... 指定一个文件/目录列表,这些文件/目录就是要添加到 JAR 文件包中的文件/目录。如果指定了目录,那么 jar 命令打包的时候会自动把该目录中的所有文件和子目录打入包中。

以下命令就是创建一个名称为hello.jar的JAR文件。

jar cf hello.jar *.class

以下命令就是列出名称为hello.jar的JAR文件的目录表

jar tf hello.jar

对象序列化

当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这样做肯定是有意义的,但是依旧存在某些情况,如果对象能够在程序不运行的情况下仍然存在并保存信息,那将非常有用。这样,在下次运行程序的时候,该对象将被重建并且拥有的信息跟程序上次运行时它所拥有的信息相同。

你可以通过将信息写入文件或者数据库来达到相同的效果,但是在使万物都成为对象的精神中,如果能够将对象声明为是持久性,并为我们处理掉所有细节,那将会显得十分方便。

Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复到原来的对象。也就是说,可以在运行Window系统的计算机上创建一个对象,将其序列化,通过网络讲它发送给一台运行UNIX系统的计算机,然后准确地重新组装,而不用担心数据在不同操作系统上的表示会不同,也不用关心字节的顺序或者其他任何细节。

对象的序列化是非常有趣的,因为利用它可以实现轻量级持久性(lightweight persistence)。“持久性”意味着一个对象的生存周期不再取决于程序,它可以生存于程序的调用之间。

之所以称为“轻量化”,是因为不能用某种“persistent”关键词来简单定义一个对象,并让系统自动维护其他细节问题,相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize)。

只要对象实现了Serializable接口,对象的序列化处理就会非常简单。当序列化的概念被加入语言中时,许多标准库类都发生了改变,以便具备序列化特性----其中包括所有基本数据类型的封装器、所有容器类以及许多其他的东西。甚至Class对象也可以被序列化。

要序列化对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,这时候只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象化序列是基于字节的,因此要使用InputStream和OutputStream继承层次结构)。要反向进行giant过程,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。

对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象锁包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组以及成员对象。

下面是简单的序列化对象和反序列化对象的例子:


public class SerializableTestClass implements Serializable {

    public SerializableTestClass(String content) {
        this.content = content;
    }

    private String content;

    public static void main(String[] args) {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("./src/com/stupidzhe/jdklearning/io/file-test/file1.c");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(new SerializableTestClass("content = \"test content\""));
            objectOutputStream.flush();
            objectOutputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./src/com/stupidzhe/jdklearning/io/file-test/file1.c");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            System.out.println(((SerializableTestClass)objectInputStream.readObject()).content);
            objectInputStream.close();
            fileInputStream.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

序列化的控制

默认的序序列化机制并不难操纵,然而,如果有特殊的需要又该怎么办?例如,也许要考虑特殊的安全问题,而且你不希望对象的某部分被序列化;或者对象被还原以后,某子对象需要重新被创建,从而不必将该子对象序列化。

在这些特殊情况下,可以通过实现Externalizable接口----代替实现Serializabe接口----来对序列化过程进行控制。这个Externalizable接口继承自Serializable接口,同时增添两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用

下面就是使用Externalizable接口的例子:

public class SerializableTestClass implements Externalizable {

	// 反序列化时,会调用无参构造器(说明实例化了一个新对象?)
    public SerializableTestClass () {
    }
    public SerializableTestClass(String content) {
        this.content = content;
    }

    private String content;

    public static void main(String[] args) {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("./src/com/stupidzhe/jdklearning/io/file-test/file1.c");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(new SerializableTestClass("content = \"test content\""));
            objectOutputStream.writeObject(new SerializableTestClass("content = \"test content1\""));
            objectOutputStream.flush();
            objectOutputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./src/com/stupidzhe/jdklearning/io/file-test/file1.c");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            System.out.println(((SerializableTestClass)objectInputStream.readObject()).content);
            System.out.println(((SerializableTestClass)objectInputStream.readObject()).content);
            objectInputStream.close();
            fileInputStream.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
    	// 通过选择性写入对象
        out.writeObject(content);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.content = (String) in.readObject();
    }
}

转载于:https://my.oschina.net/StupidZhe/blog/1572435

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值