万字集合概述

1. 集合体系图

下面所有的实现类都重写了toString方法

1.1 单列集合

image-20210318171144320

1.2 双列集合

存放的是K-V

image-20210318171226927

2.Collection方法

因为Collection Set List都是接口不能直接实例化,所以我们选用ArrayList来演示各种方法

2.1 添加操作

image-20210318180219407

2.2 删除操作

image-20210318180255035

2.3 查找

image-20210318173217532

2.4 获取元素个数

list.size()

2.5 判断为空

list.isEmpty()

2.6清空

list.clear();

2.7 遍历

2.7.1 Iterator法

  1. Iterator对象称为迭代器 主要用于遍历Collection集合中的元素

  2. 所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器

  3. Iterator仅用于遍历集合 其本身不存放对象

  4. 遍历实现步骤

//hasNext()判断是否还有下一个元素
//Next()   1.下移   2.将下移以后的元素返回
//Next() 放回的元素是Object类型
Iterator iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}
//如过希望再次遍历需要重置 iterator
iterator = list.iterator();
  1. 上述结构的快捷键是itit
  2. 显示所有快捷键的快捷键Ctrl + j

2.7.2 增强for循环

可以替代iterator 特点:增强for循环就是简化版的iterator 底层就是迭代器

可以使用在集合 或者 数组

for (Object a:list) {
    System.out.println(a);
}
//快捷键 I

--------List篇---------

3. List方法

  1. List集合中的元素是有序的(即添加顺序和取出顺序是一致的) 且可以重复
  2. List集合中的每一个元素都有其对应的顺序索引 即支持索引 如 list.get(index)
  3. 主要实现类有 ArrayList LinkedList Vector

3.1 添加操作

相比于Collections方法多了index选项,index为可选选项,表示插入的位置

image-20210704161623550

3.2 删除操作

image-20210318173154421

  1. clear()
  2. retain()

3.3 查找操作

  1. contains 与Collections中的一致

get(index)
  1. indexof(Object obj) 
    //返回obj在集合中首次出现的位置
    
  2. lastIndexof(Object obj) 
    //返回obj在集合中末次出现的位置
    

3.5 赋值(替换)

set(int index,Object o ) //设定指定index位置的元素为o 不能添加元素

3.6 子集合

subList(int fromIndex,int lastIndex)//返回的是左闭右开的

3.7遍历

  1. 迭代器
  2. 增强for
  3. 普通for

4. ArrayList

4.1 性质

  1. 允许放入null
  2. 本质上来说是由数组来实现数据存储的
  3. 基本等同于Vector 除了ArrayList是线程不安全的(执行效率高) 多线程不使用ArrayList

4.1.1 字段

  1. ArrayList中维护的是一个Object类型的数组 transient Object[] elementData
  2. transient 表示该属性不会被序列化

4.1.2 构造器

image-20210318185709752

  1. 如果使用无参构造器 则elemData容量为0 第一次添加 扩容为10 如再次需要扩容 则为原来的1.5倍
  2. 如果使用指定大小的构造器 则初始容量为指定大小 如再次需要扩容 则为原来的1.5

4.2 源码分析

4.2.1 构造

I 无参构造

image-20210319145122814

image-20210319145134468

默认构造一个空数组

II 有参构造

image-20210319170612706

注意如果赋值为0的话 那么数组初始化为EMPTY_ELEMENTDATA 这个和默认构造有区别的

区别就在于以下

image-20210319160432549

则第一次扩容的时候就不是扩为10了 而是扩为1

4.2.2 添加操作

  1. 在添加之前我们要先判断容量是否足够
  2. 而判断的方法就是将所需的最小容量(minCapacity)与现有的容量进行比较,如果不够就进行扩容
  3. 所以我们首先要确定所需的最小容量为多少

image-20210319145256524

ensureCapacityInternal()方法的内部为

image-20210319160348669

这个函数主要有两个目的

  1. 确定最小容量也就是 calculateCapacity()
  2. 判断是否容量足够,不够则进行扩容
I 判断是否扩容
  1. 确定最小容量

通过此方法来确定minCapacity,这里我们传入的minCapacity形式参数一般为size + numNew

image-20210319160432549

  1. 如果elemData是空参构造的数组,且这是第一次添加操作,则会将DEFAULT_CAPACITY(也就是10)与minCapacity(我们传入的是size+1)比较 将较大的作为新的minCapacity。否则就直接将size + numNew作为minCapacity
  2. 如果是通过有参构造的,或者不是第一次添加操作。那么直接将minCapacity返回
  1. 判断是否需要进行扩容

image-20210319165133160

II 扩容操作
  1. 确定新的容量:将minCapacity 与 原空间的1.5倍进行比较 选择较大的那个作为新的 newCapcaity
  2. 之后就是调用copyof来进行扩容

image-20210319165149782

5. Vector

相对于ArrayList他是安全的 但是效率不高 因为做了线程判断

扩容情况

  • 如果是无参 默认为10,满了以后按两倍扩容
  • 如果是有参 则每次按照两倍扩容

源码分析

无参构造

本质上是调用了初始化为10的有参构造

image-20210319173139473

image-20210319173153406

image-20210319173802517

关于capacityIncrement:每次扩充扩多少大小

The amount by which the capacity of the vector is automatically incremented when its size becomes greater than its capacity. If the capacity increment is less than or equal to zero, the capacity of the vector is doubled each time it needs to grow.

添加过程

同样要先判断空间是否足够 若不够 则需要扩容

image-20210319173835646

image-20210319174005832

扩容方法默认为扩容为原来的两倍 若设置了增长量 则按照自己设置的来

image-20210319174112786

6. LinkedList

6.1 特点

  1. 底层实现了双向链表和双端队列特点
  2. 可以添加任意元素(可以重复),包括null
  3. 线程不安全,没有实现同步

  1. 底层维护了双向链表

6.2 底层结构

Node

image-20210704152043225

LinkedList

image-20210704152126038

6.3 add方法

image-20210704155749156

7. List集合的选择

image-20210328162423213

--------Set篇---------

8. Set接口

  1. 无序(添加和取出顺序不一样,但是顺序是固定的) 没有索引

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

  3. 和List接口一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样

  4. 遍历方法

    • 可以使用迭代器
    • 增强for
    • 不能使用索引

9. HashSet

  1. 实现了Set接口

  2. 底层实际上是HashMapimage-20210328164226095 hashMap的底层是数组+链表+红黑树

  3. 不保证元素是有序的 取决于hash

  4. 不能有重复元素,可以存放null值,但是只能有一个

9.1 扩容机制

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

9.2 第一次add

9.2.1 得到对应的key和value

调用hashSet的add方法

在其中调用map的put 方法,其中有两个参数

  • 作为key的 e
  • 作为value 的PRESENT(作用是占位)
public boolean add(E e) {    return map.put(e, PRESENT)==null;}
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

9.2.2 求出对应的hash值

通过求出元素的hashCode值,之后将hash值无符号右移16位 进行异或运算,得到新的hash值

static final int hash(Object key) {    int h;    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
//不同的类型的hashCode算法是不同的//这里以String为例子public int hashCode() {    int h = hash;// Default to 0    if (h == 0 && value.length > 0) {        char val[] = value;        for (int i = 0; i < value.length; i++) {            h = 31 * h + val[i];        }        hash = h;    }    return h;}

9.2.3 添加操作

Params:

  1. hash – hash for key
  2. key – the key
  3. value – the value to put
  4. onlyIfAbsent – if true, don’t change existing value
  5. evict – if false, the table is in creation mode.
  6. Returns: previous value, or null if none
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,               boolean evict) {    Node<K,V>[] tab; Node<K,V> p; int n, i;    //如果table为null 或者为空 就先进行扩容    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 {        Node<K,V> e; K k;        //如果当前索引位置对应的链表的第一个元素的hash和key的hash一样        //并且满足下面两个条件之一        //(1)准备加入的key 和 p 指向的Node结点的key是同一个对象        //(2)p 指向的Node结点的key 的equals()和准备加入的key比较后相同        if (p.hash == hash &&            ((k = p.key) == key || (key != null && key.equals(k))))            e = p;        //再判断p 是不是一颗红黑树        //如果是一颗红黑树 就调用putTreeVal,来进行添加        else if (p instanceof TreeNode)            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);        //如果table对应索引位置,已经是一个链表了,就使用for循环比较        //(1)依次和链表的每一个元素比较后,都不相同,则加入到该链表的最后        //		注意再把元素添加到链表以后,立即判断 该链表是否已经到达8个结点 对当前链表就进行树化         //   	再转成红黑树时 如果table的长度小于64 就会进行扩容        //(2)依次和该链表的每一个元素比较后,如果有相同的情况,则break        else {            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);                    break;                }                if (e.hash == hash &&                    ((k = e.key) == key || (key != null && key.equals(k))))                    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)        resize();    afterNodeInsertion(evict);    return null;}

扩容操作

扩容操作即调用resize函数。

触发扩容操作的有以下情况

  1. 如果原来的table为null或者空表
if ((tab = table) == null || (n = tab.length) == 0)    n = (tab = resize()).length;
  1. 如果size大于临界值
if (++size > threshold)    resize();

扩容的函数如下:

我们要得到新的

  • table
  • threshold
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);    }    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;}

10. LinkedHashSet

  1. LinkedHashSet是HashSet的子类
  2. 底层是一个LinkedHashMap,底层维护了一个 数组 +双向链表
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
  4. LinkedHashSet不允许添加重复元素

image-20210329194943917

  1. LinkedHashSet 加入顺序和取出顺序是一致的
  2. LinkedHashSet 底层维护的是一个LinkedHashMap
  3. LinkedHashMap 底层结构是 数组table +双向链表
  4. 添加第一次时, 直接将table扩容为16 存放的结点类型是LinkedHashMap$Entry
  5. 数组是 HashMap N o d e [ ] 存 放 的 元 素 是 L i n k e d H a s h M a p Node[] 存放的元素是 LinkedHashMap Node[]LinkedHashMapEntry

image-20210705111326896

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

-------Map篇----------

11. Map接口

11.1 特点

  1. Map与Collection并列存在。用于保存具有映射关系的数据 key-value
  2. Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的key不允许重复,原因和HashSet一样
  4. Map中的value可以重复
  5. Map中的key可以为null,value也可以为null
  6. 常用String作为key
  7. key和value之间存在单一一对一关系,即通过指定的key总能找到对应的value

  1. k-v 最后是 HashMap$Node node= new newNode(hash,key,value,null)

  2. k-v为了方便程序员遍历 还会创建 EntrySet集合 该集合存放的元素类型Entry ,而一个Entry 对象就有k,v EntrySet<Entry<k,v>> 即transinet Set<Map.Entry<K,V>> entrySet;

  3. entrySet中,定义的类型是Map.Entry 但是实际上存放的还是HashMap$Node

    这是因为 static class Node<K,V> implements Map.Entry<K,V>

  4. Map.Entry 提供了重要方法 getKey() getValue()

  5. 得到所有key keySet()

  6. 得到所有values 用values()

11.2 方法

增加(修改)

 public V put(K key, V value)
public void putAll(Map<? extends K, ? extends V> m)

删除

public V remove(Object key)
default boolean remove(Object key, Object value) 
public void clear()

查找

public V get(Object key)
public boolean containsKey(Object key)	
public boolean containsValue(Object value)
public V getOrDefault(Object key, V defaultValue) {    Node<K,V> e;    return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;}

遍历

public Set<K> keySet()
public Collection<V> values() 
public Set<Map.Entry<K,V>> entrySet();//其中Entry的方法有1. getKey();2. V getValue();3. V setValue(V value);4. boolean equals(Object o);

image-20210706102451053

image-20210706102952925

杂项

 public int size()
public boolean isEmpty()
  1. put 添加
  2. remove 根据键删除
  3. get 根据键获取值
  4. containsKey 是否存在键
  5. size
  6. isEmpty
  7. clear

11.3 遍历

  1. containsKey

  2. keySet 获取所有的键

  3. entrySet 获取所有的关系k-v

  4. values 获取所有的值

12. HashMap

12.1 特点

image-20210706113612395

12.2 底层源码

13. HashTable

1)存放的元素是键值对:即K-V

  1. hashtable的键和值都不能为null,否则会抛出NullPointerException

  2. hashtable的键和值都不能为null,否则会抛出NullPointerException

  3. hashTable是线程安全的(synchronized), hashMap 是线程不安全的

14. Properties

15. TreeSet

16. TreeMap

17. Collections工具栏

image-20210706145507622

image-20210706151239346

16. 集合选择

image-20210706115830437

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值