├── .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 | --------------------------------------------------------------------------------