java 如何处理特殊字符压缩字节(霍夫曼编码)?

mi7gmzs6  于 2023-01-07  发布在  Java
关注(0)|答案(1)|浏览(166)

我正在写一个Huffman压缩/解压缩程序。我已经开始写我的压缩方法了,但我卡住了。我试图读取文件中的所有字节,然后将所有字节放入一个字节数组。在将所有字节放入字节数组后,我创建了一个int[]数组,该数组将存储每个字节的所有频率(索引为ASCII码)。
由于int数组的大小为256,因此它包含扩展ASCII表。但是,我在读取文件中的特殊字符时会遇到问题(ASCII值大于127的AKA字符)。我知道字节是带符号的,并且一旦超过127的数字限制,字节就会绕回负值(数组索引显然不能为负),所以我尝试在指定数组(array[myByte&0xFF])的索引时将其转换为有符号值来解决这个问题。
这种方法有效,但它给了我错误的ASCII值(例如,如果字符的正确ASCII值是134,我却得到了191或其他)。更烦人的是,我注意到特殊字符被分成2个独立的字节,我觉得这会在以后引起问题(例如,当我试图解压缩时)。
我如何使我的程序兼容每一个单一类型的字符(这个程序应该能够压缩/解压缩图片,mp3的等)。
也许我的方法是错误的,但我不知道什么是正确的方法。请给我一些提示,为结构这一点。
树:

package CompPck;

import java.util.TreeMap;

abstract class Tree implements Comparable<Tree> {
    public final int frequency; // the frequency of this tree
    public Tree(int freq) { frequency = freq; }

    // compares on the frequency
    public int compareTo(Tree tree) {
        return frequency - tree.frequency;
    }
}

class Leaf extends Tree {
    public final int value; // the character this leaf represents

    public Leaf(int freq, int val) {
        super(freq);
        value = val;
    }
}

class Node extends Tree {
    public final Tree left, right; // subtrees

    public Node(Tree l, Tree r) {
        super(l.frequency + r.frequency);
        left = l;
        right = r;
    }
}

构建树方法:

public static Tree buildTree(int[] charFreqs) {
        PriorityQueue<Tree> trees = new PriorityQueue<Tree>();

        for (int i = 0; i < charFreqs.length; i++){
            if (charFreqs[i] > 0){
                trees.offer(new Leaf(charFreqs[i], i));
            }
        }

        //assert trees.size() > 0;

        while (trees.size() > 1) {
            Tree a = trees.poll();
            Tree b = trees.poll();

            trees.offer(new Node(a, b));
        }
        return trees.poll();
    }

压缩方法:

public static void compress(File file){
        try {
            Path path = Paths.get(file.getAbsolutePath());
            byte[] content = Files.readAllBytes(path);
            TreeMap<Integer, String> treeMap = new TreeMap<Integer, String>();
            File nF = new File(file.getName() + "_comp");
            nF.createNewFile();
            BitFileWriter bfw = new BitFileWriter(nF);

            int[] charFreqs = new int[256];

            // read each byte and record the frequencies
            for (byte b : content){
                charFreqs[b&0xFF]++;
                System.out.println(b&0xFF);
            }

            // build tree
            Tree tree = buildTree(charFreqs);

            // build TreeMap
            fillEncodeMap(tree, new StringBuffer(), treeMap);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
m0rkklqb

m0rkklqb1#

编码很重要

如果我在文件中读取字符“ö”,它将由两个不同的值(191和182或类似的值)表示,而实际的ASCII表值是148。
这取决于你的文本文件是用哪种编码方式创建的,编码方式决定了文本信息的存储方式。

  • 在UTF-8中,ö存储为十六进制[0xc3, 0xb6][195, 182]
  • 在ISO/IEC 8859-1(=“Latin-1”)中,它将存储为十六进制[0xf6][246]
  • 在Mac OS中欧语言中,它将是十六进制[0x9a][154]

请注意,基本的ASCII表本身并没有真正描述这种字符的任何内容。ASCII只使用7位,这样做只Map了128个代码。

部分问题在于,在外行人看来,“ASCII”有时也被用来描述ASCII的扩展(例如,像Latin-1)
历史
这背后其实有一段历史。最初ASCII是一个非常有限的字符集。当这些字符不够时,每个地区开始使用第8位来添加特定于语言的字符。这导致了各种兼容性问题。
后来有一个联盟列出了所有可能的语言(甚至更远)中的所有字符,这个集合被称为“unicode”,它包含的不仅仅是128或256个字符,而是成千上万个字符。
从那时起,您需要更高级的编码来覆盖它们。UTF-8是覆盖整个unicode集的编码之一,并且它可以向后兼容ASCII。
每个ASCII字符仍然以相同的方式Map,但是当1个字节不够时,它将使用第8位来指示第2个字节将跟随,ö字符就是这种情况。

工具

如果您使用的是更高级的文本编辑器,如Notepad++,则可以从下拉菜单中选择编码。

编程中

话虽如此,你现在的java源代码读字节,而不是阅读字符。我认为当它在字节级工作时是一个优点,因为它可以支持所有的编码。也许你根本不需要在字符级工作。
然而,如果这对你的特定算法很重要,比如说你写了一个只处理Latin-1编码的算法,那么它实际上是在“字符级”上工作,而不是在“字节级”上工作。在这种情况下,考虑直接阅读到Stringchar[]
在这种情况下,Java可以帮你完成繁重的工作。Java中有一些阅读器可以让你直接将文本文件读到Strings/char[]。然而,在这种情况下,你当然应该在使用它们时指定一个编码。在内部,一个Java字符可以包含多达2个字节的数据。
尝试手动将字节转换为字符是一件棘手的事情。当然,除非你使用的是普通的老式ASCII。当你看到一个大于0x 7 F(127)的值时(在byte中用负值表示),你就不再使用简单的ASCII了。然后考虑使用如下的代码:new String(bytes, StandardCharsets.UTF_8)。不需要从头开始编写解码算法。

相关问题