声明:以下源代码使用的都是基于JDK1.8_112版本

1. ArrayList源码解析

  <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包装类才能加入到集合中去

  <2. 集合中放置的都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转换将其转换为真正需要的类型即放置进行的类型

1 ArrayList list = new ArrayList();
2 list.add(new Integer(4)); list.add("abc");
3 System.out.println((Integer)list.get(0));
4 System.out.println((String)list.get(1));

  <3. ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型数组。

    这里需要区分JDK版本的区别,jdk1.6或之前底层在扩容的时候使用的是基本乘法运算:(oldCapacity * 3)/2 + 1 ; 而在jdk1.7之后底层在扩容的时候采用位移运算,且也没有多加1操作:oldCapacity + (oldCapacity >> 1)  (我猜想应该是充分考虑提升运算性能)

    即:JDK1.6以及之前扩容规则为:1.5倍+1 ; JDK1.7以及之后扩容规则为:1.5倍

  <4. 真正的扩容是将原数组的内容复制到新数组当中,并且后续增加的内容都会放到这个新的数组当中去。

  这里贴出来jdk1.8扩容代码:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    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);
}

其中elementData定义如下:

transient Object[] elementData; // non-private to simplify nested class access

  transient Object[] elementData;,细心的盆友可以看出它的关键字是transient,看到这个关键字,都以为此数组是不可序列化的,其实不然,因为ArrayList实现了Serializable的writeObject()可以定制化,ArrayList实现writeObject并且强制将 transient elementData 序列化。那么为什么这样设计呢?个人认为是因为ArrayList内部实现是数组,大部分情况下会有空值,比如elementData的大小是10,但实际只有6个元素,那么剩下的4个元素没有实际的意义,如果直接将此标识为可序列化,那么最终会把空值同样序列化,因此将elementData设计为transient,然后实现Serializable的writeObject()方法将其序列化,只序列化实际存储的元素,而不是整个数组。当然还是个人认为,降低序列化的传输量来变向的提升性能(速度)

  这里如果有盆友不理解transient相关概念,请查看我的另一篇博客:http://www.cnblogs.com/liang1101/p/6382765.html 

  <5. boolean add(E) 和 void add(int, E) 底层使用的方法不同

  直接add对象到集合中默认是将该对象加入到数据最末端,这样也是最快的,即:elementData[size++] = e;-->将元素赋值给数组顺序的最后一位

  而指定插入位置的add方法则需要从指定位置+1的位置开始往后使用System.arraycopy方法赋值数组操作,之后再将对应的元素赋值,具体源代码:

1 public void add(int index, E element) {
2     rangeCheckForAdd(index);
3 
4     ensureCapacityInternal(size + 1);  // Increments modCount!!
5   //public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
6 System.arraycopy(elementData, index, elementData, index + 1, size - index); 7 elementData[index] = element; 8 size++; 9 }

对应还有set(int, E)、remove(int)、remove(Object)等都是类似的行为,即数组是怎么操作的ArrayList底层就应该是怎么操作的。

2. LinkedList源码解析

 LinkedList底层源码是采用双向链表的方式实现的,具体双向列表初始化定义如下:

 1 1 private static class Node<E> {
 2  2     E item;  //结点的值
 3  3     Node<E> next;  //结点的后继指针
 4  4     Node<E> prev;  //结点的前驱指针
 5  5    //构造函数完成Node成员的赋值
 6  6     Node(Node<E> prev, E element, Node<E> next) {
 7  7         this.item = element;
 8  8         this.next = next;
 9  9         this.prev = prev;
10 10     }
11 11 }

  何为双向列表,单向链表为通过后继可以找到下一个指向的元素;双向链表为既可以通过后继找到下一个指向的元素,也可以通过前驱找到前一个元素。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些。那么剩下的add、remove等方法就是改变相应的指向即可,这个实现起来就很简单了,这里就不再做详细的说明了,不会的可以查看一下源代码就明白了。

3. HashSet源码解析

首先,咱们经常使用的HashSet的无参构造函数,那么来看一下对应的源码:

/**
 * Constructs a new, empty set; the backing HashMap instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

从以上源代码可以看出,HashSet底层是使用HashMap来实现的,且对应HashMap默认初始长度为16,对应的负载因子为0.75。那我们再看看常用的add()、remove()、iterator()等方法源代码:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

public Iterator<E> iterator() {
    return map.keySet().iterator();
}

其中PRESENT常量定义如下:

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

  从以上源代码发现,当使用add()方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的Map对象的key,而value则是同一个Object对象(该对象我们其实是用不上的)

那么既然HashSet底层直接使用的是HashMap进行维护,那么我们的重点就是要分析HashMap底层源代码到底是什么情况。

4. HashMap源码解析

  首先,咱们经常使用的HashMap的无参构造函数,那么来看一下对应的源码:(JDK1.8代码)

/**
 * Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

而对应的JDK1.7和JDK1.6代码有一定的区别,如下:

/**
 * Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
}

  由上面不同版本比较发现,在1.8之后其他不必要的参数都已经修改为全局默认值,不需要在每次申请的时候再进行开辟。我的理解可能为了提升创建对象的性能,可见java底层为了提升性能可谓是下足了功夫。

  再来看下他们共同的常量设置:

/**
 * The default initial capacity - MUST be a power of two.
 * JDK1.8
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka-->as known as(亦称) 16
//static final int DEFAULT_INITIAL_CAPACITY = 16; //JDK1.7 OR JDK1.6源代码

static final int MAXIMUM_CAPACITY = 1 << 30;

static final float DEFAULT_LOAD_FACTOR = 0.75f;

  看见了没,这么细小的优化真的是"无所不用其极"了,java一直秉承着:快才是王道的真理,做的很到位啊!其中需要注意到注释标红的地方,意思是对应的HashMap初始大小"必须为2的整数倍"才可以,这点需要注意,具体原因请继续往后看,后面有详细的解释说明。

  通过JDK1.7的HashMap构造函数可以发现并推断,HashMap底层实现也是基于数组实现的,但是该数组为Entry对象数组,不过这点通过查看源代码发现1.7之前是直接使用Entry对象来操作的,在1.8之后换为Node对象了,它们两者都是继承自Map.Entry<K,V>,所以都是一样的,我猜想应该是为了区别Map中的Entry对象防止混用,故将底层封装对象该名为Node,具体就上源代码看吧:

JDK1.8源码:

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}
View Code

相关文章: