Java 容器知识点(简)

来源:互联网 发布:怎么克服懒惰 知乎 编辑:程序博客网 时间:2024/06/08 05:20

1、加入Set的元素必须定义equals()方法以确保对象的唯一性。关于Set子类集合的元素顺序问题:HashSet以某种神秘的顺序保存所有的元素(O(∩_∩)O~)、LinkedHashSet按照元素的插入顺序保存元素、TreeSet按照排序顺序维护元素。

2、除了并发应用,Queue在JavaSE5中仅有的两个实现是LinkedList和PriorityQueue。

3、散列与散列码

Object的hashcode方法默认使用对象的地址计算散列码,Object的equals方法默认比较的也是对象的地址。因此如果使用自己的类作为Hash***的键,必须同时重载hashcode和equals方法。其中,equals用于判断当前的键是否与表中的键相等,而hashcode用于快速查找。

if(map.containsKey(key)){

map.get(key);

}

-->查看containsKey的源码:

@Override public boolean containsKey(Object key) {    if (key == null) {        return entryForNullKey != null;    }    int hash = Collections.secondaryHash(key);    HashMapEntry<K, V>[] tab = table;    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];            e != null; e = e.next) {        K eKey = e.key;        if (eKey == key || (e.hash == hash && key.equals(eKey))) {            return true;        }    }    return false;}
我们发现Map确实是使用equals来判断当前的键是否与表中的键相等。
使用散列给我们带来了什么好处呢?那就是速度。
    我们知道线性查询是最慢的查询方式,散列的价值在于速度:散列使得查询得以快速的进行。散列将键保存在某处,以便能够很快的找到。而存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(是键的信息,而非键本身),数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码
为了解决数组容量被固定的问题,不同的键可以产生相同的下标,也就是是可能存在冲突的,因此数组的多大就不重要了,任何键都可以在数组中找到它的位置。
    于是查询一个值的过程首先就是计算散列码,然后使用散列码来查询数组。如果要是没有冲突,那就有了一个完美的散列函数了。但这种情况只是特例。(完美的散列函数只在EnumMap和EnumSet中得以实现,因为enum定义了固定数量的实例)。解决冲突的方式如下:

数组仅保存键的信息,而值都保存在List中。然后再对List中的元素进行线性查询。这部分查询相对会慢一些。但是如果散列函数好的话,数组每个位置就只有较少的值。因此不是查询整个List,而是快速的跳到数组的某个位置,只对很少的元素进行比较。这就是HashMap如此快的原因。借用一张别人画的图,说明HashMap的内部结构:


理解如下代码,可以更好的明白HashMap的工作原理:


为了使散列均匀,bucket(桶位)的数量通常是质数。注意:为了能够自动的处理冲突,使用了一个LinkedList,每个新元素只是直接添加到list末尾的某个特定的桶位中。

3.1、如何覆写hashcode()

要想使hashcode()实用,它必须速度快,有意义。也就是说它必须基于对象的内容生成散列码。以下是Joshua Bloch为编写一份像样的hashcode给出的指导:

1)给int变量result赋予某个非零的常量值,比如result = 17;

2)给对象内每个有意义的域f(即每个可以做equals操作的域)计算一个int散列码

域类型                                                                              计算

boolean                                                                          c=(f?0:1)

byte、short、int或char                                                c= (int)f

long                                                                                 c= (int)(f^(f>>32))

float                                                                                  c= Float.floatToIntBits(f)

double                                                                            long l = Double.doubleToLongBits(f)

                                                                                         c= (int)(l^(l>>32))

object,其equals()调用这个域的equals            c = f.hashCode()

数组                                                                                 对每个元素应用上述规则

3)合并计算得到的散列码

result =  37*result + c;

4)返回result

5)检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。

示例:一个类具有如下的两个字段:

private String s;

private int id;

public int hashCode(){

int result = 37;

result  = 37*result  +  s.hashCode();

result  = 37*result  +  id;

return result;

}

public boolean equals(Object o){

return o instanceof ClassA && s.equals(((ClassA)o).s) && id == ((ClassA)o).id

}

4、HashMap 性能调整

与HashMap相关的术语:

容量:表中的桶位数

初始容量:表在创建的时候所拥有的桶位数

尺寸:表中当前存储的项数

负载因子:尺寸/容量。空表的负载因子是0,而半满表的负载因子是0.5,以此类推。负载轻的表,则产生的冲突的可能性较小,因此对于插入和查找都是理想的。HashMap和HashSet都具有允许你指定负载因子的构造器,表示当负载情况达到该负载因子的水平时,容器将自动的增加(桶位数),实现的方式是容量大致加倍,并重新将现有对象分配到新的桶位集中(这被称为再散列)。

HashMap默认的负载因子是0.75.

原创粉丝点击