演示视频链接:可以点进去看看撒!
zip压缩软件完成思路
原理:
根据UTF-8编码方式,字符往往占据多个字节,如果我们将其用二进制位表示出来(同时也可借助二进制位译码),将大大节省空间
而由于我们借助出现次数,建立哈夫曼树,来得到代表的二进制码(可以小小思考一下,如何得到呢?),使得出现频率越高,代表的二进制码越短,使得压缩效果更加优越
知识点 | 基本原理 |
---|---|
文件输入输出 | 利用输入输出流完成(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文件:如图
源代码(含有所有代码):
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.youkuaiyun.com/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());
}
}