Java集合类的简单梳理01

一、总结

Java 集合在util([jutil])包下,主要包括Collection和Map两个接口。

Collection接口没有直接的实现类,其下包括Set,List,Queue三个接口。

Map接口Java.util包中的另一个接口,其中包括了Hashtable、HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap等。


Set中的元素是无序,不可重复的。Set中包含:AbstractSet,HashSet,TreeSet,Set接口底层实现是根据Map的,采用适配器模式。add方法即Map接口中的put(e,null)。 下面详解。

List中元素是有序,可重复的。List中的元素查询速度快,插入,删除慢。List中包含:ArrayList,LinkedList,Vector(Stack 继承了Vector)等实现类。

Queue即队列,其中主要实现类有 LinkedList,优先级队列  :PriorityQueue


二、各具体集合类的简介及联系

1.ArrayList

底层用数组实现,插入,添加,获取的的时间复杂度都为。删除的时间复杂度为。有两个标志数据,capacity为容量,size为当前元素的数量。当容量不够扩容时使用Arrays.copyOf进行复制操作,并且容量扩展为原来容量的1.5倍。DEFULT_CAPACITY为10。copyOf 方法调用了System.arraycopy方法,该方法是一个native方法。

我们可以先看一下add() 方法:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

其中每次添加元素之前都要执行一下ensureCapacityInternal()方法,我们来看一下:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);   //扩容1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

首先来看Arrays.copyof()方法。它有很多个重载的方法,但实现思路都是一样的,我们来看泛型版本的源码:

public static <T> T[] copyOf(T[] original, int newLength) {  
    return (T[]) copyOf(original, newLength, original.getClass());  
}

很明显调用了另一个copyof方法,该方法有三个参数,最后一个参数指明要转换的数据的类型,其源码如下:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
    T[] copy = ((Object)newType == (Object)Object[].class)  
        ? (T[]) new Object[newLength]  
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);  
    System.arraycopy(original, 0, copy, 0,  
                     Math.min(original.length, newLength));  
    return copy;  
} 

这里可以很明显地看出,该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。

    下面来看System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。

    具体可见:ArrayList实现原理

2.Vector

与ArrayList的区别在于Vector是线程安全的,(可以用Collections.sychronizedList()包装ArrayList)单线程情况下效率较低,同时在add操作时,ArrayList扩容为1.5倍,Vector扩容为2倍。
Vector中有提供indexOf(obj, start)接口,ArrayList没有。

3.LinkedList(1.8改动很大,需要补充)

底层用双向链表实现,插入,获取需要时间复杂度,添加、删除需要的时间复杂度。

可见这篇:LinkedList源码解析

     JDK1.8中与之前有所不同:

     私有属性:

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

    其中first指向第一个节点,last指向最后一个节点,Node为节点类:

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

节点类很简单,item存放数据,previous与next分别存放前后节点的信息(在数据结构中我们通常称之为前后节点的指针)。

4.HashTable(需补充)

Hashtable是一个线程安全的类,其方法都是sychronized的。其与HashMap的关系有点像Vector与ArrayList的关系。

Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

5.HashMap(重点)

HashMap继承了AbstractMap类,实现了Map<K,V>, Cloneable, Serializable接口。

HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

5.1 HashMap的底层实现 参考:HashMap(JDK1.8)

    在JDK1.8中,HashMap是数组+链表+红黑树实现的,如下图所示。


开放地址法和链地址法
那么通过什么方式来控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制

在理解Hash和扩容流程之前,我们得先了解下HashMap的几个字段。从HashMap的默认构造函数源码可知,构造函数就是对下面几个字段进行初始化,源码如下:

1
2
3
4
int threshold;             // 所能容纳的key-value对极限
final float loadFactor;    // 负载因子
int modCount; 
int size;

首先,Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

5.2 HashMap中的put方法

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

JDK1.8HashMap的put方法源码如下:

1     public V put(K key, V value) {
  2     // 对key的hashCode()做hash
  3     return putVal(hash(key), key, value, false , true );
  4 }
  5
  6 final V putVal( int hash, K key, V value, boolean onlyIfAbsent,
  7                boolean evict) {
  8     Node<K,V>[] tab; Node<K,V> p; int n, i;
  9     // 步骤①:tab为空则创建
10     if ((tab = table) == null || (n = tab.length) == 0 )
11         n = (tab = resize()).length;
12     // 步骤②:计算index,并对null做处理
13     if ((p = tab[i = (n - 1 ) & hash]) == null )
14         tab[i] = newNode(hash, key, value, null );
15     else {
16         Node<K,V> e; K k;
17         // 步骤③:节点key存在,直接覆盖value
18         if (p.hash == hash &&
19             ((k = p.key) == key || (key != null && key.equals(k))))
20             e = p;
21         // 步骤④:判断该链为红黑树
22         else if (p instanceof TreeNode)
23             e = ((TreeNode<K,V>)p).putTreeVal( this , tab, hash, key, value);
24         // 步骤⑤:该链为链表
25         else {
26             for ( int binCount = 0 ; ; ++binCount) {
27                 if ((e = p.next) == null ) {
28                     p.next = newNode(hash, key,value, null );
                         //链表长度大于8转换为红黑树进行处理
29                     if (binCount >= TREEIFY_THRESHOLD - 1 ) // -1 for 1st 
30                         treeifyBin(tab, hash);
31                     break ;
32                 }
                     // key已经存在直接覆盖value
33                 if (e.hash == hash &&
34                     ((k = e.key) == key || (key != null && key.equals(k))))                                            break ;
36                 p = e;
37             }
38         }
39        
40         if (e != null ) { // existing mapping for key
41             V oldValue = e.value;
42             if (!onlyIfAbsent || oldValue == null )
43                 e.value = value;
44             afterNodeAccess(e);
45             return oldValue;
46         }
47     }
 
48     ++modCount;
49     // 步骤⑥:超过最大容量 就扩容
50     if (++size > threshold)
51         resize();
52     afterNodeInsertion(evict);
53     return null ;
54 }
5.3 HashMap中的扩容机制 resize()



6.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值