繁体   English   中英

HashMap中的LinkedList搜索性能

[英]LinkedList search performance in HashMap

我几乎可以肯定我在某个地方看到过这样的问题,但找不到。 如果为了相等地查找键,则HashMap如何获得性能为O(1),并且通过LinkedList进行迭代,并且此过程为O(n)。

编辑:

我发现的是,在没有碰撞的情况下,get()的性能实际上介于O(1)和在每个键处于碰撞状态时的O(n)之间。 这是正确的吗?

get的预期性能为O(1) hashCode方法在HashMap的存储桶之间均匀地分配键的假设下计算出预期的性能,因此每个链接列表的平均大小(即每个存储桶中条目的平均数目)很小,因此我们可以假设每个这样的列表都可以在一个小的常数范围内遍历。

在最坏的情况下,如果错误的hashCode将所有键都映射到同一存储桶,则get将花费O(n)

基于散列的对象将根据散列值确定将键值对存储在哪个存储桶中。 在每个存储桶内部都有一个存储该对的结构(在HashMap情况下为LinkedList)。

如果哈希值通常相同,则存储桶通常相同,因此性能会降低很多,下面来看一个示例:

考虑这堂课

package hashTest;

import java.util.Hashtable;

public class HashTest {

    public static void main (String[] args) {

        Hashtable<MyKey, String> hm = new Hashtable<>();

        long ini = System.currentTimeMillis();

        for (int i=0; i<100000; i++) {
            MyKey a = new HashTest().new MyKey(String.valueOf(i));

            hm.put(a, String.valueOf(i));
        }

        System.out.println(hm.size());

        long fin = System.currentTimeMillis();
        System.out.println("tiempo: " + (fin-ini) + " mls");
    }

    private class MyKey {

        private String str;

        public MyKey(String i) {
            str = i;
        }

        public String getStr() {
            return str;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MyKey) {
                MyKey aux = (MyKey) o;
                if (this.str.equals(aux.getStr())) {
                    return true;
                }
            }
            return false;
        }
    }
}

请注意,类MyKey中的hashCode始终返回“ 0”作为哈希值。 哈希码定义( http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode() )可以。 如果我们运行该程序,这就是结果

100000 
tiempo: 62866 mls

性能很差,现在我们要更改MyKey哈希码:

package hashTest;

import java.util.Hashtable;

public class HashTest {

    public static void main (String[] args) {

        Hashtable<MyKey, String> hm = new Hashtable<>();

        long ini = System.currentTimeMillis();

        for (int i=0; i<100000; i++) {
            MyKey a = new HashTest().new MyKey(String.valueOf(i));

            hm.put(a, String.valueOf(i));
        }

        System.out.println(hm.size());

        long fin = System.currentTimeMillis();
        System.out.println("tiempo: " + (fin-ini) + " mls");
    }

    private class MyKey {

        private String str;

        public MyKey(String i) {
            str = i;
        }

        public String getStr() {
            return str;
        }

        @Override
        public int hashCode() {
            return str.hashCode() * 31;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof MyKey) {
                MyKey aux = (MyKey) o;
                if (this.str.equals(aux.getStr())) {
                    return true;
                }
            }
            return false;
        }
    }
}

请注意,只有MyKey中的哈希码已更改,现在当我们运行代码te结果是

100000
tiempo: 47 mls

现在,只需稍作更改,即可获得令人难以置信的更好性能。 一种很常见的做法是,使用与equals方法内部相同的哈希码成员返回哈希码乘以质数(在本例中为31),以确定两个对象是否相同(在本例中仅为str)。

最佳性能的关键是选择最佳的hashcodeequals在HashMap中充当Key的类中的实现。

@Eran的答案解释了为什么HashMap “平均”给出O(1) 他没有说明的关键点是,当数组大小与条目数之比超过(可配置)加载因子时, HashMap将自动调整哈希数组的大小并重新分配哈希链。

如果哈希函数和键行为良好,则哈希链较短,平均搜索链时间为O(1)

在三种情况下,分析会分解:

  1. 如果散列功能不佳,您可能会发现许多/大多数键具有相同的散列值,并且最终位于同一散列链上。 在最坏的情况下,比可能导致O(N)搜索时间。

  2. 应用程序可能必须处理一组键,这些键都不幸具有相同的哈希码。 您还可以得到,如果有人故意选择, 使一些散列链长按键,返回相同的哈希码。 (请考虑……拒绝服务攻击。)

  3. HashMap对象的哈希数组受Java数组的最大大小限制。 当阵列达到最大大小时,将无法再调整大小。 因此,对于非常大的地图(数十亿个条目),查找时间从O(1)切换为O(N) (具有非常小的C )。

这些场景中的每一个都可能是一个问题,因此在Java 8中,他们对HashMap的实现方式进行了一些重大更改。 在Java 8版本中,如果哈希链足够长,则HashMap将从使用链的链表切换为使用平衡的二叉树。 这会将最坏情况的行为从O(N)更改为O(logN)查找。 需要注意的是,这仅在链中的键全部实现Comparable时才有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM