Java 集合总结

Java集合

Java 集合都在Java.util包下,分为两种Collection和Map
Collection:单列存储,只存储一个元素
Map:双列存储,键值对存储方式

单列集合体系结构

在这里插入图片描述

List:有序,可重复(实现类:ArrayList,LinkedList,Vector)

常用方法:
增:add(index,element),addAll(index,collection)
删:remove(index),removeAll(collection),clear()
改:set(index,element)
查:get(index),subList(from,to),listInerator(),indexOf(object),size()
判断:isEmpty(),contains(collection)

ArrayList
底层实现:动态数组
特点:线程不安全,效率高,因为有索引下标,所以支持随机访问,
	 查询快,增删慢,随机查询优于LinkedList
扩容机制:默认初始化是10,其实并不是,当第一次添加元素的时候长度扩展为10
		如果使用无参构造初始化,会初始化一个长度为0的空集合,
		如果使用带参构造初始化,那么去判断参数是否大于零,如果大于零,
		那么参数就是指定的数组的长度,等于零的话,就去初始化一个空集合,
		小于零的话,抛出异常IllegalArgumentException(非法长度)
核心是grow方法,数组的扩容机制,以当前数组的1.5倍进行扩容
	/**
	* ArrayList 源码分析
	**/
	//默认的初始化容量
    private static final int DEFAULT_CAPACITY = 10;
    //初始化一个空集合长度为0
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //初始化一个空集合长度为0
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存储ArrayList元素的数组缓冲区,当第一次添加元素时,长度扩展为默认长度就是10
    transient Object[] elementData;
    //实际数组长度默认为0
    private int size;
    //数组最大长度Integer的最大值减去8,超出的话,JVM就不在给数组分配,抛出OutOfMemoryError
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //带参构造
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    //无参构造
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //扩容机制
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //当前数组长度>>向右移1位,就是除2,就是原来大小+原来大小的一半就是1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //判断如果扩容后的值小于指定的参数值,那么将扩容值变为参数值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //判断如果扩容后的值是否大于JVM能分配的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //copy数组,改变容量大小
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //构造一个包含指定元素的集合 不常用
    public ArrayList(Collection<? extends E> c) {
    	//转换到当前集合
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //判断如果不是object类型是别的类型,需要当前集合处理一下,
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
LinkedList
单向链表:每一个元素都知道自己的下一个元素是谁,是单方向的
双向链表:每个元素都知道自己的上一个元素和下一个元素是谁,双方向
底层实现:双向链表
特点:链表中每一个元素称为Node对象,LinkedList是链表实现,存储过程中,每一个Node
	 对象都有自己的前驱和后继,当然也可以定位增删改查,只不过它不能像ArrayList一
	 样直接定位元素,只能从第一个元素或最后一个元素两个方向向后循环遍历查找,效率低
	 。下列源码可看出,它的增删改查大部分都和第一个和最后一个挂钩,就和ArrayList
	 下标直接定位一样,效率高,线程不安全,查询慢,增删快
扩容机制:双向链表实现的,所以说没有默认初始化容量,也没有扩容机制
	/**
	* LinkedList 源码分析
	**/
	//列表中元素的数量 默认0
	transient int size = 0;
	//指向第一个节点
	transient Node<E> first;
	//指向最后一个节点
	transient Node<E> last;
	//获取链表中第一个节点
	public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    //获取链表中最后一个节点
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
    //删除链表中第一个节点
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    //删除链表中最后一个节点
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    //新增链表第一个节点
    public void addFirst(E e) {
        linkFirst(e);
    }
    //新增链表中最后一个节点
    public void addLast(E e) {
        linkLast(e);
    }
    /**
    * 上面的方法都是包装,核心在下面
    **/
    //将e插入第一个节点位置
    private void linkFirst(E e) {
	    //第一个节点
        final Node<E> f = first;
        //构建一个节点并指定该节点的前驱和后继
        final Node<E> newNode = new Node<>(null, e, f);
       	//向第一个节点插入新节点
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }
    //将e插入最后一个元素 
    void linkLast(E e) {
    	//获取最后一个节点
        final Node<E> l = last;
        //构建一个节点并指定该节点的前驱和后继
        final Node<E> newNode = new Node<>(l, e, null);
        //向最后一个节点插入新节点
        last = newNode;
        //判最后一个节点是否为空
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    //将e插入到指定元素succ之前的位置
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        //构建节点并指定前驱和后继
        final Node<E> newNode = new Node<>(pred, e, succ);
        //将succ(指定元素)前驱指向新节点元素
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }
    //将第一个元素从链表中移出
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null;
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
    //将最后一个元素从链表中移出
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null;
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }
    //移除链表中指定的节点
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }
    //返回指定索引处的节点
    Node<E> node(int index) {
        // assert isElementIndex(index);
		//判断index是在链表前一半中从头结点开始,后一半的话,从最后一个开始
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    //构建一个node节点,就是在插入数据时通过它去构建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;
        }
    }
	/**
	* 以下是遍历的源码,我不是很了解,只知道它和上面一样要从第一个节点或
	* 最后一个节点去逐个遍历,遍历LinkedList要注意,使用foreach
	* 就是迭代器,如果使用随机获取,效率相当低就是普通的for循环
	**/
	public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
	}
	private class ListItr implements ListIterator<E> {
	    private Node<E> lastReturned;
	    private Node<E> next;
	    private int nextIndex;
	    private int expectedModCount = modCount;
	    ListItr(int index) {
	        // assert isPositionIndex(index);
	        next = (index == size) ? null : node(index);
	        nextIndex = index;
	    }
	    public boolean hasNext() {
	        return nextIndex < size;
	    }
	    public E next() {
	        checkForComodification();
	        if (!hasNext())
	            throw new NoSuchElementException();
	
	        lastReturned = next;
	        next = next.next;
	        nextIndex++;
	        return lastReturned.item;
	    }
	}

Vector
Vector是个比较老的集合,我自己也不常用
底层实现:动态数组
特点:线程安全,效率低,查询快,增删慢,通过下标,支持而随机访问,
	 和ArrayList区别在于ArrayList线程不安全,Vector线程安全(有synchronized关键字)
扩容机制:当扩容量大于0时,扩容机制为原数组长度加上扩容量,否则默认初始容量10,超过之后以当前长度的2倍进行扩容
	/**
	* Vector源码解析
	**/
	//用来保存元素
	protected Object[] elementData;
	//数组中元素的个数
	protected int elementCount;
	//保存数组的扩容量
	protected int capacityIncrement;
	//数组最大长度为int类型的最大长度减去8
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	//构造一个指定初始容量和指定的扩容量的数组
	public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)//小于零 则抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];//赋值
        this.capacityIncrement = capacityIncrement;//赋值
    }
    //构造一个指定初始化容量和0扩容量的数组
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    //构造一个默认初始化容量为10的数组
    public Vector() {
        this(10);
    }
    //构造一个指定内容的集合数组
    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();//赋值内容
        elementCount = elementData.length;//赋值长度
        // defend against c.toArray (incorrectly) not returning Object[]
        // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
        if (elementData.getClass() != Object[].class)//判断筛选
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }
    //vector的扩容机制
    private int newCapacity(int minCapacity) {
        int oldCapacity = elementData.length;//元数组元素的长度
        //扩容:如果扩容量(capacityIncrement)大于零,那么扩容机制为原数组元素的长度加扩容量
        //     如果扩容量(capacityIncrement)不大于零,那么扩容机制为原数组元素的长度加上原数组元素长度就是乘于二
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        //如果新容量小于指定容量 抛出java 堆溢出异常
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0)
                throw new OutOfMemoryError();
            return minCapacity;//返回指定容量
        }
        //如果新容量小于数组指定最大容量,返回新容量
        //否则以下方法处理
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
    //处理指定容量大于数组最大指定容量
    private static int hugeCapacity(int minCapacity) {
    	//小于零抛出java 堆异常
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        //指定容量大于数组最大指定容量返回int类型的最大存储长度,否则返回数组最大指定容量
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
总结:

ArrayList: 线程不安全,查询快,增删慢,支持随机查询(索引下标),
LinkedList:线程不安全,增删快,查询慢,
Vector: 线程安全,效率低,,增删慢,查询快,支持随机查询(索引下标)
应用场景明显,各有所长,根据实际业务需求选择!

Set:无需,不可重复(实现类:HashSet,TreeSet)

常用方法:
增:add(element),addAll(collection)
删:remove(object),removeAll(collection),clear()
查:get(index),size()
判断:isEmpty(),contains()
区别list:hashCode(),equals(object)

HashSet
底层实现:数组+链表+红黑树
特点:HashMap中的Kay部分,线程不安全,按hash算法来存储元素
扩容机制:默认初始化容量为16,当超过整个数组的0.75,会以当前长度的2倍进行扩容
检索是否重复:
当你想HashSet加入值时,会去先调用HashCode来获取元素的hashcode值,
两个对象去比较hashcode值,如果两个元素的hashcode值相等,它不会去确
定为相同,然后会去调用equals方法来去比较,如果equals返回true,表示
两者整的相同,那么hashSet不会让你的操作成功!
	/**
	* HashSet源码解析
	**/
	//用来保存元素,看出其实是map
	private transient HashMap<E,Object> map;
	//hashSet构造器
	public HashSet() {
        map = new HashMap<>();
    }
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    //看hashSet的底层原理 建议先去了解hashMap hashSet的底层其实都是hashMap在工作包括基本操作(不过多解释)
TreeSet
底层实现:红黑树
特点:TreeMap中的Kay部分,线程不安全,有序,
排序方式:支持两种排序方式,自然排序和定制排序,默认为自然排序,
		自然排序:通过compareTo()方法将两个对象进行大小比较,然后将集合元素按升序排序
	//看treeMap的底层
Map:键值对存储方式,键唯一,值不唯一(实现类:hashMap,treeMap)
双列集合的体系图

在这里插入图片描述

HashMap:

1.8之前:
hashMap在1.8之前底层实现是数组+链表来解决hash碰撞,可以理解为,一个链表数组数组的每个元素是
一个个的链表,当存入两个值时,值不同,但是得到的hash值是相同的,就会出现hash碰撞,而当遇到
hash碰撞时,hash值存放在数组,将碰撞的值放在对应的链表,再次碰撞时,存入这个元素的下一个,
遍历时,get到键,需要遍历对应的链表来获取对应的值,而当hash值相同的值较多时,获取的效率极低
,看1.8之后的优化
在这里插入图片描述

1.8之后
hashMap在1.8之后底层实现改为数组+链表+红黑树(平衡二叉树),其实在使用时没有达到默认指定阈值之前,还是数组+
链表形式的,当hash值碰撞的元素值数量达到指定阈值(默认是8)之后链表转换为红黑树形式,而当hash值
碰撞的元素值数量达到指定默认阈值(默认是6)后悔红黑树转换为链表形式,,这种方式来优化hash值碰撞
的值很多的场景下,增加查询的效率
在这里插入图片描述

底层实现:数组+链表+红黑树
特点:键值对存储方式,键不可重复,值可重复,线程不安全,允许为空,在jdk1.8之前采用数组+链表,1.8之后改为数组+链
	 表+红黑树
扩容机制:默认初始化容量为16,当元素的长度达到整个数组的0.75,会以当前长度的2倍进行扩容,你也可以指定初始化容量
		和加载因子,建议不要去更改,既然默认是160.75,那么肯定有它对空间节省或者效率高低的一个比较好的选择
	/**
	* hashMap源码解析:
	**/
	//默认初始化容量为16
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
	//默认存储最大容量为1<<30 (1073741824)
	static final int MAXIMUM_CAPACITY = 1 << 30;
	//hashMap的扩容加载因子
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	//指链表转换红黑树的阈值,当链表的元素超过之后,转换为树结构存储
	static final int TREEIFY_THRESHOLD = 8;
	//指红黑树转换链表的阈值,当元素小于之后,转换为链表结构存储
	static final int UNTREEIFY_THRESHOLD = 6;
	//桶转换为红黑树的阈值
	static final int MIN_TREEIFY_CAPACITY = 64;
	//看过LinkedList底层的一定知道node节点,hash值取决保存的位置,键值,和链表下一个元素封装为节点node
	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//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;
        }
    }
    //用于遍历map集合
    transient Set<Map.Entry<K,V>> entrySet;
    //保存集合中键值对的数量
    transient int size;
    //加载因子
    final float loadFactor;
    //构造方法
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)//指定初始化容量小于零抛出异常
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)//指定初始化容量大于最大存储容量是,将初始化容量改为最大容量
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))//指定加载因子小于等于零或是非法数字 抛异常
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor; //赋值加载因子
        this.threshold = tableSizeFor(initialCapacity); 
    }
    //构造指定初始化容量默认的加载因子 0.75
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    //默认构造方法
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //将指定集合存入新集合中
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值