Java面试宝典之集合

一 概念

集合就是可以存储一组数据的一种数据结构

可以分为两大类

Collection:

        List:用于存储一组数据,元素可以重复,有下标

        Set:用于存储一组数据,元素不可以重复,无下标

Map: 用于存储键值对key->value,key不可以重复

       

二 实现类

List:

        线程不安全:

                ArrayList:底层通过数组实现,适合查询多,增删少

                LinkedList:底层通过双向链表实现,适合增删多,查询少

        线程安全:

                CopyOnWriteArrayList:底层ReentrantLock + 数组实现

                Vector:底层synchronized + 数组实现

Map:

        线程不安全:

                HashMap:

                        JDK1.7:底层通过数组 链表实现

                        JDK1.8:底层通过数组 + 链表 + 红黑树实现

        线程安全:

                HashTable:底层通过synchronized  + 数组 链表实现

                ConcurrentHashMap:                        

                        JDK1.7:底层通过Segment数组 + 数组链表实现(Segment 继承ReentrantLock)

                        JDK1.8:底层通过synchronized  + 数组 + 链表 + 红黑树实现

Set:

        线程不安全:

                HashSet:底层通过HashMap实现

        线程安全:

                CopyOnWriteArraySet:底层通过CopyOnWriteArrayList实现

三 源码解析

ArrayList:

  核心属性:

    // 默认容量10,但是new List时并未真正初始化数组
    private static final int DEFAULT_CAPACITY = 10;

    //用于初始化时的默认赋值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 用于真正存放元素的数组, transient 是Java关键字,作用是序列化的时候忽略该字段
    transient Object[] elementData; // non-private to simplify nested class access

    // 真实存在元素的个数,List.size()返回值
    private int size;

新增元素:

//初始化List
public ArrayList() {
    //初始化并未真正的初始化数组,而是指向一个空的数组引用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//新增元素
public boolean add(E e) {
    //计算数组的容量,第一次初始化数组,后面的根据条件判断是否扩容, size是当前元素个数,+1代表add元素时需要的容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //把元素放入数组
    elementData[size++] = e;
    return true;
}


private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//用于计算所需最小数组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //判断当前数组是否为初始化时的默认数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //返回DEFAULT_CAPACITY (10), minCapacity中大的,初始化返回DEFAULT_CAPACITY 
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
 }


private void ensureExplicitCapacity(int minCapacity) {
    //用于防止并发修改
    modCount++;

    //所需最小容量与当前数组对比, 如果大于就需要扩容,初始化时elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 所以length == 0
    if (minCapacity - elementData.length > 0)
        //扩容
         grow(minCapacity);
}


private void grow(int minCapacity) {
        // 临时保存老的数组大小
        int oldCapacity = elementData.length;

        //新数组容量 = 老容量 + 老容量/2 相当于扩容1.5
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        //如果需要容量大于计算出来的就用所需容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 
        //如果需要容量大于MAX_ARRAY_SIZE;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        //扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
}

//如果需要容量大于MAX_ARRAY_SIZE  返回Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
       throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
       Integer.MAX_VALUE :
       MAX_ARRAY_SIZE;
}

LinkedList

核心属性:

//当前实际存放的元素数量
transient int size = 0;

//第一个元素
transient Node<E> first;

//最后一个元素    
transient Node<E> last;

新增元素:

//初始化
public LinkedList() {
}

//新增元素
public boolean add(E e) {
   linkLast(e);
   return true;
}

void linkLast(E e) {
//临时保存最后一个元素
  final Node<E> l = last;、
//new 新元素, 第一个参数上一个元素, 第二个参数是当前元素, 第三个参数 下一个元素
  final Node<E> newNode = new Node<>(l, e, null);
//把当前元素 赋值给last
  last = newNode;
//如果last为null 相当于初始化,把当前元素也赋值给first
  if (l == null)
      first = newNode;
  else
      //把旧的最后一个元素的指针指向当前元素
      l.next = newNode;
      size++;
      modCount++;
}

//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;
        }
    }

CopyOnWriteArrayList 

写时复制,每次放入元素先加锁,复制数组创建新数组长度+1,替换旧数组

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;
    
    //控制并发
    transient final ReentrantLock lock = new ReentrantLock();

    //存放元素的数组
    private volatile transient Object[] array;

    final Object[] getArray() {
        return array;
    }

   
    final void setArray(Object[] a) {
        array = a;
    }

   
    //构造器默认是0的数组
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //防止并发
        lock.lock();
        try {
            //获取当前数组
            Object[] elements = getArray();
            int len = elements.length;
            //数组赋值,新长度为旧数组长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //放入元素
            newElements[len] = e;
            //替换存放元素的数组
            setArray(newElements);
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    
}

HashSet

核心属性:

//底层核心就是HashMap
private transient HashMap<E,Object> map;

//因为Map是键值对,所以Set中的元素就是Key, 值就是PRESENT
private static final Object PRESENT = new Object();

 新增元素:

//初始化
public HashSet() {
    map = new HashMap<>();
}

//新增元素
public boolean add(E e) {
   return map.put(e, PRESENT)==null;
}


//为什么value放入的是PRESENT不是NULL??
//remove方法返回boolean, 调用的是 map.remove(o)
//map的remove方法返回的是key对应的value, 如果没有返回null
//如果value放入的是null, 那么map.remove()不管这个key是否存在,返回的一定是null
//所以set中放入的value是PRESENT, 用于判断是否存在某个元素并成功移除
public boolean remove(Object o) {
   return map.remove(o)==PRESENT;
}

CopyOnWriteArraySet 

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;
    //底层就是CopyOnWriteArrayList
    private final CopyOnWriteArrayList<E> al;

    //构造器
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    
    //新增元素
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }

    
}

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    public boolean addIfAbsent(E e) {
        //加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //创建一个新数组长度是旧数组+1
            Object[] newElements = new Object[len + 1];
            //遍历旧数组看是否有相同
            for (int i = 0; i < len; ++i) {
                //已存在返回false
                if (eq(e, elements[i]))
                    return false; // exit, throwing away copy
                else
                    //不存在复制当前元素到新数组
                    newElements[i] = elements[i];
            }
            //把新加的元素放入数组
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

      

HashMap

JDK1.7

JDK1.8

ConcurrentHashMap

JDK1.7

JDK1.8

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值