Java集合

集合

个人笔记----23.4.21

Collection

单列集合(value)
在这里插入图片描述

Iterator

Iterable接口中里有方法iterator(),用以返回一个实现了Iterator接口的对象(迭代器)

迭代器遍历

Iterator iterator = list.iterator();
while(iterator.hasNext()){
	Object dog = iterator.next();
	system.out.println(dog);
}

增强for循环遍历

底层使用的也是迭代器

for(Object dog : list){
	system.out.println(dog);
}

List

List接口是Collection接口的子接口

  • List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复

  • List集合中的每个元素都有其对应的顺序索引, 即支持索引。

  • List容器中的元素都对应一 个整数型的序号记载其在容器中的位置,可以根据序号取元素

  • JDK API中List接口的实现类有

    AbstractList,AbstractSequentialList,ArrayList,AttributeList,CopyOnWriteArrayList, LinkedList,RoleList,RoleUnresolvedList ,Stack,Vector

ArrayList

  • 可以放入null,甚至多个
  • 是由数组来实现数据存储的
  • 基本等同于Vector,但线程不安全(效率高)

底层结构和源码分析:

  • ArrayList中维护了一个Object类型的数组transient Object[] elementData;

    transient:瞬间,短暂的,表示该属性不会被序列化

  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0, 第1
    次添加,则扩容elementData为10, 如需要再次扩容,则扩容elementData为1.5倍。

  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容,
    则直接扩容elementData为1.5倍。

  • 源码分析

    //创建了一个空的elementData数组={}
    public ArrayList() {
        this.elementData =DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    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);
        }
    }
    //1.先确定是否要扩容 2.赋值
    public boolean add(E e) {
    		ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //确定minCapacity,第一次扩容为10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //1.modCount记录集合被修改的次数2.elementData大小不够就调用grow()扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //第一次newCapacity=10,以后1.5倍,扩容使用的是Arrays.copyOf(),底层为c语言实现
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍
        if (newCapacity - minCapacity < 0) //初始是0就扩10
            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);
    }
    
    

Vector

  • 底层也是一个对象数组,protected Object[] elementData;

  • 线程同步,带有synchronized

  • 扩容机制:无参默认10,满了2倍扩容;指定大小直接2倍

  • 源码分析

    //构造器
    public Vector() {
        this(10);
    }
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
    //add,基本跟ArrayList大同小异
    public synchronized boolean add(E e) {
        modCount++; //
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    private void ensureCapacityHelper(int minCapacity) {
        // 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 + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

LinkedList

  • 底层实现了双向链表和双端队列的特点

  • 可以添加任意元素(包括null)并且可重复

  • 线程不安全

  • LinkedList底层维护了一个双向链表

  • LinkedList中维护了两个属性first和last分别指向首节点和尾节点,每个节点(Node对象) ,里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表

  • 所以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;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        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;
        }
    }
    //删除
    public E remove() {
        return removeFirst();
    }
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    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; // help GC 地址为空,交给GC清理
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
    //...其他类似,就是一个双向链表
    

Set

  • 无序(添加和取出的顺序不一致),没有索引

  • 不允许重复元素,所以最多包含一个null

  • JDK API中Set接口的实现类有

    AbstractSet,ConcurrentHashMap. KeySetView,ConcurrentSkipListSet ,CopyOnWriteArraySet, EnumSet, HashSet,JobStateReasons,LinkedHashSet ,TreeSet

HashSet

  • 底层是HashMap(数组+链表+红黑树),就是一个邻接表,当链达到一定长度(8)就变成红黑树
  • 可以放null,但只有一个,不能重复
  • 不保证元素有序,取决于hash后,再确定索引的结果
  • 不能有重复元素/对象

经典面试题

set.add(new Dog("tom"));//0K 
set.add(new Dog("tom"));//0k

set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了。

hashSet底层原理:

  1. HashSet 底层是HashMap
  2. 添加一个元素时,先得到hash值-会转成->索引值
  3. 找到存储数据表table ,看这个索引位置是否已经存放的有元素
  4. 如果没有,直接加入
  5. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则
    添加到最后
  6. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认
    是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
  • 源码

    public HashSet() {
        map = new HashMap<>();
    }
    //add
    public boolean add(E e) {
        return map.put(e, PRESENT)==null; //PRESENT只起到占位作用
    }
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //hash(key)得到key对应的hash值,hash值不和hashCode完全等价
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //无符号右移16位+^
    }
    //核心代码
    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 ((tab = table) == null || (n = tab.length) == 0) //table为hashmap中邻接表数组
            n = (tab = resize()).length; //该函数(扩容)见下
        if ((p = tab[i = (n - 1) & hash]) == null) //根据key,得hash,计算其应存table中位置
            tab[i] = newNode(hash, key, value, null);//并赋给p,然后判断p为空就newNode
        else {
            Node<K,V> e; K k; //辅助变量
    /**当前索引位置对应的链表第一个元素和准备添加的key的hash值一样,并且满足条件(1.准备加入的key和p指向的Node结点的key是同一个对象2.不是同一个对象但内容相同(equals())就不能加入*/
            if (p.hash == hash && 
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p; 
            else if (p instanceof TreeNode)//判断p是一颗红黑树调用putTreeVal添加
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//如果table对应索引位置已经是链表,就for循环比较
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);//添加元素后达到8个链表树化(该方法见下)
                        break;//与每个元素比较,都不相同,加入到链表最后,break
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;//与元素比较过程中如果有相同,直接break
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) //大于临界值扩容(只要增加了元素size++,不一定要填充table)
            resize();
        afterNodeInsertion(evict); //留给hashmap子类(如linkedhashma)实现,做一些自己的动作
        return null;
    }
    //扩容
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //容量到0.75
        }                                   //就扩容,DEFAULT_LOAD_FACTOR(0.75)为加载因子
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    //树化(当链表长度到8个,且table到了64)
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) //64
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
    

hashset扩容机制详解

  • HashSet底层是HashMap,第一次添加时,table 数组扩容到16,

    临界值(threshold)=16*加载因子(loadFactor=0.75) = 12

  • 如果table数组使用到了临界值(12),就会扩容到162 = 32,新的临界值就是
    32
    0.75 = 24,依次类推

  • 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=
    MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

  • 注意只要添加了元素(不管是在table上新占一个还是在链表后加一个都算),就算是size++

LinkedHashSet

  • LinkedHashSet是HashSet的子类,extends HashSet<E> implements Set<E>
  • 底层是LinkedHashMap,维护了一个数组+双向链表
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。(拆开就是双向链表,不过结点hash到不同的结点 )
  • LinkedHashSet不允许添重复元素

说明

  • LinkedHashSet加入顺序和取出元素/数据的顺序一致

  • LinkedHashSet底层维护的是一个LinkedHashMap( 是HashMap的子类)

  • LinkedHashSet底层结构( 数组tabLe+双向链表)

  • 添加第一次时,直接将数组table扩容到16 , 存放的结点类型是LinkedHashMap E n t r y 数组是 H a s h M a p Entry 数组是HashMap Entry数组是HashMapNode[] 存放的元素/数据是LinkedHashMap$Entry类型

  • 源码

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    //add等全使用父类hashmap的源码
    

TreeSet

  • 使用无参构造器创建TreeSet时,任然是无序的

  • 使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则

  • 源码

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    //传入比较器
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    //add
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    private transient NavigableMap<E,Object> m;//m
    ...
    
    

Map

双列集合(key-value)

在这里插入图片描述

  • Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  • Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
  • Map中的key不允许重复,原因和HashSet一样,前面分析过源码.
  • Map中的value可以重复
  • Map的key可以为null, value也可以为null ,注意key为null,只能有一个,value为null ,可以多个
  • 常用String类作为Map的key
  • key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
  • Map存放的k-v是放在一个HashMap$Node中的(Node实现了Entry接口)

Node为HashMap的内部类

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;
        }
  • k-v最后是HashMap$NodenewNode(hash, key, value, null)

  • k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型Entry

    transient Set<Map.Entry<K,V>> entrySet;

    其中Entry(一个接口)中的K指向Node中的key,value指向value

  • Map.Entry定义了 K getKey(); K getValue();

HashMap

  • 添加相同的key,会覆盖原来的key-val,等同于修改
  • 没有实现同步,线程不安全
  • 具体底层实现在HashSet已分析

HashTable

  • 存放的元素是键值对:即K-V
  • hashtable的键和值都不能为null,否则会抛出NullPointerException
  • hashTable使用方法基本上和HashMap-样
  • hashTable是线程安全的,hashMap是线程不安全的

底层:

  • 底层有一个数组private transient Entry<?,?>[] table; 初始化大小为11

  • 临界值threshold 8 = 11 * 0.75

  • 扩容:if (count >= threshold) 满足时 int newCapacity = (oldCapacity << 1) + 1;

  • 源码

    //put
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
    
        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
    
        addEntry(hash, key, value, index);
        return null;
    }
    //addEntry:添加k-v,封装到entry
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;
    
        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();
    
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
    
        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
    //rehash:扩容
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;
    
        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
    
        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;
    
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;
    
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }
    

Properties

  • Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形
    式来保存数据。
  • 他的使用特点和Hashtable类似
  • Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,
    并进行读取和修改
  • 可以通过k-v存放数据,key和value不能为null

TreeMap

ConcurrentHashMap

Collections

  • 一个操作Set、List、Map等集合的工具类
  • 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

选用参考

总结-开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

  1. 先判断存储的类型 (一组对象[单列]或一组键值对[双列])
  2. 一组对象[单列]:Collection接口
    允许重复:List
    增删多: LinkedList [底层维护了一个双向链表]
    改查多: ArrayList [底层维护 Object类型的可变数组]
    不允许重复:Set
    无序: HashSet[底层是HashMap ,维护了一个哈希表 即(数组+链表+红黑树)]
    排序: TreeSet
    插入和取出顺序一致: LinkedHashSet ,维护数组+双向链表
  3. 一组键值对[双列]:Map
    键无序: HashMap [底层是: 哈希表 jdk7: 数组+链表,jdk8: 数组+链表+红黑树]
    键排序: TreeMap
    键插入和取出顺序一致: LinkedHashMap
    读取文件 Properties
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值