1. 程式人生 > 其它 >java實現哈夫曼編碼檔案解壓

java實現哈夫曼編碼檔案解壓

java實現哈夫曼編碼檔案解壓

壓縮見:java實現哈夫曼編碼的檔案壓縮 - CoderDreams - 部落格園 (cnblogs.com)

解壓新增程式碼

/**
 * 解壓檔案
 *
 * @param src     壓縮檔案的全路徑
 * @param dstFile 壓縮後存放的路徑
 */
private static void unZipFile(String src, String dstFile) {
    FileInputStream is = null;
    ObjectInputStream ois = null;
    FileOutputStream os = null;
    try {
        is = new FileInputStream(src);
        // 建立物件輸入流
        ois = new ObjectInputStream(is);
        // 讀取
        byte[] huffmanBtyes = (byte[]) ois.readObject();
        Map<Byte,String> zipMap = (Map<Byte, String>) ois.readObject();

        // 解碼
        byte[] bytes = unZip(zipMap, huffmanBtyes);
        // 寫入目標檔案
        os = new FileOutputStream(dstFile);
        os.write(bytes);
    } catch (Exception exception) {
        exception.printStackTrace();
    } finally {
        try {
            is.close();
            ois.close();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

測試方法

public static void main(String[] args) {
    String srcFile = "D:\\圖片\\dst.zip";
    String destFile = "D:\\圖片\\new.jpg";

    unZipFile(srcFile,destFile);
    System.out.println("解壓成功");
}

全程式碼

public class HuffmanEncodingDemo {

    public static void main(String[] args) {
        String srcFile = "D:\\圖片\\dst.zip";
        String destFile = "D:\\圖片\\new.jpg";

        unZipFile(srcFile,destFile);
        System.out.println("解壓成功");
    }

    /**
     * 解壓檔案
     *
     * @param src     壓縮檔案的全路徑
     * @param dstFile 壓縮後存放的路徑
     */
    private static void unZipFile(String src, String dstFile) {
        FileInputStream is = null;
        ObjectInputStream ois = null;
        FileOutputStream os = null;
        try {
            is = new FileInputStream(src);
            // 建立物件輸入流
            ois = new ObjectInputStream(is);
            // 讀取
            byte[] huffmanBtyes = (byte[]) ois.readObject();
            Map<Byte,String> zipMap = (Map<Byte, String>) ois.readObject();

            // 解碼
            byte[] bytes = unZip(zipMap, huffmanBtyes);
            // 寫入目標檔案
            os = new FileOutputStream(dstFile);
            os.write(bytes);
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            try {
                is.close();
                ois.close();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 壓縮檔案
     *
     * @param src     壓縮檔案的全路徑
     * @param dstFile 壓縮後存放的路徑
     */
    private static void zipFile(String src, String dstFile) {
        FileInputStream is = null;
        FileOutputStream os = null;
        ObjectOutputStream oos = null;
        try {
            // 建立IO流
            is = new FileInputStream(src);
            // 建立和原始檔相同大小的byte陣列
            byte[] bytes = new byte[is.available()];
            // 讀取檔案
            is.read(bytes);
            // 解壓
            byte[] huffmanZip = huffmanZip(bytes);
            // 建立檔案輸出流存放壓縮檔案
            os = new FileOutputStream(dstFile);
            // 建立和輸出流相關的物件輸出流
            oos = new ObjectOutputStream(os);
            // 這裡以物件的方式進行輸出壓縮檔案,方便恢復
            oos.writeObject(huffmanZip);

            // 最後將編碼表寫入
            oos.writeObject(huffmanCodes);
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            try {
                is.close();
                os.close();
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解碼
     *
     * @param huffmanCodes 編碼表
     * @param bytes        需要解碼的位元組陣列
     */
    private static byte[] unZip(Map<Byte, String> huffmanCodes, byte[] bytes) {
        // 拿到對應的字串
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            // 判斷是不是最後一個位元組
            boolean flag = (i == bytes.length - 1);
            // 不是則為真
            String s = byteToBitString(!flag, bytes[i]);
            stringBuilder.append(s);
        }

        // 拿到之後按照編碼表解碼
        // 先將編碼表調換
        HashMap<String, Byte> unzipMap = new HashMap<>();
        Set<Map.Entry<Byte, String>> entries = huffmanCodes.entrySet();
        for (Map.Entry<Byte, String> entry : entries) {
            unzipMap.put(entry.getValue(), entry.getKey());
        }

        // 建立集合存放byte
        ArrayList<Byte> byteArrayList = new ArrayList<>();

        // 解碼並存放
        for (int i = 0; i < stringBuilder.length(); ) {
            // 小計數器
            int count = 1;
            boolean flag = true;
            Byte b = null;

            while (flag) {
                // 從i開始取出n個位元組進行判斷
                String value = stringBuilder.substring(i, i + count);
                b = unzipMap.get(value);
                if (b == null) {
                    ++count;
                } else {
                    flag = false;
                }
            }
            byteArrayList.add(b);
            i += count;
        }

        // 把list轉成陣列返回
        byte[] result = new byte[byteArrayList.size()];
        for (int i = 0; i < byteArrayList.size(); i++) {
            result[i] = byteArrayList.get(i);
        }
        return result;
    }

    /**
     * 將一個byte轉成一個二進位制的字串
     *
     * @param flag 標誌是否需要補高位,true表示需要,false表示不需要
     * @return 按補碼返回
     */
    public static String byteToBitString(boolean flag, byte b) {
        int temp = b;

        // 如果是最後一個位元組不需要補高位
        if (flag) {
            // 按位與
            temp |= 256;
        }

        String str = Integer.toBinaryString(temp);
        if (flag) {
            str = str.substring(str.length() - 8);
        }

        return str;
    }

    /**
     * 封裝,方便呼叫(位元組陣列)
     *
     * @param bytes 傳入的位元組陣列
     * @return 經過哈夫曼編碼後的位元組陣列
     */
    private static byte[] huffmanZip(byte[] bytes) {
        // 轉成Node集合
        List<Node> nodes = getNodes(bytes);
        // 建立哈夫曼樹
        Node huffmanTree = createHuffmanTree(nodes);
        // 建立編碼表
        HashMap<Byte, String> encoding = createEncoding(huffmanTree);
        // 壓縮並返回
        return zip(bytes, huffmanCodes);
    }

    /**
     * 封裝,方便呼叫(字串)
     *
     * @param str 傳入的字串
     * @return 經過哈夫曼編碼後的位元組陣列
     */
    private static byte[] huffmanZip(String str) {
        byte[] bytes = str.getBytes();
        // 轉成Node集合
        List<Node> nodes = getNodes(bytes);
        // 建立哈夫曼樹
        Node huffmanTree = createHuffmanTree(nodes);
        // 建立編碼表
        HashMap<Byte, String> encoding = createEncoding(huffmanTree);
        // 壓縮並返回
        return zip(bytes, huffmanCodes);
    }

    /**
     * 將位元組陣列根據編碼表壓縮
     *
     * @param bytes 被轉換的位元組陣列
     * @return 壓縮後的陣列
     */
    private static byte[] zip(byte[] bytes, HashMap<Byte, String> huffmanCodes) {
        StringBuilder builder = new StringBuilder();
        // 遍歷bytes
        for (byte b : bytes) {
            // 根據編碼表拿到對應的編碼
            String code = huffmanCodes.get(b);
            builder.append(code);
        }
        // 編碼後再次壓縮,轉成byte陣列
        // 先得到byte[]的長度
        int length = builder.length();
        int len;
        // 或者寫成 len = (length + 7) / 8;
        if (length % 8 == 0) {
            len = length / 8;
        } else {
            len = length / 8 + 1;
        }
        byte[] result = new byte[len];
        // 每8位對應
        int index = 0;
        for (int i = 0; i < length; i += 8) {
            String str;
            if (i + 8 < length) {
                // 夠八位
                str = builder.substring(i, i + 8);
            } else {
                // 最後不夠八位
                str = builder.substring(i);
            }
            // 轉成一個byte存入
            result[index++] = (byte) Integer.parseInt(str, 2);
        }
        return result;
    }


    static HashMap<Byte, String> huffmanCodes = new HashMap<>();
    static StringBuilder stringbuilder = new StringBuilder();

    /**
     * 為了方便呼叫和使用,過載
     */
    private static HashMap<Byte, String> createEncoding(Node root) {
        if (root == null) {
            return null;
        }
        // 為了減少root的一次
        createEncoding(root.left, "0", stringbuilder);
        createEncoding(root.right, "1", stringbuilder);
        return huffmanCodes;
    }

    /**
     * 根據哈夫曼樹建立對應的哈夫曼編碼表
     *
     * @param node          傳入的節點
     * @param code          路徑:左位元組為0,右子節點為1
     * @param stringBuilder 用於拼接路徑
     */
    private static void createEncoding(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder builder = new StringBuilder(stringBuilder);
        builder.append(code);

        if (node != null) {
            // 判斷是不是應該編碼的
            if (node.data == null) {
                // 左右遞迴
                createEncoding(node.left, "0", builder);
                createEncoding(node.right, "1", builder);
            } else {
                // 如果是
                huffmanCodes.put(node.data, builder.toString());
            }
        }
    }

    /**
     * 根據Node集合建立哈夫曼樹
     */
    private static Node createHuffmanTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            Collections.sort(nodes);

            Node left = nodes.get(0);
            Node right = nodes.get(1);

            // 通過另一個構造器
            Node parent = new Node(left.weight + right.weight);
            parent.left = left;
            parent.right = right;

            nodes.remove(left);
            nodes.remove(right);
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    /**
     * 將字元陣列按出現個數轉換成一個個Node,並存到List返回
     *
     * @param bytes 被轉換的字元陣列
     * @return 轉換後的list
     */
    private static List<Node> getNodes(byte[] bytes) {
        ArrayList<Node> nodes = new ArrayList<>();
        // 遍歷統計出現的次數
        HashMap<Byte, Integer> map = new HashMap<>();
        for (byte b : bytes) {
            Integer integer = map.get(b);
            if (integer == null) {
                map.put(b, 1);
            } else {
                map.put(b, integer + 1);
            }
        }
        // 將map裡的元素轉成一個個Node
        Set<Map.Entry<Byte, Integer>> entries = map.entrySet();
        for (Map.Entry<Byte, Integer> entry : entries) {
            Node node = new Node(entry.getKey(), entry.getValue());
            // 並加入list
            nodes.add(node);
        }
        return nodes;
    }
}

/**
 * 節點類
 */
class Node implements Comparable<Node> {
    Byte data;
    int weight;

    Node left;
    Node right;

    public Node(int weight) {
        this.weight = weight;
    }

    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    /**
     * 前序遍歷
     */
    public void preOrder() {
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
}