我正在写一个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();
}
}
1条答案
按热度按时间m0rkklqb1#
编码很重要
如果我在文件中读取字符“ö”,它将由两个不同的值(191和182或类似的值)表示,而实际的ASCII表值是148。
这取决于你的文本文件是用哪种编码方式创建的,编码方式决定了文本信息的存储方式。
[0xc3, 0xb6]
或[195, 182]
[0xf6]
或[246]
[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编码的算法,那么它实际上是在“字符级”上工作,而不是在“字节级”上工作。在这种情况下,考虑直接阅读到
String
或char[]
。在这种情况下,Java可以帮你完成繁重的工作。Java中有一些阅读器可以让你直接将文本文件读到Strings/char[]。然而,在这种情况下,你当然应该在使用它们时指定一个编码。在内部,一个Java字符可以包含多达2个字节的数据。
尝试手动将字节转换为字符是一件棘手的事情。当然,除非你使用的是普通的老式ASCII。当你看到一个大于0x 7 F(127)的值时(在
byte
中用负值表示),你就不再使用简单的ASCII了。然后考虑使用如下的代码:new String(bytes, StandardCharsets.UTF_8)
。不需要从头开始编写解码算法。