myZip自制压缩软件(基于哈夫曼树完成)----小吐半升血,为教小萌新 (。→‿←。) (有手就行,不会打我)

演示视频链接:可以点进去看看撒!
在这里插入图片描述

原理:

根据UTF-8编码方式,字符往往占据多个字节,如果我们将其用二进制位表示出来(同时也可借助二进制位译码),将大大节省空间
而由于我们借助出现次数,建立哈夫曼树,来得到代表的二进制码(可以小小思考一下,如何得到呢?),使得出现频率越高,代表的二进制码越短,使得压缩效果更加优越

Created with Raphaël 2.3.0 得到文件的文本 借助HashMap,key存字符,value存出现次数,便于得到权值 借助于哈夫曼树(依据权值), 得到字符编码 编码写入压缩文件(注意同时写入所需的hashMap) 读取出压缩的编码内容,根据hashMap译码出来,写到解压文件
知识点基本原理
文件输入输出利用输入输出流完成(OutputStream/InputStream),均是按字节进行读写操作的,且如何写如何读
哈希表利用键,查找值的一种链表数组,可以看看我写的文章: hashMap
哈夫曼树按照各数据的权值进行建树,即两个权值最小的值,合并成根节点为权值之和的树,然后将父结点的权值放进剩余数据中,不断循环,直至,只存在一个数
字符串拼接底层逻辑是,每次拼接,都要创建一个 StringBuilder 对象,然后执行Copy方法(遍历该字符串)赋值于该对象,由其进行拼接后,再返回值给字符串。

实现:

压缩方法:

1.读文档,建hashMap<>:

值得注意,每当我们想要使用输入输出流时,注意抛出异常。
同时,可以采取代码段所写方法得到文本。
最后,hashMap的使用 ,也十分简单,只需先遍历字符串的字符数组,查询是否存在该字符(键),然后修改权值(出现次数),即可存入。存入后,使用Set映射出来,建立我们的初始节点

//读取待压缩文件
    private String readWaitCop(File wait) throws IOException {
        //读取文件内容
        FileInputStream fip = new FileInputStream(wait);
        byte[] buf = new byte[fip.available()];
        //**重中之重,一定要先读文本内容,填充这个byte数组,不然出大乱子——————大冤种的劝告 ~_~。
        /**
         * 在文件读写里:string 和 byte 可以互换,达到input/output都成为可阅读文本
         * 写文件:fop.write(String.getBytes());
         * 读文件: fip.read(bytes[]);
         *         new String(bytes[])
         */
        fip.read(buf);
        return new String(buf);
    }
    
     //获取权值和内容,构建初始节点
    private String initSet(File wait) throws IOException {
        String str = readWaitCop(wait);
        HashMap<Character,Integer> hashMap = new HashMap<Character,Integer>();
        char[] chars = str.toCharArray();
        for (char cc:chars) {
            String sinStr = String.valueOf(cc);
            if(hashMap.containsKey(cc)){
                hashMap.put(cc, hashMap.get(cc) + 1);
            }else{
                hashMap.put(cc,1);
            }
        }
        Set<Map.Entry<Character, Integer>> entrys = hashMap.entrySet();
        for(Map.Entry<Character, Integer> entry:entrys) {
            Node node = new Node(null,null, entry.getKey(), entry.getValue(), null);
            arrayList.add(node);
        }
        return str;

2.搭哈夫曼树:

首先我们要明白节点建立:可以看看我的内部Node类:
左右节点方便建树,权值用来比较,road可以记录所做的编码,content记录字符内容

//内部节点类
    public class Node{
        private Node left;
        private Node right;
        //出现频率(权值)
        private int num;
        //记载路径
        private String road;
        //记载内容
        private char content;
        public Node(Node left, Node right,char content, int num,String road) {
            this.left = left;
            this.right = right;
            this.content = content;
            this.num = num;
            this.road = null;
        }
    }

思路于原理处的哈夫曼树一致,可以看看我是如何实现的
值得注意下,每次找到最小权值,都需要将该节点删除,防止重复比较
同时,剩下最后最后一个节点时,树已经搭建完成,记录该根节点。
由于该节点的ArrayList一直使用,故也要记得清除最后一个节点。

//搭建树
    private void setTree(){
        while(arrayList.size() != 1){
            //最小权值结点
            Node bro1 = Min();
            Node bro2 = Min();
            Node father = new Node(bro1,bro2,'0',bro1.num + bro2.num,null);
            arrayList.add(father);
        }
        root = arrayList.get(0);
        arrayList.remove(0);
    }
    //找到最小权值
    private Node Min(){
        int minIndex = 0;
        for (int i = 0; i < arrayList.size(); i++) {
            if(arrayList.get(minIndex).num > arrayList.get(i).num){
                minIndex = i;
            }
        }
        Node node = arrayList.get(minIndex);
        arrayList.remove(minIndex);
        return node;
    }

3.得到字符编码,存入hashMap<编码,字符>:

这里说的字符编码,就是节点的road,该如何实现呢?
其实,我们可以遍历哈夫曼树,往左走即为‘0’,往右走即为‘1’,这样就解决了编码,而我们之前借助的权值大小,使得一些常用数据层数更少,故编码更短,达到压缩效果

以下代码得到编码:
存有实际数据的节点,其实就是叶节点,即该节点不具有子结点,这个可以自行思考一下原因。
而遍历树其实有前序遍历,后序遍历等多种方法,而我写的采用层序遍历,可以避开递归,简化思路。
但值得注意的是,该Queue队列为我自己写的,所以可能与常规的不同。
在这里插入图片描述
可以解释一下层序遍历:由于哈夫曼树是完美二叉树,故每个节点的子结点要么没有,要么两个,所以我们可以利用Queue数据结构,pop一个节点时,push两个节点,再由于FIFO机制,达到遍历效果。
然后,至于为什么又存入hashMap中,属于重要伏笔,之后进行讲解。

//得到路径
    private void setRoad() throws IOException {
        Queue<Node> queue = new Queue<>();
        queue.enqueue(root);
        root.road = "";
        while (!queue.isEmpty()){
            if(queue.peak().left != null) {
                queue.enqueue(queue.peak().left);
                queue.peak().left.road = queue.peak().road + '0';
            }
            if(queue.peak().right != null){
                queue.enqueue(queue.peak().right);
                queue.peak().right.road = queue.peak().road + '1';
            }else{
                arrayList.add(queue.peak());
            }
            queue.dequeue();
        }
        //得到readMap
        for (int i = 0; i < arrayList.size(); i++) {
            readMap.put(arrayList.get(i).road,arrayList.get(i).content);
        }
    }

4.据hashMap<Character,String>译码,得到0/1字符串:

这里比较简单,将我们读出的文本遍历一遍,根据hashMap将字符换成其对应的0/1编码,得到一个新的字符串即可,但是要明白,由于1 byte = 8 bit,故我们需要补足成8的倍数,不然转换成byte写入文件会出现差错,同时补完后,也需在字符串头添加补足位数。

 		//存入内容
        char[] chars = comContent.toCharArray();
        //先将comContent清空,得到有效位数+二进制字符串
        /**
         * 这是压缩时最耗时间的部分,可以考虑换成string来算,因为这个比char的hashcode算的快,
         * 因为算hashCode从map取值,重复过多了
         */
        //注意清空啊,嘚吧!
        comContent = "";
        StringBuilder builder = new StringBuilder();
        for (char cc:chars) {
                //hashMap.get可查看源码,返回值为V;
                builder.append(hashMap.get(cc));
        }

        //存入补足编码
        int num = 8 - builder.length() % 8;
        for (int i = 0; i < num; i++) {
            builder.append('0');
        }
        //存入补的位数,直接存数字,eg:……+"num"。
        byte b = (byte) num;
        //运用 StringBuilder ,出现了heap溢出
        builder.insert(0,String.valueOf(b));
        comContent = builder.toString();

值得注意一点:comContent是借助initSet(wait)【初始化构建节点】得到,同时也要记住将其转化为char【】数组后,要记住将其清空,得到编码后的字符串

5.(0/1字符串->byte[])写入文档(注意hashMap<编码,字符>写入):

这里主要讲讲,0/1字符串转换成byte的方法,读到‘0’,则byte 往左移位,读到‘1’,则byte 往左移位,且 + 1,读八次即可构成一个btye值,写入到byte【】里。
而我们的byte【】首位,存的是最后一字节的几位0/1有效,便于解压过程,
这样我们就将文本转化为了byte【】数组,符合文件写入的规定了。

	//写入到文件的具体方法
    private void outputStream(String comContent,File after) throws IOException {
        //得到要写入字符串的字符
        char[] chars = comContent.toCharArray();
        //得到需要写多少byte,初始位置存num
        byte[] bytes = new byte[(chars.length - 1) / 8 + 1];
        //得到最后一个字节中的几位有效
        int num = 8 - ((int) chars[0] - 48);
        //(byte)转型,可以得到int num的最后一个字节
        bytes[0] = (byte) (num & 255);
        //第一位是存储需要丢弃几个数字,所以可以从下标1开始
        for (int i = 1; i < bytes.length; i++) {
            byte b = 0;
            int bb = i - 1;
            for (int j = 1; j <= 8; j++) {
                if(chars[bb * 8 + j] == '0'){
                    b = (byte) (b << 1);
                }else{
                    b = (byte) (b <<1 + 1);
                }
                bytes[i] = b;
            }
        }
        //开写开写
        FileOutputStream fop = new FileOutputStream(after);
        /**
         * 可以去仔细研究:::
         * 写入readMap,只能用ObjectInputStream,写入任何类型
         * 但要注意,该类型要实现java.io.Serializable,使其“序列化”
         */
        ObjectOutputStream foop = new ObjectOutputStream(fop);
        //写入任何类型
        foop.writeObject(readMap);
        fop.write(bytes);
    }

这里我们需要强调一下 3.步骤的 伏笔了,由于编码规则并不一样,所以解压读出来,需要一个可供参照的译码表,就是当时我们所存的hashMap。如果缺乏这个表格,可以想象二战时期截获电报后,却一脸懵逼,犹如废止,不知所言。而译码表借助ObjectOutputStream(都基于File I/O Stream) 写入,其功能强大,可以写入任何实现了Serializable接口的类型。
如果对其感到好奇,可以去查询一下。

解压方法:

1.(byte[]->0/1字符串)过程:

注意得到移位是从7->0不断移位的,才可以按照顺序得到0/1字符串,可以自己思考一下。

for (int i = 1; i < bytes.length; i++) {
            //将byte转换为字符串
            for (int j = 7; j >= 0 ; j--) {
                int num = bytes[i];
                if(((num >> j) & 1) == 0){
                    codeBulider.append('0');
                }
                else{
                    codeBulider.append('1');
                }
            }
        }

2.根据所读hashMap<编码,字符>,(0/1字符串->String),得到真正内容字符串

读出我们的hashMap<编码,字符>译码表

        FileInputStream fip = new FileInputStream(wait);
        // Object的输入输出流都是基于file输入输出流
        ObjectInputStream fiop = new ObjectInputStream(fip);

开始译码,用sinBuilder(单个字符)拼接一个0/1,再查找有无该key(编码),循环往复,直至存在该编码,
再用contentBuilder拼接hashMap通过该key,查找出的value(字符),浏览完成,我们也就译码完成了。在这里插入代码片

for (char cc:chars) {
            sinBuilder.append(cc);
            if(readMap.containsKey(sinBuilder.toString())){
                contentBuilder.append(readMap.get(sinBuilder.toString()));
                sinBuilder.delete(0, sinBuilder.length());
            }
        }
        content = contentBuilder.toString();

3.写入到解压文件中:

记住写入String时,可以使用String.getBytes()方法。

		FileOutputStream fop = new FileOutputStream(after);
        fop.write(content.getBytes());

界面设计:

外观设计:

无非是建立窗体,然后一些组件而已,代码在源代码处,十分简单。

建立ActionListener

我们可以通过内部监听器类,从而可以随意调用所需数据,而不需要想方设法传过去。
比如,这里我们就可以直接使用文本框的文本,不需要先得到其地址之后,才可以操作。
注意:内部监听器类的结尾处要有分号,可以把这理解为一个长语句。

ActionListener ac = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Color tuWhite = new Color(238,238,238);
                g.setColor(tuWhite);
                g.fillRect(300,550,400,400);
                g.drawImage(starBuf,300,550,400,400,null);
                long start = System.currentTimeMillis();
                String btname = e.getActionCommand();
                File wait = new File(jt1.getText());
                File after = new File(jt2.getText());
                if(btname.equals("压缩")){
                    try {
                        hf.copCode(wait,after);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }else{
                    try {
                        hf.unPack(wait,after);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    } catch (ClassNotFoundException ex) {
                        ex.printStackTrace();
                    }
                }
                long end = System.currentTimeMillis();
                double time = (end - start) / 1000;
                System.out.println(btname + ":  "+ time + "  S");
                g.setColor(tuWhite);
                g.fillRect(300,550,400,400);
                g.drawImage(finBuf,300,550,400,400,null);
            }
        };

添加运行图片标志

点击按钮时,便可以绘制我们的加载条图片,结束后绘制印章成功图片,这些代码都在上方。

优化

提升速度

如果你使用的原始字符串拼接过程,你会发现巨慢无比,已经到了无法忍受的地步,在这里我提出我自己的解决方案。

1.找最小权值:

解决方案:如果你使用的是冒泡排序等低级排序,其效率低下,在字符众多的情况下,显然是不适用的。可以使用快速排序,或者像我一样,直接找到最小值的下标,均可以大大提升速率

2.字符串拼接耗时:

解决方案:如果直接使用 字符串的 ” + “ 进行拼接,依据其底层原理,我们可以想一下有多浪费时间,每连接一次都需要遍历复制,再进行拼接,【时间复杂度为(n^2)】。故我们只需要创建一个StringBuilder对象,由其拼接完所有内容,再转换成String类型。
其效果十分显著,当时不足1M文件也要200多秒,如今100M也只需要6秒左右。
有兴趣可以去看一下,String的append方法。

提升容量

StringBuilder存在极限

当使用300M的文本压缩时,会报heap overflow 的异常,在查询了StringBuilder的源代码后 ,发现它底层为char【】数组,而其长度为int类型是有限的,故文本过多时会堆内存溢出
解决方案:我们可以创建StringBuilder【】的数组,规定连接多少字符后,转到下一个StringBuilder,最后再进行拼接,如此便可进行容量扩充。
这只是个人思路,还没有完成,希望有志之士可以操作操作。

打包成jar文件:如图

jar打包流程

源代码(含有所有代码):

1.界面

package fcj1028;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Ui extends JFrame {
    public void ui() throws IOException {
        Huffman_Compress hf = new Huffman_Compress();
        this.setTitle("压缩软件");
        this.setSize(1000,1000);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
        JLabel jl1 = new JLabel("待处理文件:");
        jl1.setFont(new java.awt.Font("Dialog", 1, 30));
        JLabel jl2 = new JLabel("处理后文件:");
        jl2.setFont(new java.awt.Font("Dialog", 1, 30));
        JTextField jt1 = new JTextField();
        Dimension dim1 = new Dimension(800,100);
        Dimension dim2 = new Dimension(400,150);
        jt1.setFont(new java.awt.Font("Dialog", 1, 30));
        JTextField jt2 = new JTextField();
        jt2.setFont(new java.awt.Font("Dialog", 1, 30));
        JButton jb1 = new JButton("压缩");
        jb1.setFont(new java.awt.Font("Dialog", 1, 30));
        JButton jb2 = new JButton("解压");
        jb2.setFont(new java.awt.Font("Dialog", 1, 30));
        jt1.setPreferredSize(dim1);
        jt2.setPreferredSize(dim1);
        jb1.setPreferredSize(dim2);
        jb2.setPreferredSize(dim2);
        this.add(jl1);
        this.add(jt1);
        this.add(jl2);
        this.add(jt2);
        this.add(jb1);
        this.add(jb2);
        this.setLayout(new FlowLayout());
        this.setVisible(true);
        //先可视化,再得到画笔
        Graphics g = this.getGraphics();
        File starPic = new File("C:\\Users\\27259\\Pictures\\java_pic\\9df86687_E848203_9ea3a43f-removebg-preview.png");
        BufferedImage starBuf = ImageIO.read(starPic);
        File finPic = new File("C:\\Users\\27259\\Pictures\\java_pic\\R-C-removebg-preview(1).png");
        BufferedImage finBuf = ImageIO.read(finPic);
        ActionListener ac = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Color tuWhite = new Color(238,238,238);
                g.setColor(tuWhite);
                g.fillRect(300,550,400,400);
                g.drawImage(starBuf,300,550,400,400,null);
                long start = System.currentTimeMillis();
                String btname = e.getActionCommand();
                File wait = new File(jt1.getText());
                File after = new File(jt2.getText());
                if(btname.equals("压缩")){
                    try {
                        hf.copCode(wait,after);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }else{
                    try {
                        hf.unPack(wait,after);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    } catch (ClassNotFoundException ex) {
                        ex.printStackTrace();
                    }
                }
                long end = System.currentTimeMillis();
                double time = (end - start) / 1000;
                System.out.println(btname + ":  "+ time + "  S");
                g.setColor(tuWhite);
                g.fillRect(300,550,400,400);
                g.drawImage(finBuf,300,550,400,400,null);
            }
        };
        jb1.addActionListener(ac);
        jb2.addActionListener(ac);
    }

    public static void main(String[] args) throws IOException {
        new Ui().ui();
    }
}

```java

```java

```java

2.压缩软件

package fcj1028;

import Algorithm.Linear.Queue;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Huffman_Compress {
    //写到压缩文件里,方便之后,文件读出内容(避免程序结束,所存hashMap消失)
    private HashMap<String,Character> readMap = new HashMap<>();
    //根节点,便于寻找
    private Node root;
    /**哈夫曼树:a.先对节点排序保存到List中
    b.取出List中最小的两个节点,让它们的权值累加,保存新的父节点中
    c.让最小两个节点作为左右子树,把父节点添加到List中重新排序
    d.直到List只有一个节点
   3.设置叶子节点的编码,往左编码为1 往右编码为0
    统计每个叶子节点对应的编码
     */
    //用途:节省空间存储内容
    //原理:使用路径编码即可代表字符,而且越常用字符编码越少
    //用来存节点
    private ArrayList<Node> arrayList = new ArrayList<Node>();

    //内部节点类
    public class Node{
        private Node left;
        private Node right;
        //出现频率(权值)
        private int num;
        //记载路径
        private String road;
        //记载内容
        private char content;
        public Node(Node left, Node right,char content, int num,String road) {
            this.left = left;
            this.right = right;
            this.content = content;
            this.num = num;
            this.road = null;
        }
    }
    //获取权值和内容,构建初始节点
    private String initSet(File wait) throws IOException {
        String str = readWaitCop(wait);
        HashMap<Character,Integer> hashMap = new HashMap<Character,Integer>();
        char[] chars = str.toCharArray();
        for (char cc:chars) {
            String sinStr = String.valueOf(cc);
            if(hashMap.containsKey(cc)){
                hashMap.put(cc, hashMap.get(cc) + 1);
            }else{
                hashMap.put(cc,1);
            }
        }
        Set<Map.Entry<Character, Integer>> entrys = hashMap.entrySet();
        for(Map.Entry<Character, Integer> entry:entrys) {
            Node node = new Node(null,null, entry.getKey(), entry.getValue(), null);
            arrayList.add(node);
        }
        return str;
    }
    //读取待压缩文件
    private String readWaitCop(File wait) throws IOException {
        //读取文件内容
        FileInputStream fip = new FileInputStream(wait);
        /**
         * V1.0:一开始的想法是,得到的是byte【】数组,转换为字符串
         * 其中借用了fip.available(),可以返回文件的字节数
         * 但是问题在于,这样的话byte[]数组并不能转换成字符串内容,反而得到的是其长度
         * 原因:当时也忘记了用fip.read(bytes)存放内容,所以读出长度十分正常
         * 故,我们的想法可以是用将每个字节转换为char。
         * V2.0:由于数字和中文编码并不一样,所以这个方法也要淘汰
         * V3.0:在得到bytes[]后,可以使用String()方法,规定好长度自动会帮我们转型
         * 值得注意的是:String.valueOf()会得到乱码
         */
        byte[] buf = new byte[fip.available()];
        //**重中之重,一定要先读文本内容,填充这个byte数组,不然出大乱子——————大冤种的劝告 ~_~。
        /**
         * 在文件读写里:string 和 byte 可以互换,达到input/output都成为可阅读文本
         * 写文件:fop.write(String.getBytes());
         * 读文件: fip.read(bytes[]);
         *         new String(bytes[])
         */
        fip.read(buf);
        return new String(buf);
    }
    //搭建树
    private void setTree(){
        while(arrayList.size() != 1){
            //最小权值结点
            Node bro1 = Min();
            Node bro2 = Min();
            Node father = new Node(bro1,bro2,'0',bro1.num + bro2.num,null);
            arrayList.add(father);
        }
        root = arrayList.get(0);
        arrayList.remove(0);
    }
    //找到最小权值
    private Node Min(){
        int minIndex = 0;
        for (int i = 0; i < arrayList.size(); i++) {
            if(arrayList.get(minIndex).num > arrayList.get(i).num){
                minIndex = i;
            }
        }
        Node node = arrayList.get(minIndex);
        arrayList.remove(minIndex);
        return node;
    }
    //得到路径
    /**
     * 首先表扬一下自己,通过不断输出语句还是找到了bug
     * 这里的问题并非是树连接失败,而是我(nt)的忘了把队列里的节点叉出去,导致死循环
     */
    private void setRoad() throws IOException {
        Queue<Node> queue = new Queue<>();
        queue.enqueue(root);
        root.road = "";
        while (!queue.isEmpty()){
            if(queue.peak().left != null) {
                queue.enqueue(queue.peak().left);
                queue.peak().left.road = queue.peak().road + '0';
            }
            if(queue.peak().right != null){
                queue.enqueue(queue.peak().right);
                queue.peak().right.road = queue.peak().road + '1';
            }else{
                arrayList.add(queue.peak());
            }
            queue.dequeue();
        }
        //得到readMap
        for (int i = 0; i < arrayList.size(); i++) {
            readMap.put(arrayList.get(i).road,arrayList.get(i).content);
        }
    }
    //写入到文件的具体方法
    private void outputStream(String comContent,File after) throws IOException {
        //得到要写入字符串的字符
        char[] chars = comContent.toCharArray();
        //得到需要写多少byte,初始位置存num
        byte[] bytes = new byte[(chars.length - 1) / 8 + 1];
        //得到最后一个字节中的几位有效
        int num = 8 - ((int) chars[0] - 48);
        //(byte)转型,可以得到int num的最后一个字节
        bytes[0] = (byte) (num & 255);
        //第一位是存储需要丢弃几个数字,所以可以从下标1开始
        for (int i = 1; i < bytes.length; i++) {
            byte b = 0;
            /**
             * V1.0:使用byte存储,思路是想用位运算使得效率更高,但是发现不是想要的效果,存入的都是0
             * 原因:暂时还不清楚,可去看这篇文章
             * https://blog.csdn.net/weixin_39775428/article/details/114176698
             * 所以,现在还是用 * 2.
             */
            int bb = i - 1;
            for (int j = 1; j <= 8; j++) {
                if(chars[bb * 8 + j] == '0'){
                    b = (byte) (b * 2);
                }else{
                    b = (byte) (b * 2 + 1);
                }
                /**
                 * 值得注意:byte表示范围为 -128~127,一旦超出127,会自动转换
                 * 但是读取时就需要主要判断正负,得到第一位了,问题在于补码中的0,存在 10000000,00000000两种情况,需要特殊判断
                 */
                bytes[i] = b;
            }
        }
        //开写开写
        FileOutputStream fop = new FileOutputStream(after);
        /**
         * 可以去仔细研究:::
         * 写入readMap,只能用ObjectInputStream,写入任何类型
         * 但要注意,该类型要实现java.io.Serializable,使其“序列化”
         */
        ObjectOutputStream foop = new ObjectOutputStream(fop);
        //写入任何类型
        foop.writeObject(readMap);
        fop.write(bytes);
    }
    //得到压缩后的编码(除了补足八位,构成byte存入),还需要记录补了几个),并写入到文件
    public void copCode(File wait,File after) throws IOException {
        String comContent = initSet(wait);
        setTree();
        setRoad();
        //利用HashMap存好字符的数据编码,即路径
        HashMap<Character,String> hashMap = new HashMap<>();
        for (Node node:arrayList) {
            hashMap.put(node.content, node.road);
        }
        //存入内容
        char[] chars = comContent.toCharArray();
        //先将comContent清空,得到有效位数+二进制字符串
        /**
         * 这是压缩时最耗时间的部分,可以考虑换成string来算,因为这个比char的hashcode算的快,
         * 因为算hashCode从map取值,重复过多了
         */
        //注意清空啊,嘚吧!
        comContent = "";
        /**
         *     神之一手 ———— 郝为高
         *     由于每次字符串的连接,都会先创建 StringBuilder builder对象,浪费大量时间,不如直接一步到位,先创建出来
         */
        StringBuilder builder = new StringBuilder();
        for (char cc:chars) {
                //hashMap.get可查看源码,返回值为V;
                builder.append(hashMap.get(cc));
        }

        //存入补足编码
        int num = 8 - builder.length() % 8;
        for (int i = 0; i < num; i++) {
            builder.append('0');
        }
        //存入补的位数,直接存数字,eg:……+"num"。
        byte b = (byte) num;
        //运用 StringBuilder ,出现了heap溢出
        builder.insert(0,String.valueOf(b));
        comContent = builder.toString();
        builder.delete(0,builder.length());
        outputStream(comContent,after);
    }

    //解压压缩后的文本
    public void unPack(File wait,File after) throws IOException, ClassNotFoundException {
        //开始读出readMap(sb,你自己要注意你是用的那个文件啊啊啊啊啊!!!!!)
        FileInputStream fip = new FileInputStream(wait);
        // Object的输入输出流都是基于file输入输出流
        ObjectInputStream fiop = new ObjectInputStream(fip);
        HashMap<String,Character> readMap = (HashMap<String, Character>) fiop.readObject();
        //将内容都写到byte[]数组里
        byte[] bytes = new byte[fip.available()];
        fip.read(bytes);
        /**
         *     神之一手 ———— 郝为高
         *     由于每次字符串的连接,都会先创建 StringBuilder builder对象,浪费大量时间,不如直接一步到位,先创建出来
         */
        //存储编码后的0/1串
        String copCode = "";
        StringBuilder codeBulider = new StringBuilder();
        for (int i = 1; i < bytes.length; i++) {
            //将byte转换为字符串
            for (int j = 7; j >= 0 ; j--) {
                int num = bytes[i];
                if(((num >> j) & 1) == 0){
                    codeBulider.append('0');
                }
                else{
                    codeBulider.append('1');
                }
            }
        }
        copCode = codeBulider.toString();
        //删除补足‘0’字符串
        copCode = copCode.substring(0,copCode.length() - bytes[0] - 1);
        //为了提高效率的到str的char[]数组
        char[] chars = copCode.toCharArray();
        //得到全体字符
        String content = "";
        /**
         *     神之一手 ———— 郝为高
         *     由于每次字符串的连接,都会先创建 StringBuilder builder对象,浪费大量时间,不如直接一步到位,先创建出来
         */
        StringBuilder sinBuilder = new StringBuilder();
        StringBuilder contentBuilder = new StringBuilder();
        for (char cc:chars) {
            sinBuilder.append(cc);
            if(readMap.containsKey(sinBuilder.toString())){
                contentBuilder.append(readMap.get(sinBuilder.toString()));
                sinBuilder.delete(0, sinBuilder.length());
            }
        }
        content = contentBuilder.toString();
        FileOutputStream fop = new FileOutputStream(after);
        fop.write(content.getBytes());
    }
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
文件名:MyZip.dll 函 数: ************************************************** *** *** *** 压缩文件 *** *** *** ************************************************** 1. Function MyZip_AddFile(SrcFile,ZipFile:pchar):integer;stdcall; 功能 : 将文件SrcFile添加到压缩文档ZipFile 参数 : SrcFile 待压缩文件(全路径) Zipfile 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) 2. Function MyZip_AddDirectory(SrcPath,ZipFile:pchar):integer;stdcall; 功能 : 将目录SrcPath里的所有文件(子目录)添加到压缩文档ZipFile 参数 : SrcPath 待压缩目录(全路径) Zipfile 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) ************************************************** *** *** *** 解压缩文件 *** *** *** ************************************************** 1. Function MyZip_ExtractFileAll(ZipFile,PathName:pchar):integer;stdcall; 功能 : 将ZipFile中包含的所有文件解包到文件夹PathName 参数 : ZipFile 压缩文件(全路径) PathName 文件输出路径(如果不存在,则自动创建该目录) 返回 : 0 解包的文件数量 说明 : 同名文件将自动被替换(overwrite) 2. Function MyZip_ExtractFile(ZipFile,srcName,DstName:pchar):integer;stdcall; 功能 : 从ZipFile中将由SrcName指定的文件解包到由DstName指定的目标文件 参数 : Zipfile 压缩文件(全路径) SrcName 需要解包的文件(不包含路径) DstName 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) ************************************************** *** *** *** 获取错误信息 *** *** *** ************************************************** 1. Function MyZip_GetLastError(out msg : ShortString):integer;stdcall; 功能 : 在压缩/解压的过程中,如有错误发生,可立即调用该函数获取相关错误信息,并由msg返回 参数 : msg 用于返回相关错误信息 返回 : 0 成功

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值