ArrayList、LinkedList、HashMap分析

ArrayList、LinkedList、HashMap分析


1. ArrayList 分析

  • 案例
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
  • 源码
// 默认容量大小为10
private static final int DEFAULT_CAPACITY = 10;
// 
private static final Object[] EMPTY_ELEMENTDATA = {};
// 初始数组(存放元素的数组)
transient Object[] elementData; // non-private to simplify nested class access
// 初始size大小(实际元素的个数)
private int size;

// 无参构造方法,从这里可以看到创建一个ArrayList并且没有没list添加任何元素的时候,默认的size为 0
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

// 创建好list之后,执行第一个add操作,因为默认的size=0,所以会调用 ensureCapacityInternal(size + 1)的方法,
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将元素赋值给数组指定的下标
    elementData[size++] = e;
    return true;
}

// ensureCapacityInternal(size + 1)
private void ensureCapacityInternal(int minCapacity) {
    // 初始数组elementData的大小为0,EMPTY_ELEMENTDATA也为0(null)
    if (elementData == EMPTY_ELEMENTDATA) {
        // Math.max(10,1) = 10,所以添加第一个元素的时候,数组的长度变为 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    
    ensureExplicitCapacity(minCapacity);
}

// add(1)之后,minCapacity = 10
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 10 - 0 > 0
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// minCapacity = 10
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;  // 0
    // newCapacity = 0 + 0 / 2(右移相当于除以2)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 0 - 10 < 0  ==> newCapacity = 10
    // 15 - 10 > 0 (第二次扩容判断)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // MAX_ARRAY_SIZE 非常大的数,所以它几乎不成立
    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);
}

/ ===========================================
// 所以在list.size() = 10的时候,如果再进行 list.add(11),那么就会有以下的操作步骤:
// 1. 进入ensureCapacityInternal(size + 1); // 此时为: minCapacity = 10 + 1 = 11
// 2. 在方法ensureCapacityInternal(11)中, 直接跳过判断,执行方法 ensureExplicitCapacity(11),并且方法中的判断成立执行grow(minCapacity)操作。
// 3. 在grow方法中: oldCapacity = 10,newCapacity = 10 + 10 >> 1 = 10 + 10 / 2 = 15,所以两个if不成立,直接进行扩容: elementData = Arrays.copyOf(elementData, newCapacity);
// 所以可以得出扩容的大小为之前容量的 1.5倍,也就是在原数组上多扩容0.5倍。

2. LinkedList 分析

  • 案例
 LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
list.add(2);
  • 源码
// 链表的长度
transient int size = 0;

// 指定第一个节点
transient Node<E> first;

// 指向最后一个节点
transient Node<E> last;

// 空构造器
public LinkedList() {
}

// 调用add方法
public boolean add(E e) {
    linkLast(e);
    return true;
}

// 添加第一个元素
void linkLast(E e) {
    // l相当于游标指针,初始指向为空(last)
    final Node<E> l = last;
    // 创建第一个新节点(左指针指向 l 为null,右指针也为null(last))
    final Node<E> newNode = new Node<>(l, e, null);
    // last和first均指向第一个节点
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

// LinkedList的静态内部类
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;
    }
}

 ==============
// 1.添加第二个结点时,仍然调用方法linkLast(2),此时l指向last,也就是l指向了第一个有效节点,创建建的新节点的左指针指向l,右指针指向last,last再指向新节点,此时l不为空,l.next指向newNode,从而将新节点和链表拼接成功。

3. ArrayList和LinkedList的区别

  • 线程是否安全:它们两个都是线程不安全的,因为方法都没有用到synchronized
  • 底层数据结构:ArrayList使用数组,LinkedList使用双向循环链表
  • 插入删除是否受位置的影响:ArrayList因为使用的是数组,所有插入的删除的时间复杂度为O(n),而LinkedList的为O(1)
  • 是否支持快速随机访问:ArrayList支持随机访问,LinkedList不支持。

4. HashSet 和 TreeSet的比较

  • HashSet
  • 基于HashCode计算元素存放的位置
  • 当存入元素的hashCode值相同时,会调用equals方法进行确认,如结果为true,则拒绝后者存入。
  • TreeSet
  • 基于排列顺序实现元素的不重复
  • 实现了SortedSet接口,对集合元素自动排序
  • 元素对象的类型必须实现Comparable接口,执行排序规则
  • 通过CompareTo方法确定是否为重复元素

4. HashMap 分析

  • 案例
// HashMap空参构造默认是创建初始容量为 16, 默认加载因子是 0.75
HashMap<String, String> map = new HashMap<>();
map.put("1", "11");
map.put("2", "22");

// 获取的是map中所有的key值
Set<String> keySet = map.keySet();
// 获取的是map中一个个的键值对
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
    // 获取每个entry中的键key和对应的值value
    System.out.println(entry.getKey() + " , " + entry.getValue());
}
  • 源码
// Hashmap的初始容量大小: 2^4 = 16 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// hashmap的数组的最大容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// jdk1.8 当链表的长度大于8时(要求map元素个数大于64),链表调整为红黑树
static final int TREEIFY_THRESHOLD = 8;
// jdk1.8 当链表的长度小于6时,红黑树转为链表
static final int UNTREEIFY_THRESHOLD = 6;
// jdk1.8,当链表的长度大于8时,并且集合的元素大于64,链表调整为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希表中的数组
transient Node<K,V>[] table;
// 元素个数
transient int size;

// 初始化hashMap时,设置加载因子
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

// 添加元素
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 添加元素是执行第一个if语句,resize()等于16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        ......
    }
}
  • 总结
# 1. HashMap刚创建的时候,table是null,为了节省空间,当添加第一个元素的时候,table的容量为16。
# 2. 当元素的个数大于阈值:16*0.75 = 12时,会畸形数组扩容,扩容后的大小为原来的2倍,目的是减少调整元素的个数。
# 3. jdk1.8,当链表的长度大于8,并且数组的总量大于等于64时,链表会自动调整为红黑树,目的是提高执行的效率。如果数组的总量小于64,会优先进行数组的扩容。
# 4. jdk1.8,当链表的长度小于6时,红黑树会转为链表。
# 5. jdk1.8以前,链表时头插入,1.8以后使用的是尾插入。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值