├── .gitignore
├── README.md
├── bible.txt
├── pom.xml
└── src
└── main
└── java
└── me
└── masterbear
├── BitWriter.java
├── CNode.java
├── Compressor.java
├── DeCompressor.java
└── Main.java
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.class
3 | *.dat
4 | bible_copy.txt
5 | .DS_Store
6 |
7 | # User-specific stuff
8 | .idea/*
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Compressor
2 |
3 | `教学`用,基于哈夫曼树的文件压缩器
4 |
5 |
6 | ### 用法
7 |
8 | ```java
9 | Compressor compressor = new Compressor("file");
10 | compressor.compress("zip.dat");
11 | DeCompressor deCompressor = new DeCompressor("zip.dat");
12 | deCompressor.decompress("file2");
13 | ```
14 |
15 | ### 作者
16 |
17 | 熊爷
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.log
8 | C2
9 | 1.0
10 |
11 |
12 |
13 | org.apache.maven.plugins
14 | maven-compiler-plugin
15 |
16 | 8
17 | 8
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/me/masterbear/BitWriter.java:
--------------------------------------------------------------------------------
1 | package me.masterbear;
2 |
3 | import java.io.*;
4 |
5 | public class BitWriter {
6 |
7 | int pos = 0;
8 | byte cur = 0;
9 | int totalBit = 0;
10 | final int MOD = 8;
11 | FileOutputStream fileOutputStream;
12 | int totalBitOffset;
13 | final String filename;
14 |
15 | public BitWriter(final String filename) {
16 | this.filename = filename;
17 | totalBitOffset = 0;
18 | try {
19 | fileOutputStream = new FileOutputStream(filename);
20 | } catch (IOException e) {
21 | e.printStackTrace();
22 | System.exit(-1);
23 | }
24 | }
25 |
26 | private void writeInt(int val) {
27 | try {
28 | fileOutputStream.write(new byte[]{(byte) ((val & (0xFF000000))>>24)});
29 | fileOutputStream.write(new byte[]{(byte) ((val & (0x00FF0000))>>16)});
30 | fileOutputStream.write(new byte[]{(byte) ((val & (0x0000FF00))>>8)});
31 | fileOutputStream.write(new byte[]{(byte) ((val & (0x000000FF)))});
32 | } catch (IOException e) {
33 | e.printStackTrace();
34 | System.exit(-1);
35 | }
36 | }
37 |
38 | public void writeFreqHead(int[] fre) {
39 | int length = fre.length;
40 | writeInt(length);
41 | for (int j : fre) {
42 | writeInt(j);
43 | }
44 | totalBitOffset = (1 + fre.length) * 4;
45 | }
46 |
47 | public void writeBit(int i) {
48 | if (i != 0 && i != 1) {
49 | throw new RuntimeException("Argument Error");
50 | }
51 | cur = (byte) ((cur << 1) | ((i == 1) ? 1 : 0));
52 | pos++;
53 | totalBit++;
54 |
55 | if (pos == MOD) {
56 | pos = 0;
57 | try {
58 | fileOutputStream.write(new byte[]{cur});
59 | } catch (IOException e) {
60 | e.printStackTrace();
61 | throw new RuntimeException("IO Error");
62 | }
63 | cur = 0;
64 | }
65 | }
66 |
67 | public void close() {
68 | try {
69 | if (pos > 0) {
70 | while (pos != MOD) {
71 | cur = (byte) (cur << 1);
72 | pos++;
73 | }
74 | fileOutputStream.write(new byte[]{cur});
75 | }
76 | fileOutputStream.flush();
77 | fileOutputStream.close();
78 | } catch (IOException e) {
79 | e.printStackTrace();
80 | }
81 | }
82 |
83 | public void writeTotalBitPlaceHolder() {
84 | writeInt(0);
85 | }
86 |
87 | public void writeTotalBit() {
88 | try {
89 | RandomAccessFile r = new RandomAccessFile(filename, "rw");
90 | r.seek(totalBitOffset);
91 | r.writeInt(totalBit);
92 | r.close();
93 | } catch (IOException e) {
94 | e.printStackTrace();
95 | throw new Error("Write Total Bit Error");
96 | }
97 | }
98 |
99 |
100 | public int getTotalBit() {
101 | return totalBit;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/me/masterbear/CNode.java:
--------------------------------------------------------------------------------
1 | package me.masterbear;
2 |
3 | public class CNode {
4 |
5 | final public byte val;
6 | final int freq;
7 | final boolean isLeaf;
8 |
9 | public final CNode[] ch = new CNode[2];
10 |
11 | public CNode(byte val, int fre, boolean isLeaf) {
12 | this.val = val;
13 | this.freq = fre;
14 | this.isLeaf = isLeaf;
15 | }
16 |
17 | public int getFreq() {
18 | return freq;
19 | }
20 |
21 | public boolean isLeaf() {
22 | return isLeaf;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/me/masterbear/Compressor.java:
--------------------------------------------------------------------------------
1 | package me.masterbear;
2 |
3 |
4 | import java.io.*;
5 | import java.util.Comparator;
6 | import java.util.HashMap;
7 | import java.util.PriorityQueue;
8 |
9 | public class Compressor {
10 |
11 | final static int TABLE_LENGTH = 256;
12 | final static int OFFSET = 128;
13 | final String filename;
14 | final HashMap encodeTable = new HashMap<>();
15 | final HashMap decodeTable = new HashMap<>();
16 | int[] fre = new int[256];
17 | int totalOriginByte;
18 | int totalCompressedBit;
19 |
20 | CNode root;
21 |
22 | public Compressor(String file) {
23 | filename = file;
24 | }
25 |
26 | private void init() {
27 | genFreTable();
28 | generateTree();
29 | processTree();
30 | }
31 |
32 | BufferedInputStream getReader(String filename) {
33 | FileInputStream f = null;
34 | try {
35 | f = new FileInputStream(filename);
36 | } catch (FileNotFoundException e) {
37 | e.printStackTrace();
38 | System.exit(-1);
39 | }
40 | return new BufferedInputStream(f);
41 | }
42 |
43 |
44 | void genFreTable() {
45 | BufferedInputStream r = getReader(filename);
46 | byte[] buf = new byte[1024];
47 | int sz;
48 | int total = 0;
49 | try {
50 | while ((sz = r.read(buf)) > 0) {
51 | for (int i = 0; i < sz; i++) {
52 | fre[buf[i] + OFFSET]++;
53 | }
54 | total += sz;
55 | }
56 | r.close();
57 | } catch (Exception e) {
58 | e.printStackTrace();
59 | System.out.println("IO Error");
60 | System.exit(-1);
61 | }
62 | totalOriginByte = total;
63 | System.out.println("Total:" + total + " bytes");
64 | }
65 |
66 | private void generateTree() {
67 | PriorityQueue q = new PriorityQueue<>(Comparator.comparing(CNode::getFreq));
68 | for (int i = 0; i < TABLE_LENGTH; i++) {
69 | if (fre[i] == 0) continue;
70 | q.add(new CNode((byte) (i - OFFSET), fre[i], true));
71 | }
72 |
73 | while (q.size() != 1) {
74 | CNode left = q.poll();
75 | CNode right = q.poll();
76 | assert left != null;
77 | assert right != null;
78 | CNode t = new CNode((byte) 0, left.freq + right.freq, false);
79 | t.ch[0] = left;
80 | t.ch[1] = right;
81 | q.add(t);
82 | }
83 | root = q.peek();
84 | }
85 |
86 | void processTree() {
87 | dfs(root, "");
88 | for (byte b : encodeTable.keySet()) {
89 | decodeTable.put(encodeTable.get(b), b);
90 | }
91 | }
92 |
93 | void dfs(CNode root, String cur) {
94 | if (root == null) {
95 | return;
96 | }
97 | if (root.isLeaf) {
98 | encodeTable.put(root.val, cur);
99 | //return;
100 | }
101 |
102 | dfs(root.ch[0], cur + "0");
103 | dfs(root.ch[1], cur + "1");
104 | }
105 |
106 | public void compress(String to) {
107 | init();
108 | BufferedInputStream r = getReader(filename);
109 | BitWriter bitWriter = new BitWriter(to);
110 | bitWriter.writeFreqHead(fre);
111 | bitWriter.writeTotalBitPlaceHolder(); // write 0 first
112 | byte[] buf = new byte[1024];
113 | int sz;
114 | try {
115 | while ((sz = r.read(buf)) > 0) {
116 | for (int i = 0; i < sz; i++) {
117 | String e = encodeTable.get(buf[i]);
118 | assert e != null && e.length() != 0;
119 | for (int j = 0; j < e.length(); j++) {
120 | if (e.charAt(j) == '0') {
121 | bitWriter.writeBit(0);
122 | } else {
123 | bitWriter.writeBit(1);
124 | }
125 | }
126 | }
127 | }
128 | } catch (Exception e) {
129 | e.printStackTrace();
130 | System.out.println("IO Error");
131 | System.exit(-1);
132 | }
133 | bitWriter.close();
134 | totalCompressedBit = bitWriter.getTotalBit();
135 | System.out.println("Compressed bit: " + bitWriter.getTotalBit());
136 | bitWriter.writeTotalBit();
137 | }
138 | }
139 |
140 |
--------------------------------------------------------------------------------
/src/main/java/me/masterbear/DeCompressor.java:
--------------------------------------------------------------------------------
1 | package me.masterbear;
2 |
3 | import java.io.*;
4 | import java.nio.file.Files;
5 | import java.nio.file.Paths;
6 | import java.util.Comparator;
7 | import java.util.PriorityQueue;
8 |
9 | public class DeCompressor {
10 |
11 | int tableLength;
12 |
13 | final static int OFFSET = 128;
14 | final String filename;
15 | int[] fre;
16 | int totalCompressedBit;
17 | CNode root;
18 |
19 | public DeCompressor(String filename) {
20 | this.filename = filename;
21 | }
22 |
23 | public void generateTree() {
24 | PriorityQueue q = new PriorityQueue<>(Comparator.comparing(CNode::getFreq));
25 | for (int i = 0; i < tableLength; i++) {
26 | if (fre[i] == 0) continue;
27 | q.add(new CNode((byte) (i - OFFSET), fre[i], true));
28 | }
29 |
30 | while (q.size() != 1) {
31 | CNode left = q.poll();
32 | CNode right = q.poll();
33 | assert left != null;
34 | assert right != null;
35 | CNode t = new CNode((byte) 0, left.freq + right.freq, false);
36 | t.ch[0] = left;
37 | t.ch[1] = right;
38 | q.add(t);
39 | }
40 | root = q.peek();
41 |
42 | }
43 |
44 | private static int readInt(InputStream inputStream) {
45 | byte[] b = new byte[1];
46 | int ans = 0;
47 | try {
48 | int l = 0;
49 | int offset = 24;
50 | while (inputStream.read(b) > 0) {
51 | l++;
52 | int val = (int) b[0];
53 | if (val < 0) val += 256;
54 | ans = (ans | (val << offset));
55 | offset -= 8;
56 | if (l == 4) {
57 | break;
58 | }
59 | }
60 | if (l != 4) {
61 | (new RuntimeException("Read Int Error")).printStackTrace();
62 | System.exit(-1);
63 | }
64 | } catch (IOException e) {
65 | e.printStackTrace();
66 | }
67 | return ans;
68 | }
69 |
70 | void readHead(BufferedInputStream inputStream) {
71 | int tableSize = readInt(inputStream);
72 | fre = new int[tableSize];
73 | for (int i = 0; i < tableSize; i++) {
74 | fre[i] = readInt(inputStream);
75 | }
76 | tableLength = tableSize;
77 | totalCompressedBit = readInt(inputStream);
78 | System.out.println("Table Size:" + tableLength + ", totalByte: " + totalCompressedBit / 8 + "bytes");
79 | }
80 |
81 | void processTree(BufferedInputStream in) {
82 | readHead(in);
83 | generateTree();
84 | }
85 |
86 | public void decompress(String to) {
87 | try {
88 | BufferedInputStream r = new BufferedInputStream(Files.newInputStream(Paths.get(filename)));
89 | BufferedOutputStream w = new BufferedOutputStream(Files.newOutputStream(Paths.get(to)));
90 | processTree(r);
91 | byte[] buf = new byte[1];
92 | CNode temp = root;
93 | int processed = 0;
94 | while (r.read(buf) > 0) {
95 | byte b = buf[0];
96 | for (int i = 0; i < Math.min(totalCompressedBit - processed, 8); i++) {
97 | if ((b & (1 << (8 - i - 1))) != 0) {
98 | temp = temp.ch[1];
99 | } else {
100 | temp = temp.ch[0];
101 | }
102 |
103 | if (temp.isLeaf) {
104 | w.write(new byte[]{temp.val});
105 | temp = root;
106 | }
107 | }
108 | processed += 8;
109 | }
110 | w.close();
111 | } catch (IOException e) {
112 | e.printStackTrace();
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/me/masterbear/Main.java:
--------------------------------------------------------------------------------
1 | package me.masterbear;
2 |
3 | public class Main {
4 |
5 | public static void main(String[] args) {
6 | final String from = "bible.txt";
7 | final String to = "bible_copy.txt";
8 | Compressor compressor = new Compressor(from);
9 | compressor.compress("zip.dat");
10 | DeCompressor deCompressor = new DeCompressor("zip.dat");
11 | deCompressor.decompress(to);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------