我正在尝试用Java实现一个带有203675个单词的trie结构用于文本编辑器。
之前,我使用ArrayList存储单词,占用了90兆字节的空间。所以我想使用trie来减少空间消耗。
这是我到目前为止所拥有的,但现在空间消耗为250兆字节。这种增加的原因是什么?
package TextEditor;
import java.io.*;
import java.util.*;
import javax.swing.JOptionPane;
class Vertex {
int words;
Map<Character, Vertex> child;
public Vertex() {
words = 0;
child = new HashMap<>();
}
}
class Trie {
private Vertex root;
private InputStream openFile;
private OutputStream openWriteFile;
private BufferedReader readFile;
private BufferedWriter writeFile;
public Trie() {
root = new Vertex();
}
public Trie(String path) {
try {
root = new Vertex();
openFile = getClass().getResourceAsStream(path);
readFile = new BufferedReader( new InputStreamReader(openFile));
String in = readFile.readLine();
while(readFile.ready()) {
this.insert(in);
try {
in = readFile.readLine();
} catch (IOException ex) {
JOptionPane.showMessageDialog(null,
"TRIE CONSTRUCTION ERROR!!!!");
}
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(null,
"TRIE CONSTRUCTION ERROR!!!!");
}
}
private void addWord(Vertex vertex, String s, int i) {
try {
if(i>=s.length()) {
vertex.words += 1;
return;
}
char ind = s.charAt(i);
if(!vertex.child.containsKey(ind)) {
vertex.child.put(ind, new Vertex());
}
addWord(vertex.child.get(ind), s, i+1);
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
final void insert(String s) {
addWord(root, s.toLowerCase(), 0);
}
private void DFS(Vertex v, String s, ArrayList list,
boolean store, String startsWith, int ind) {
if(v != null && v.words != 0) {
if(!store) {
System.out.println(s);
}
else {
if(s.length() >= startsWith.length()) {
list.add(s);
}
}
}
for (Map.Entry<Character, Vertex> entry : v.child.entrySet()) {
Character c = entry.getKey();
if((startsWith == null) || (ind>=startsWith.length()) ||
(startsWith.charAt(ind) == c)) {
DFS(v.child.get(c), s + c, list, store, startsWith, ind+1);
}
}
}
public void Print() {
DFS(root, new String(""), null, false, null, 0);
}
ArrayList<String> getAsList(String startsWith) {
ArrayList ret = new ArrayList();
DFS(root, new String(""), ret, true, startsWith, 0);
return ret;
}
int count(Vertex vertex, String s, int i) {
if(i >= s.length()) {
return vertex.words;
}
if(!vertex.child.containsKey(s.charAt(i))) {
return 0;
}
return count(vertex.child.get(s.charAt(i)), s, i+1);
}
int count(String s) {
return count(root, s, 0);
}
}
我可以使用trie结构的工作示例吗?
答案 0 :(得分:1)
您对“空间”一词的使用含糊不清。根据你的描述,这听起来像你在谈论堆。如果是这样,增加内存使用的原因是像trie这样的数据结构实际上占用了额外的内存来存储节点之间的引用。 ArrayList
只包含所有内容,一个String
引用一个接一个,并且除了数组之外没有任何其他信息。特里有更多的簿记来指定所有节点之间的关系。
特别是,每个顶点中的HashMap
将非常昂贵;默认情况下,Sun实现为16个条目的映射分配足够的空间,这需要存储地图自己的内存分配记录hashCodes
(32位int
,而不是char
s),每个Character
的对象包装器......
答案 1 :(得分:0)
首先,将数据结构(您的trie)与填充它的任何代码分开。它只需要以结构化的形式保存数据,并提供一些基本功能,就是这样。填充它应该发生在数据结构本身之外,这样您就可以正确处理流。没有一个充分的理由让你的trie通过提供一条路径作为一个参数来填补自己。 为了澄清我的第一点 - 拉出trie的填充:目前,流在trie中吞噬了大量内存,因为它们被保存在私有变量中,并且流不会被关闭或破坏。这意味着你将文件加载到内存中的填充数据结构上。否则垃圾收集可以像使用arraylist一样清理这些项目。
请不要重新发明轮子并使用如下的基本实现。让它使用这个基本设置,并担心以后改进它。
public class Trie {
private Map<String, Node> roots = new HashMap<>();
public Trie() {}
public Trie(List<String> argInitialWords) {
for (String word:argInitialWords) {
addWord(word);
}
}
public void addWord(String argWord) {
addWord(argWord.toCharArray());
}
public void addWord(char[] argWord) {
Node currentNode = null;
if (!roots.containsKey(Character.toString(argWord[0]))) {
roots.put(Character.toString(argWord[0]), new Node(argWord[0], "" + argWord[0]));
}
currentNode = roots.get(Character.toString(argWord[0]));
for (int i = 1; i < argWord.length; i++) {
if (currentNode.getChild(argWord[i]) == null) {
currentNode.addChild(new Node(argWord[i], currentNode.getValue() + argWord[i]));
}
currentNode = currentNode.getChild(argWord[i]);
}
currentNode.setIsWord(true);
}
public boolean containsPrefix(String argPrefix) {
return contains(argPrefix.toCharArray(), false);
}
public boolean containsWord(String argWord) {
return contains(argWord.toCharArray(), true);
}
public Node getWord(String argString) {
Node node = getNode(argString.toCharArray());
return node != null && node.isWord() ? node : null;
}
public Node getPrefix(String argString) {
return getNode(argString.toCharArray());
}
@Override
public String toString() {
return roots.toString();
}
private boolean contains(char[] argString, boolean argIsWord) {
Node node = getNode(argString);
return (node != null && node.isWord() && argIsWord) || (!argIsWord && node != null);
}
private Node getNode(char[] argString) {
Node currentNode = roots.get(Character.toString(argString[0]));
for (int i = 1; i < argString.length && currentNode != null; i++) {
currentNode = currentNode.getChild(argString[i]);
if (currentNode == null) {
return null;
}
}
return currentNode;
}
}
public class Node {
private final Character ch;
private final String value;
private Map<String, Node> children = new HashMap<>();
private boolean isValidWord;
public Node(char argChar, String argValue) {
ch = argChar;
value = argValue;
}
public boolean addChild(Node argChild) {
if (children.containsKey(Character.toString(argChild.getChar()))) {
return false;
}
children.put(Character.toString(argChild.getChar()), argChild);
return true;
}
public boolean containsChildValue(char c) {
return children.containsKey(Character.toString(c));
}
public String getValue() {
return value.toString();
}
public char getChar() {
return ch;
}
public Node getChild(char c) {
return children.get(Character.toString(c));
}
public boolean isWord() {
return isValidWord;
}
public void setIsWord(boolean argIsWord) {
isValidWord = argIsWord;
}
public String toString() {
return value;
}
}
如果您正在考虑改进内存使用(以性能为代价),您可以通过以下方式(单独或组合)来实现
一般来说,一个实现良好的trie,并且我强调实施良好应该大约相当于你输入的同一数据集的90Mb的内存消耗,尽管它完全取决于实际的数据集。
如果您设法将大多数单词不是任何其他单词的前缀的单词列表放在一起。您的内存使用量将远远大于ArrayList,因为您需要更多节点来表示相同的内容。
如果你真的想为真正的随机数据集保存一些内存,你应该看看Burst tries,另一个可行的选择可能是patricia trie。