您将使用哪种数据结构:TreeMap 或 HashMap? (Java)[重复]

Posted

技术标签:

【中文标题】您将使用哪种数据结构:TreeMap 或 HashMap? (Java)[重复]【英文标题】:Which data structure would you use: TreeMap or HashMap? (Java) [duplicate] 【发布时间】:2010-09-23 01:48:20 【问题描述】:

说明 | 一个 Java 程序,用于读取文本文件并按字母顺序打印每个唯一单词以及该单词在文本中出现的次数。

程序应该声明一个Map<String, Integer> 类型的变量来存储单词和相应的出现频率。但是,哪种具体类型? TreeMap<String, Number>HashMap<String, Number> ?

输入应转换为小写。

单词不包含以下任何字符:\t\t\n]f.,!?:;\"()'

示例输出 |

 Word            Frequency
  a                 1
  and               5
  appearances       1
  as                1
         .
         .
         .

备注 | 我知道,我已经在 Perl 中看到了用大约两行代码来解决这个问题的优雅解决方案。但是,我想在 Java 中看到它。

编辑:哦,是的,使用其中一种结构(在 Java 中)显示实现会很有帮助。

【问题讨论】:

哇,这是在明目张胆地寻找家庭作业的答案吗? 实际上,作业问题是实际实现这两个版本——它没有询问哪种数据结构更好。然而,为了保持半真诚学习的失传艺术,我在拐弯抹角……只是想在这里收集一些见解! 我怎么会因为上面的评论得到 6 分。我知道......关于元的问题...... 我有类似的问题,一切都一样,我只想按值排序,而不是键......最好的方法是什么? 【参考方案1】:

TreeMap 对我来说似乎很容易 - 仅仅是因为“按字母顺序”的要求。 HashMap 迭代时没有排序; TreeMap 按自然键顺序迭代。

编辑:我认为康拉德的评论可能暗示“使用HashMap,然后排序。”这很好,因为虽然我们最初会有 N 次迭代,但由于重复,我们最终会有 K

话虽如此,我暂时坚持我的答案:因为这是实现目标的最简单方式。我们真的不知道 OP 特别担心性能,但这个问题暗示他关心优雅和简洁。使用TreeMap 使这非常简短,这对我很有吸引力。我怀疑如果性能确实是一个问题,那么可能有比TreeMapHashMap 更好的攻击方法:)

【讨论】:

乔恩,不要高估排序。以我的经验,即使是巨大的数据集也可以忽略不计。给定足够的项目,O(1) 插入击败 O(logn)。但与往常一样,这只是纯粹的猜测,没有分析。 如果你能实现 O(1) 插入,那么你就有 O(n) 总插入时间。如果您还建议在迭代之前不涉及更多工作,那么您已经以某种方式实现了对 n 个(相对任意)项目的 O(n) 排序。它是如何工作的? 康拉德:我想我现在明白你的意思了。正在编辑... 乔恩,我知道这是古代历史;P 但这是解释。哈希表实际上确实在预期的、摊销的 O(n) 时间内实现排序。已证明的与排序相关的操作的 n log n 下限适用于计算的比较模型。哈希表通过利用传统计算机中随机存取存储器的顺序特性来解决这个问题。然而!实际上,基于比较的排序几乎总是更快。有趣的是,一个好的哈希表实现比普通的快速排序实现要贵 100 倍。 @Mark:我不太确定你在解释什么,或者内存的顺序性如何避免 N log N 复杂性......【参考方案2】:

TreeMap 优于 HashMap,因为 TreeMap 已经为您排序。

但是,您可能需要考虑使用更合适的数据结构,即包。看 Commons Collections - 和 TreeBag 类:

这有一个很好的优化内部结构和 API:

bag.add("big")
bag.add("small")
bag.add("big")
int count = bag.getCount("big")

编辑:Jon 回答了 HashMap 与 TreeMap 性能的问题 - HashMap 和排序可能更快(试试吧!),但 TreeBag 更容易。袋子也是如此。有一个 HashBag 和一个 TreeBag。根据实现(使用可变整数),bag 应该优于 Integer 的等效普通映射。与任何性能问题一样,唯一确定的方法是测试。

【讨论】:

是的,但是在最后排序一次而不是产生一直保持排序的成本会更快吗? Herms,这取决于所涉及的数据结构的效率。使用 random-pivot 快速排序对数组进行就地排序是 FAAAST n log n,但 Java 的 TreeMap 也是一个非常快的 n log n。 Java 的 HashMap 的工作速度非常慢。由于我们需要随机访问元素,因此我们在一次快速 O(n log t) 和一次慢速 O(n) 加上非常快的 O(t log t) 之间进行选择;其中 t very 大。对于谷歌来说,hash+sort 会更好;对于学校,可能是树状图。【参考方案3】:

我看到很多人说“TreeMap 查找需要O(n log n)”!!怎么会?

我不知道它是如何实现的,但在我的脑海中它需要O(log n)

这是因为在树中查找可以在O(log n) 中完成。每次在其中插入项目时,您不会对整个树进行排序。这就是使用树的全部想法!

因此,回到最初的问题,用于比较的数字是:

HashMap 方法: O(n + k log k) 平均情况,最坏情况可能更大

TreeMap 方法: O(k + n log k) 最坏情况

其中 n = 文本中的单词数,k = 文本中不同单词的数量。

【讨论】:

【参考方案4】:

哈希映射应该快得多。您不应该根据您希望项目最终如何排列来选择容器;只需在最后对(单词,频率)对列表进行排序。通常需要排序的此类对比文件中的单词少,因此使用哈希映射的渐近(和真实)性能会更好。

【讨论】:

不一定。在最坏的情况下,哈希表的访问时间为 O(n)。而自平衡二叉搜索树即使在最坏的情况下也总是 O(log n)。 哈希表实际上只会在加载因子差和/或散列函数差的情况下遇到最坏情况下的性能。默认情况下,Java 中提供了足够好的哈希值和足够好的因子,因此这不是一个现实的担忧。【参考方案5】:
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream.GetField;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

public class TreeMapExample 

    public static void main (String args[])
        Map<String,Integer> tm = new TreeMap<String,Integer>();
        try 

            FileInputStream fis = new FileInputStream("Test.txt");
            DataInputStream in = new DataInputStream(fis);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            int countValue = 1;
            while((line = br.readLine())!= null )
                line = line.replaceAll("[-+.^:;,()\"\\[\\]]","");
                StringTokenizer st = new StringTokenizer(line, " ");    
                while(st.hasMoreTokens())
                    String nextElement = (String) st.nextElement();

                    if(tm.size()>0 && tm.containsKey(nextElement))
                        int val = 0;
                        if(tm.get(nextElement)!= null)
                        val = (Integer) tm.get(nextElement);
                        val = val+1;
                        
                        tm.put(nextElement, val);
                    else
                    tm.put(nextElement, 1);
                    

                
            
            for(Map.Entry<String,Integer> entry : tm.entrySet()) 
            System.out.println(entry.getKey() + " : " + entry.getValue());
            

         catch (FileNotFoundException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IOException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    


【讨论】:

【参考方案6】:

您不能将TreeMap&lt;String,Number&gt; 分配给类型为Map&lt;String,Integer&gt; 的变量。 DoubleLong 等可以“放入”TreeMap&lt;String,Number&gt;。当我从Map&lt;String,Integer&gt;“获取”一个值时,它必须是Integer

完全忽略任何 i18n 问题、内存限制和错误处理,这里是:

class Counter 

  public static void main(String... argv)
    throws Exception
  
    FileChannel fc = new FileInputStream(argv[0]).getChannel();
    ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
    CharBuffer cb = Charset.defaultCharset().decode(bb);
    Pattern p = Pattern.compile("[^ \t\r\n\f.,!?:;\"()']+");
    Map<String, Integer> counts = new TreeMap<String, Integer>();
    Matcher m = p.matcher(cb);
    while (m.find()) 
      String word = m.group();
      Integer count = counts.get(word);
      count = (count == null) ? 1 : count + 1;
      counts.put(word, count);
    
    fc.close();
    for (Map.Entry<String, Integer> e : counts.entrySet()) 
      System.out.printf("%s: %d%n", e.getKey(), e.getValue());
    
  


【讨论】:

是的,你是对的——我会回去编辑它。我的意思是把它声明为类型 Map 而不是 Map,那么 TreeMap 是 Map 的子类型。【参考方案7】:

“当一个键已经存在时,它具有与 HashMap 相同的性能。” - 这完全是错误的。 HashMap 有 O(1) 插入和 TreeMap O(n log n)。至少需要 n log n 检查才能确定它是否在表中!

【讨论】:

【参考方案8】:

对于这种方式,在我看来,最好使用Apache Commons Collections 中的HashBag 或Guava 中的HashMultiset 或Eclipse Collections 中的HashBag(正式为GS Collections)或以下任何类:

    Order    |  Guava           |   Apache  | Eclipse(GS) | JDK analog
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Not define   | HashMultiset     |   HashBag | HashBag     | HashMap<String, Integer>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Sorted       | TreeMultiset     |   TreeBag | TreeBag     | TreeMap<String, Integer>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Linked       |LinkedHashMultiset|     -     |     -       | LinkedHashMap<String, Integere>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Concurrent & | ConcurrentHash-  |Synchroniz-|Synchroniz-  | Collections.synchronizedMap(
not define   | Multiset         |   edBag   | edBag       |       HashMap<String, Integer>)
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Concurrent   |         -        |Synchroniz-|Synchroniz-  | Collections.synchronizedSorted-
and sorted   |                  |edSortedBag| edSortedBag |       Map(TreeMap<>))
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Immutable and| ImmutableMultiset|Unmodifiab-|Unmodifiab-  | Collections.unmodifiableMap(
not define   |                  |   leBag   | leBag       | HashMap<String, Integer>)
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Immutable and| ImmutableSorted- |Unmodifiab-|Unmodifiab-  | Collections.unmodifiableSorted-
sorted       | Multiset         |leSortedBag| leSortedBag | Map(TreeMap<String, Integer>))
────────────────────────────────────────────────────────────────────────

例子:

1。 Using SynchronizedSortedBag from Apache:

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    Bag bag = SynchronizedSortedBag.synchronizedBag(new TreeBag(Arrays.asList(INPUT_TEXT.split(" "))));

    // Print count words
    System.out.println(bag); // print [1:All!,2:Hello,1:Hi,2:World!]- in natural (alphabet) order
    // Print all unique words
    System.out.println(bag.uniqueSet());    // print [All!, Hello, Hi, World!]- in natural (alphabet) order


    // Print count occurrences of words
    System.out.println("Hello = " + bag.getCount("Hello"));    // print 2
    System.out.println("World = " + bag.getCount("World!"));    // print 2
    System.out.println("All = " + bag.getCount("All!"));    // print 1
    System.out.println("Hi = " + bag.getCount("Hi"));    // print 1
    System.out.println("Empty = " + bag.getCount("Empty"));    // print 0

    // Print count all words
    System.out.println(bag.size());    //print 6

    // Print count unique words
    System.out.println(bag.uniqueSet().size());    //print 4

2. Using TreeBag from Eclipse(GC):

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    MutableSortedBag<String> bag =  TreeBag.newBag(Arrays.asList(INPUT_TEXT.split(" ")));

    // Print count words
    System.out.println(bag); // print [All!, Hello, Hello, Hi, World!, World!]- in natural order
    // Print all unique words
    System.out.println(bag.toSortedSet());    // print [All!, Hello, Hi, World!]- in natural order

    // Print count occurrences of words
    System.out.println("Hello = " + bag.occurrencesOf("Hello"));    // print 2
    System.out.println("World = " + bag.occurrencesOf("World!"));    // print 2
    System.out.println("All = " + bag.occurrencesOf("All!"));    // print 1
    System.out.println("Hi = " + bag.occurrencesOf("Hi"));    // print 1
    System.out.println("Empty = " + bag.occurrencesOf("Empty"));    // print 0

    // Print count all words
    System.out.println(bag.size());    //print 6

    // Print count unique words
    System.out.println(bag.toSet().size());    //print 4

3。 Using LinkedHashMultiset from Guava:

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    Multiset<String> multiset = LinkedHashMultiset.create(Arrays.asList(INPUT_TEXT.split(" ")));

    // Print count words
    System.out.println(multiset); // print [Hello x 2, World! x 2, All!, Hi]- in predictable iteration order
    // Print all unique words
    System.out.println(multiset.elementSet());    // print [Hello, World!, All!, Hi] - in predictable iteration order

    // Print count occurrences of words
    System.out.println("Hello = " + multiset.count("Hello"));    // print 2
    System.out.println("World = " + multiset.count("World!"));    // print 2
    System.out.println("All = " + multiset.count("All!"));    // print 1
    System.out.println("Hi = " + multiset.count("Hi"));    // print 1
    System.out.println("Empty = " + multiset.count("Empty"));    // print 0

    // Print count all words
    System.out.println(multiset.size());    //print 6

    // Print count unique words
    System.out.println(multiset.elementSet().size());    //print 4

More examples you can find in my github projects

【讨论】:

【参考方案9】:

我肯定会选择 TreeMap:

TreeMap 在插入时自动对新键进行排序,之后无需排序。 当键已经存在时,它的性能与 HashMap 相同。

TreeSet 内部使用 TreeMap,所以为什么不直接使用 TreeMap。

【讨论】:

【参考方案10】:

根据速度要求,您也可以使用Trie。但是,如果 TreeMap 足够快,那么实现其中之一是没有意义的。

【讨论】:

【参考方案11】:

考虑添加或删除数据结构的频率。如果 TreeMap 很高,它就不是理想的了。除了搜索现有条目 nLn 之外,它还经常进行重新平衡。

另一方面,哈希结构在内存上有点华丽(过度分配)。如果你能咬紧牙关,那就去散列结构并在需要时进行排序。

【讨论】:

【参考方案12】:

这里是读取文本文件的 java 示例,根据键排序,然后根据值排序;取决于文件中单词出现的次数。

public class SortFileWords 

    public static void main(String[] args) 
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        ValueCompare vc = new ValueCompare(map);
        TreeMap<String, Integer> sorted_map = new TreeMap<String, Integer>(map);
        List<String> list = new ArrayList<>();
        Scanner sc;
        try 
            sc = new Scanner(new File("c:\\ReadMe1.txt"));
            while (sc.hasNext()) 
                list.add(sc.next());
            
            sc.close();
         catch (FileNotFoundException e) 
            e.printStackTrace();
        

        for (String s : list) 
            if (map.containsKey(s)) 
                map.put(s, map.get(s) + 1);
             else
                map.put(s, 1);
        

        System.out.println("Unsorted map: " + map);
        sorted_map.putAll(map);
        System.out.println("Sorted map on keys: " + sorted_map);

        TreeMap<String, Integer> sorted_value_map = new TreeMap<>(vc);
        sorted_value_map.putAll(map);
        System.out.println("Sorted map on values: " + sorted_value_map);
    


class ValueCompare implements Comparator<String> 

    Map<String, Integer> map;

    public ValueCompare(Map<String, Integer> map) 
        this.map = map;
    

    @Override
    public int compare(String s1, String s2) 
        if (map.get(s1) >= map.get(s2))
            return -1;
        else
            return 1;
    

【讨论】:

【参考方案13】:

为什么不使用TreeSet?

与 TreeMap 相同的排序概念,但它是一个 Set - 根据定义,它是“不包含重复元素的集合”。

根据您的问题描述,听起来您需要一个 Set,我看不出您将哪些键和值映射在一起。

该类实现了由 TreeMap 实例支持的 Set 接口。此类保证排序后的集合将按元素升序排序,根据元素的自然顺序(请参阅 Comparable)或在集合创建时提供的比较器排序,具体取决于使用的构造函数。

【讨论】:

Java TreeSet 也是 Java TreeMap,因此性能考虑相同。【参考方案14】:

基本上取决于需求。有时哈希图很好,有时树图很好。但是哈希映射最好只使用它们是对开销进行排序的一些约束。

【讨论】:

以上是关于您将使用哪种数据结构:TreeMap 或 HashMap? (Java)[重复]的主要内容,如果未能解决你的问题,请参考以下文章

您将使用哪种模型(GPT2、BERT、XLNet 等)进行文本分类任务?为啥?

大数据Java基础DAY21——集合完结(Map接口,HashMap,Linked Hash Map,TreeMap,Collections类)

一致性hash-java实现treemap版

理解一致性hash算法以及TreeMap简易实现示例

您将哪种 Java 类型用于 JPA 集合,为啥?

您将使用哪种 C# 项目类型来重新开发 MFC C++ activex 控件?