myZip自制压缩软件(基于哈夫曼树完成)

编程入门 行业动态 更新时间:2024-10-10 04:24:53

myZip自制<a href=https://www.elefans.com/category/jswz/34/1759467.html style=压缩软件(基于哈夫曼树完成)"/>

myZip自制压缩软件(基于哈夫曼树完成)

演示视频链接:可以点进去看看撒!

zip压缩软件完成思路

  • 原理:
  • 实现:
    • 压缩方法:
      • 1.读文档,建hashMap<>:
      • 2.搭哈夫曼树:
      • 3.得到字符编码,存入hashMap<编码,字符>:
      • 4.据hashMap<Character,String>译码,得到0/1字符串:
      • 5.(0/1字符串->byte[])写入文档(注意hashMap<编码,字符>写入):
    • 解压方法:
      • 1.(byte[]->0/1字符串)过程:
      • 2.根据所读hashMap<编码,字符>,(0/1字符串->String),得到真正内容字符串
      • 3.写入到解压文件中:
  • 界面设计:
    • 外观设计:
    • 建立ActionListener
    • 添加运行图片标志
  • 优化
    • 提升速度
      • 1.找最小权值:
      • 2.字符串拼接耗时:
    • 提升容量
      • StringBuilder存在极限
  • 打包成jar文件:*如图*
  • 源代码(含有所有代码):
    • 1.界面
    • 2.压缩软件

原理:

根据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();}//得到readMapfor (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,初始位置存numbyte[] 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() {@Overridepublic 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文件:如图

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

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() {@Overridepublic 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();}//得到readMapfor (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,初始位置存numbyte[] 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* 原因:暂时还不清楚,可去看这篇文章* * 所以,现在还是用 * 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());}
}

更多推荐

myZip自制压缩软件(基于哈夫曼树完成)

本文发布于:2024-03-14 16:04:03,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1736832.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:压缩软件   myZip   哈夫曼树

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!