【发布时间】:2021-01-31 18:59:05
【问题描述】:
我正在学习一个算法类,我们必须在 Java 中实现 LZW 压缩。我决定为此使用 Trie 数据结构,并且我已经实现了 Trie 并让它工作,但是它非常很慢,而且几乎没有压缩。
我们应该使用 8 位符号并且能够压缩任何二进制文件。
给定一个约 4MB 的文件 (bible.txt),我的代码数组中有大约 549,012 个元素。当我将这些元素写入一个文件(每行一个整数代码)时,我最终得到一个 3.5MB 的“压缩”文件,所以我得到了 0.5MB 的压缩。
我怎样才能使这个程序更有效率?我觉得我在这里误解了一些基本的东西,或者我遗漏了一些明显的东西,但我不知道为什么这不会压缩。
(我从这个网站获得了我的测试文件 bible.txt:https://corpus.canterbury.ac.nz/descriptions/)
我从这样的二进制文件中读取字节(读取为 int 并转换为 char 是必要的,因此 0x80 以上的值不是负数):
public String readFile(String path) throws IOException, FileNotFoundException {
File file = new File(path);
StringBuilder string = new StringBuilder();
try (FileInputStream fileInputStream = new FileInputStream(file)) {
int singleCharInt;
char singleChar;
while((singleCharInt = fileInputStream.read()) != -1) {
singleChar = (char) singleCharInt;
string.append(singleChar);
}
}
return string.toString();
}
我的主要方法是这样的:
public static void main(String args[]) throws FileNotFoundException, IOException {
String bytes = new FileReader().readFile("/home/user/Code/Trie/bible.txt");
ArrayList<Integer> codes = new Compress().compress(bytes);
}
我的 Compress 类如下所示:
public class Compress {
private int code = 0;
public ArrayList<Integer> compress(String data) {
Trie trie = new Trie();
// Initialize Trie Data Structure with alphabet (256 possible values with 8-bit
// symbols)
for (code = 0; code <= 255; code++) {
trie.insert(Character.toString((char) code), code);
}
code++;
String s = Character.toString(data.charAt(0));
ArrayList<Integer> codes = new ArrayList<Integer>();
for (int i = 1; i < data.length(); i++) {
String c = Character.toString(data.charAt(i));
if (trie.find(s + c) > 0) {
s += c;
} else {
codes.add(trie.find(s));
trie.insert(s + c, code);
code++;
s = c;
}
}
codes.add(trie.find(s));
return codes;
}
}
我的 Trie 类如下所示:
public class Trie {
private TrieNode root;
public Trie() {
this.root = new TrieNode(false);
}
public void insert (String word, int code) {
TrieNode current = root;
for (char l: word.toCharArray()) {
current = current.getChildren().computeIfAbsent(Character.toString(l), c -> new TrieNode(false));
}
current.setCode(code);
current.setWordEnd(true);
}
public int find(String word) {
TrieNode current = root;
for (int i = 0 ; i < word.length(); i++) {
char ch = word.charAt(i);
TrieNode node = current.getChildren().get(Character.toString(ch));
if (node == null) {
return -1;
}
current = node;
}
return current.getCode();
}
}
我的 TrieNode 类如下所示:
public class TrieNode {
private HashMap<String, TrieNode> children;
private int code;
private boolean wordEnd;
public TrieNode(boolean wordEnd) {
this.children = new HashMap<String, TrieNode>();
this.wordEnd = wordEnd;
}
public HashMap<String, TrieNode> getChildren() {
return this.children;
}
public void setWordEnd(boolean wordEnd) {
this.wordEnd = wordEnd;
}
public boolean isWordEnd() {
return this.wordEnd;
}
public int getCode() {
return this.code;
}
public void setCode(int code) {
this.code = code;
}
}
感谢您的宝贵时间!
【问题讨论】:
-
您需要先了解
int、char和byte之间的区别,它们如何表示数据,以及Java 如何在它们之间进行转换。您没有压缩,因为您使用 4 个字节(int)来存储每个字节,即占用 4 倍的空间。此外,虽然我没有详细检查您的代码,但您可能还会通过 int 到 char 转换引入损坏。这些基础知识对于教程中的整个章节来说已经足够了,而对于 Stack Overflow 来说太宽泛了。 -
@JimGarrison 我不明白如果 int 不适合存储代码,我应该使用什么。我得到一个大文件的大约 250,000 个代码,它比字节大而且短。下一个选项是 int,但您说它们太大了,所以我不确定替代方案是什么。不过,在我缺乏的基础上,你绝对是对的。
-
在内部,您最终将使用
int,但您使用的输入值都是 1 字节长 (0x00-0xFF),并且由于您预计 250k 代码您将使用可变长度编码。要将压缩数据实际写入文件,您将这些代码转换为可变长度位字符串,将它们打包在缓冲区中,然后写出结果字节。 NIO 缓冲区在这里很有用。您的代码中不应有char或String,因为输入数据必须被视为二进制,无需字符集转换。
标签: java compression lzw