首先是前面一些英文的翻译
hashtable基于map接口的实现,这个实现提供了map所有可选的操作,并且允许null值和null键。hashmap类和hashtable大致相同,除了hashmap是非同步的,并且hashmap是允许null的。这类不保证map中键值对的顺序,不保证map中的顺序
假设hash函数把元素分散到各个桶里,这个类的基础操作(get/put)只需要常量级的操作时间。对集合视图的迭代需要与实例的“容量”(桶的数量)加上其大小(键值映射的数量)成比例的时间。所以,如果迭代效率很重要的话,不要把初始容量设的太大,也不要把load factor设的太小。
一个hashmap的实例的初始容量和加载因子影响着它的性能。容量是哈希表的桶数量,初始容量就是哈希表建立时的容量。加载因子代表哈希表装到多满时进行扩容。当哈希表中项的数量超出加载因子乘以当前容量的乘积,哈希表会进行重新哈希(哈希表内部结构会变化),为了让哈希表的桶数量翻倍。
通常的规则是,默认加载因子是0.75,这在时间和空间之间是比较好的一个平衡。更高的值降低了空间花销但是提高了寻找的开销(反应在hashmap中最多的操作 get和put)。在设置初始容量时应考虑希望存储在map内的元素数量和加载因子,为了最小化重新哈希的次数。如果初始容量 > (元素的最大数量 / 加载因子)将不会发生重新哈希
如果要把很多键值对存在hashmap中,应该在创建时设置足够大的初始容量,这样可以更有效的存储,避免了在容器增长时多次重新哈希操作。使用大量相同的key毫无疑问会降低任何哈希表的时间表现。为了渐少影响,当key可以比较的时候,hashmap将会使用比较顺序来帮助打破关系。
这实现是不同步的。如果多线程同时访问一个hashmap,并且至少一个线程会改变结构,必须保证在外部同步(结构修改就是任何会增加或删除键值对的操作,只是改变了key对应的值不是改变结构)这通常用包含着map的对象实现同步。
如果不存在这对象,map应该包在{Collections#synchronizedMap Collections.synchronizedMap}方法里面。最好是在创建的时候,为了避免偶然对map的不同步访问
Map m = Collections.synchronizedMap(new HashMap(...))
该类返回的所有迭代器都是 “快失败” 的,如果创建了迭代器后map遇到了结构性改变,调用除了迭代器自己的删除方法以外的方法,将会抛出ConcurrentModificationException异常。因此,面对同步操作,迭代器迅速干净的失效,而不是在未来的未确定时间冒任意,非确定性行为的风险。
请注意,迭代器的快速失败行为无法保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。快速失败的迭代器在最好的情况下抛出ConcurrentModificationException异常。因此,因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于debug
此映射通常用作分区(分区)哈希表,但是当分区变得太大时,它们将转换为TreeNodes的分区,每个分区的结构与java.util.TreeMap中的类似。大多数方法尝试使用普通的bin,但在适用时中继到TreeNode方法(只需检查节点的实例)。 TreeNodes的Bin可以像其他任何一样遍历和使用,但是当人口过多时还支持更快的查找。但是,由于正常使用的绝大多数垃圾箱都不是人口过多,因此在表格方法的过程中可能会延迟检查树箱的存在。
树容器(即,其元素都是TreeNodes的容器)主要由hashCode排序,但在tie的情况下,如果两个元素具有相同的“C类实现Comparable <C>”,则键入然后将其compareTo方法用于排序。 (我们保守地通过反射检查泛型类型以验证这一点 - 请参阅方法equivalentClassFor)。当密钥具有不同的哈希值或可订购时,增加树容器的复杂性对于提供最坏情况的O(log n)操作是值得的。因此,在hashCode()方法返回值很差的偶然或恶意用法中,性能会优雅地降级分布式,以及许多密钥共享hashCode的那些,只要它们也是可比较的。 (如果这些都不适用,与不采取任何预防措施相比,我们可能在时间和空间上浪费大约两倍。但是,唯一已知的情况源于糟糕的用户编程实践,这些实践已经非常缓慢,这几乎没有什么区别。)
由于TreeNodes的大小约为常规节点的两倍,因此只有当bin包含足够的节点以保证使用时,我们才使用它们(请参阅TREEIFY_THRESHOLD)。当它们变得太小(由于移除或调整大小)时,它们会转换回普通箱。在具有良好分布的用户hashCodes的用法中,很少使用树容器。理想情况下,在随机hashCodes下,bin中节点的频率遵循泊松分布(http://en.wikipedia.org/wiki/Poisson_distribution),参数平均值约为0.5,默认调整阈值为0.75,尽管由于调整粒度而导致的大差异。忽略方差,列表大小k的预期出现是(exp(-0.5)* pow(0.5,k)/ factorial(k))。第一个值是:
0:0.60653066
1:0.30326533
2:0.07581633
3:0.01263606
4:0.00157952
5:0.00015795
6:0.00001316
7:0.00000094
8:0.00000006
更多:不到千万分之一
树bin的根通常是它的第一个节点。但是,有时(目前仅在Iterator.remove上),根可能在其他地方,但可以在父链接之后恢复(方法TreeNode.root())。
所有适用的内部方法都接受哈希码作为参数(通常从公共方法提供),允许它们相互调用而无需重新计算用户hashCodes。大多数内部方法也接受“tab”参数,通常是当前表,但在调整或转换时可能是新的或旧的。
当bin列表被树化,拆分或未解析时,我们将它们保持在相同的相对访问/遍历顺序(即,字段Node.next)中以更好地保留局部性,并略微简化对调用iterator.remove的拆分和遍历的处理。在插入时使用比较器时,为了保持整个重新排序的总排序(或者尽可能接近),我们将类和identityHashCodes作为绑定器进行比较。
普通vs树模式之间的使用和转换由于子类LinkedHashMap的存在而变得复杂。请参阅下面的钩子方法,这些钩子方法定义为在插入,删除和访问时调用,允许LinkedHashMap内部以其他方式保持独立于这些机制。 (这也要求将地图实例传递给可能创建新节点的一些实用程序方法。)
类似于并发编程的基于SSA的编码风格有助于避免在所有扭曲指针操作中出现混叠错误。
(以上翻译很大一部分用Google翻译完成 有空有时间再直接翻吧 下面讲代码了)
前面的代码先是讲了一些变量
DEFAULT_INITIAL_CAPACITY// 默认的初始容量,必须是2的幂
MAXIMUM_CAPACITY//最大容量,必须是2的幂且少于1<<30
DEFAULT_LOAD_FACTOR//默认的加载因子
TREEIFY_THRESHOLD//当桶里的数量超过这阈值时,桶里的结构变成树
UNTREEIFY_THRESHOLD//当桶里的数量少于这阈值,从树变回线性
MIN_TREEIFY_CAPACITY//当表里的数量超过这阈值,表里的结构变成树
然后声明静态内部类Node<K,V> implements Map.Entry<K,V>,提供一些基本的get set toString方法
hashCode方法返回的值是用key的hash值做基数,value的hash值做幂
equals方法先判断传入对象是否和this是同一个对象,是就返回true,如果不是再判断key和value的hash值是否同时相等,同时相等返回true,否则返回false
然后是static final方法hash,传入对象,如果对象为null则返回0,否则返回(对象本身的hashcode值 ^ hashcode >>> 16);所以如果对象的hashcode没有16位,那么肯定是返回1
然后是comparableClassFor函数,没有看懂,注释说返回对象实现的可比较接口,从而可以调用接口的compareTo函数来对两个对象做比较,从而判断大小,如果对象没有实现可比较接口返回null
方法compareComparables,传入一个可比较泛型类和两个对象,第一个对象是泛型类的实例,如果第二个对象为null或不是泛型类的实例返回0(意味着相等)否则用第一个对象调用compareTo和第二个对象做比较关于比较函数有说明(一个负整数,零或正整数,因为此对象小于,等于或大于指定的对象。)
函数tableSizeFor一段非常骚的进位操作,传入希望得到的容量,返回比这容量大的最小的二的幂(待我学成归来再仔细看看这段代码的玄机)
变量transient Node<K,V>[] table;长度必须是2的幂,会根据需要调整大小,这里的node就是上面声明的静态类
注释说是装缓存的transient Set<Map.Entry<K,V>> entrySet;
还有表示当前map里元素数量的transient int size;
注释说是表示hashmap结构修改次数的transient int modCount;
表示下一次扩容的阈值int threshold;
加载因子final float loadFactor;
然后就到public方法辣