集合(未完)

0人阅读 评论(0) 收藏 举报
分类:

集合的表示方式

Java集合的实现是采用接口的方式。不同的具体实现类实际上是实现了多个接口。因此我们在选用集合结构时,应该从接口的角度考虑。比如要进行快速存取,就应该考虑实现了RandomAccess随机访问接口的实现类,比如ArrayList。如果想采用队列的数据结构,就应该考虑实现了Queue接口的实现类,如ArrayDeque或LinkedList。
Java在定义好接口后,并没有直接就通过实现具体接口来产生实际的类,而是先构建了几个抽象集合类,再通过对抽象集合类的继承来设计具体的集合类。这么做的好处是当我们新设计一个集合类时,不用去一次覆盖接口的每个方法,对于不常用方法可以直接从抽象类中继承过来。

迭代器

迭代器的出现使得我们可以将遍历集合的操作抽象出来,而不用针对不同的集合编写单一的遍历方式,现在我们只用编写迭代器类就可以了。迭代器类主要由4个方法构成,分别是:

  • hasNext()
  • next()
  • remove()
  • checkForComodification()

值得说明的是,迭代器的指针可以理解为指向两个元素之间,每次使用next()都会返回迭代器越过的元素。并且,每调用一次remove()之前都要调用一次next(),否则会出错。

具体接口

具体数据结构

视图

具体集合类分析

HashMap

参考:
Java 容器源码分析之 HashMap–JR’s Blog
Java HashMap工作原理及实现–YiKun
Java 8系列之重新认识HashMap–美团点评技术团队
Java HashMap工作原理–ImportNew

内部结构

HashMap使用了一个内部类Entry<K, V>来作为Node存储数据。该内部类主要有四个域,分别是K,V,K的hashcode,以及一个指向下一个Entry<K, V>的指针变量。
HashMap使用Entry<K, V>作为最小的储存单元,然后以数组table(长度始终保持为2的幂次方)为集合的基本结构,并且以该数组table的一个元素为一个桶(bin),对具有相同散列值的Entry<K, V>都放入同一个桶中,桶内采用的是链表(8个Entry<K, V>以下)和红黑树(8个以上)的结构进行保存。具体结构如图:
这里写图片描述
由此可以看出,对于Key值类,如果K是Comparable的(在红黑树内查找时候),就会对HashMap的查询有一定的加速作用。

哈希计算

HashMap的散列化原理有三步:

  • 调用Key的hashCode()得到初步哈希值h;
  • 对h调用自己的hash()方法再次散列化(主要是位操作)得到哈希值hash
  • 将hash值对数组table长度取模,得到该Key在数组中的下标。要注意的是由于数组table长度为2的幂次方,所以取模操作也可以用位操作代替

因此,如果我们能随意改动Key的hashCode()产生的Hash值,那么将在HashMap中产生影响,造成键值丢失。

扩容

HashMap的扩容是指对数组table的长度加倍,也就是将原table的长度乘以2去建立一个新数组,然后把原来的每个Entry再散列化进新数组中。值得注意的是,数组扩容为2的幂次方的一个原因是为了提高再散列化的效率,因为Java的本质储存方式为二进制的。
>

参考:YiKun’s Blog
怎么理解呢?例如我们从16扩展为32时,具体的变化如下所示:
这里写图片描述
因此元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:
这里写图片描述
因此,我们在扩充HashMap的时候,不需要重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。可以看看下图为16扩充为32的resize示意图:
这里写图片描述
这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。

HashMap的视图View–用于遍历

HashMap 提供了三种方式来遍历其中的 Entry、Key 及Value,分别是 Set<Map.Entry<K,V>> entrySet()Set<K> keySet()Collection<V> values()。这三个方法的基本用法返回的都是视图View集合,即一种遍历方式(迭代器),而不是真的返回了一个复制出来的新的Entry数组table。要弄清楚这三个方法的内部实现机制,首先要来看一下内部抽象类 HashIterator
1. HashMap构建了一个迭代器类 HashIterator,利用该类就可以对HashMap的集合进行遍历操作。
2. KeyIteratorValueIteratorEntryIterator 都继承了 HashIterator,区别只在于next() 方法返回的是 Key、Value 还是 Entry。
3. 利用KeyIteratorValueIteratorEntryIterator,可以构成Set<Map.Entry<K,V>> entrySet()Set<K> keySet()Collection<V> values()方法,以返回三种不同的View视图。

线程安全性

HashMap的是线程不安全的,因此可以采用ConcurrentHashMap类。

查看评论
    个人资料
    持之以恒
    等级:
    访问量: 4102
    积分: 614
    排名: 8万+