Java集合,超详细整理,适合新手入门

这篇具有很好参考价值的文章主要介绍了Java集合,超详细整理,适合新手入门。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

java集合体系结构图,javaSE,java,学习

1. 单列集合Collection

  • 指的是在集合里面放的是单个的对象
  • Collection 接口有两个重要的子接口 List、Set
  • Collection提供了size()方法,List提供了get()方法
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习

1.0 Collection接口实现类的特点

  1. Collection接口的实现子类可以存放多个元素,每个元素可以是Object;
  2. Collection的实现类,有些可以存放重复的元素,有些不可以;
  3. Collection的实现类,有些是有序的(比如List),有些是无序的(比如Set);
  4. Collection接口没有直接的实现子类,通过它的子接口Set和List来实现的;

1.1 Collection常用方法

  1. add添加单个元素,只要是Object的子类,都可以往里放,输入整数存在一个自动装箱的过程
    java集合体系结构图,javaSE,java,学习
  2. remove()删除指定的元素
    remove构成重载: remove(Object 0) 返回布尔值; remove(int index) 返回被删除的对象
    java集合体系结构图,javaSE,java,学习
  3. contains()查找某个元素是否存在
    java集合体系结构图,javaSE,java,学习
  4. size()获取元素个数,从1开始计数
    java集合体系结构图,javaSE,java,学习
  5. addAll()可以添加多个元素
    可以在某个下标存入一个实现了Collection接口的ArrayList
    java集合体系结构图,javaSE,java,学习
  6. removeAll()删除多个元素
    参数为一个实现了Collection接口的ArrayList
    java集合体系结构图,javaSE,java,学习
  7. clear()清空
    java集合体系结构图,javaSE,java,学习
  8. isEmpty()判断是否为空
    java集合体系结构图,javaSE,java,学习
  9. containsAll()查找多个元素是否都存在
    参数为一个实现了Collection接口的ArrayList
    java集合体系结构图,javaSE,java,学习

1.2 继承了Iterable接口

  1. 由于Collection实现了Iterable接口,所以所有实现了Collection接口的集合类都有一个iterator()方法,返回一个实现了Iterator接口的对象,即迭代器;java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
  2. iterator.next()取不到值的时候,NoSuchElementException;

1.3 Collection接口遍历元素的方法

1.3.1 Iterator迭代器

Iterator对象称为迭代器,主要用于遍历Collection集合中的元素;所有实现了Collection接口的实现类都有这个方法;(shortcuts: itit)

  1. 遍历collection集合,得到collection对象对应的迭代器;iterator.next(),返回下一个元素,类型Object;
    obj编译类型是Object,运行类型是实际存的对象;
    java集合体系结构图,javaSE,java,学习
  2. 如果要再次遍历,需要重置迭代器; 当迭代器到达最后一个元素时,重置迭代器
    java集合体系结构图,javaSE,java,学习

1.3.2 增强for循环

  1. 增强for循环底层仍然是用的迭代器;所以可以认为增强for循环就是简化版的迭代器遍历;(shortcuts: I、iter)
    java集合体系结构图,javaSE,java,学习
    返回一个Iterator对象;
    java集合体系结构图,javaSE,java,学习
    调用iterator.hasNext()方法,判断是不是集合中的最后一个对象;java集合体系结构图,javaSE,java,学习
    如果不是,调用iterator.next()取得下一个对象;
    java集合体系结构图,javaSE,java,学习

1.4 List接口

List支持索引;

  1. List接口中的元素是有序的(添加顺序和取出顺序一致),并且元素可以重复;
    java集合体系结构图,javaSE,java,学习
  2. List集合中的每个元素都有其对应的顺序索引,List支持索引,索引从0开始;
    java集合体系结构图,javaSE,java,学习

1.4.1 List常用方法

  1. void add(int index, Object ele); 在index位置插入ele对象;如果没有index,则默认在最后插入;
    java集合体系结构图,javaSE,java,学习
  2. addAll(int index, Collection c);从index处将集合c中所有的元素添加进来;
    boolean addAll(int index, Collection c);
    boolean addAll(Collection c);
    java集合体系结构图,javaSE,java,学习
  3. int indexOf(Object o); 返回o对象在集合中首次出现的索引;
    java集合体系结构图,javaSE,java,学习
  4. int lastIndexOf(Object o);返回o对象在集合中最后一次出现的索引;
    java集合体系结构图,javaSE,java,学习
  5. Object remove(int index); 移除给定索引处的元素,并返回该元素
    boolean remove(Object o);
    java集合体系结构图,javaSE,java,学习
  6. Object set(int index, Object ele); 设置index处的元素为ele,相当于替换
    index不可以越界:IndexOutOfBoundsException
    java集合体系结构图,javaSE,java,学习
  7. List subList(int fromIndex, int toIndex); 截取fromIndex到toIndex的元素,返回一个集合
    前闭后开,fromIndex <= 返回的元素 < toIndex; [fromIndex, toIndex);
    java集合体系结构图,javaSE,java,学习

1.4.2 List接口遍历元素的方法

  1. Iterator迭代器
    java集合体系结构图,javaSE,java,学习
  2. 增强for循环
    java集合体系结构图,javaSE,java,学习
  3. 普通for循环
    java集合体系结构图,javaSE,java,学习

1.4.3 ArrayList类

java集合体系结构图,javaSE,java,学习

  1. ArrayList 线程不安全,Vector 线程安全;
  2. ArrayList中维护了一个Object类型的数组elementData;transient Object[] elementData;(transient修饰的属性表示这个属性不会被序列化);
  3. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData的容量为0;第一次添加时,elementData扩容为10,第二次添加时,elementData则扩容为原来的1.5倍;
  4. 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容,则直接扩容为原来的1.5倍;
1.4.3.1 ArrayList底层源码,1.5倍扩容,Object[]数组

transient 瞬间;短暂的
final DEFAULTCAPACITY_EMPTY_ELEMENTDATA = 0;
final DEFAULT_CAPACITY = 10;

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
    	//calculateCapacity()返回的是10或者elementData数组的容量
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //向右移动一位,除以2,1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            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);
    }
    
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. 使用无参构造器创建ArrayList源码
    java集合体系结构图,javaSE,java,学习
    1.1 创建了一个空的elementData数组
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    1.2 先确定是否要扩容;然后然后再执行赋值操作
    java集合体系结构图,javaSE,java,学习
    1.3 ensureCapacityInternal(): 该方法确定minCapacity(最小容量)
    (1) 第一次扩容为10
    java集合体系结构图,javaSE,java,学习
    1.4 (1) modCount++;modCount用来记录当前这个集合被修改的次数,防止被多线程操作;
    (2) 如果elementData容量不够,就调用grow()去扩容
    java集合体系结构图,javaSE,java,学习
    1.5 使用扩容机制来确定要扩容到多大;
    (1) 第一次扩容后newCapacity=10;
    原因:elenmentData数组的长度为0,赋给oldCapacity;oldCapacity向右移一位,即: oldCapacity + oldCapacity / 2,赋给newCapacity,则newCapacity的值还为0;此时minCapacity为10,newCapacity < minCapacity,所以将minCapacity赋给newCapacity。
    (2) 第二次扩容及其以后,按照1.5倍扩容;
    (3) 扩容使用的是Arrays.copyOf(); 保证原来数据安全的情况下,再增加空间;
    java集合体系结构图,javaSE,java,学习
    1.6. Idea再Debug情况下,显示的数据默认是已简化的,如果希望看到完成的数据,需要做设置
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
  2. 使用有参构造器创建和使用ArrayList
    java集合体系结构图,javaSE,java,学习
    2.1 创建了一个指定大小的elementData数组(Object[capacity])
    如果是有参的构造器,那么扩容机制是:第一次按照elementData的1.5倍扩容,整个执行流程还是和前面一样;
    java集合体系结构图,javaSE,java,学习

1.4.4 Vector类

  1. Vector类底层也是一个对象数组;protected Object[] elementData;

java集合体系结构图,javaSE,java,学习
2. Vector是线程同步的,即线程安全,Vector常用方法都带有synchronized;

1.4.4.1 Vector底层源码,2倍扩容
  1. 使用无参构造器创建Vector
    java集合体系结构图,javaSE,java,学习
    1.1 new Vector()底层:调用无参构造器,初始容量为10,initialCapacity=10;
    java集合体系结构图,javaSE,java,学习
    Vector(int),调用一个参数的构造器
    java集合体系结构图,javaSE,java,学习
    Vector(int,int),调用两个参数的构造器java集合体系结构图,javaSE,java,学习1.2 执行add()操作;java集合体系结构图,javaSE,java,学习
    1.3 确定是否需要扩容【minCapacity - elementData.length > 0】
    java集合体系结构图,javaSE,java,学习
    1.4 如果需要的elementData大小不够用,就扩容2倍;
    newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    java集合体系结构图,javaSE,java,学习
  2. 使用有参构造器创建Vector,初始容量为指定大小;
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
  3. 使用两个参数的构造器:Vector(int,int),可以自定义扩容步长;
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    capacityIncrement > 0 ? capacityIncrement : oldCapacity
    java集合体系结构图,javaSE,java,学习
1.4.4.2 ArrayList 和Vector比较
底层结构 版本 线程安全(同步)效率 扩容倍数
ArrayList 可变数组Object[] jdk1.2 不安全,效率高 (无参)10➡15➡22➡33➡49
Vector 可变数组Object[] jdk1.0 安全,效率低 (无参)10➡20➡40➡80➡160
  1. Vector如果是无参构造器,则默认初始容量为10,之后扩容按照2倍扩容;
    如果是有参构造器则第一次指定大小,之后扩容按照2倍扩容;
  2. ArrayList第一次扩容为10,第二次扩容及其以后,按照1.5倍扩容;

1.4.5 LinkedList类

java集合体系结构图,javaSE,java,学习

  1. LinkedList中维护了两个属性first和last,分别指向首结点和尾结点;
  2. 每个节点中(Node对象),又维护了prev、next、item三个属性;其中通过prev指向前一个,通过next指向后一个,最终实现双向链表;
  3. 所有LinkedList的元素的添加和删除,不是通过数组完成的,所以效率极高;
  4. LinkedList也是线程不安全的;
1.4.5.1 简单实现双向链表

java集合体系结构图,javaSE,java,学习
java集合体系结构图,javaSE,java,学习
java集合体系结构图,javaSE,java,学习

  • 从头到尾循环链表
    java集合体系结构图,javaSE,java,学习
  • 从尾到头循环链表
    java集合体系结构图,javaSE,java,学习
  • 添加节点
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
1.4.5.2 LinkedList底层源码,没有扩容,双向链表
  1. 调用无参构造器后LinkedList的属性first=null,last=null
    java集合体系结构图,javaSE,java,学习
    size大小为0,只是做了一个初始化
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    1.2 执行add方法;
    java集合体系结构图,javaSE,java,学习
    1.2.1 添加第一个节点,加入到双向链表的最后;
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    1.2.2 LinkedList的first属性和last属性都指向了同一个节点,这个节点两头为空,item值为1;
    java集合体系结构图,javaSE,java,学习
    1.2.3 内存布局图:
    java集合体系结构图,javaSE,java,学习
    1.2.4 向链表添加第二个节点
    java集合体系结构图,javaSE,java,学习
  2. remove(),默认删除第一个节点;(新版本遗弃了此方法)
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    2.1 首先让f指向first,即f指向了第一个节点;
    java集合体系结构图,javaSE,java,学习
    2.2 将f.next即第二个节点赋给了next
    java集合体系结构图,javaSE,java,学习
    2.3 将第一个节点的item和next置空
    java集合体系结构图,javaSE,java,学习
    2.4 first指向next,即first也指向了第二个节点
    java集合体系结构图,javaSE,java,学习
    2.5 将next.pre置空,此时第一个节点成为垃圾;
    java集合体系结构图,javaSE,java,学习
  3. linkedList.set(1,999); 修改某个节点对象;索引从0开始;
  4. linkedList.get(1); 取得第二个节点对象;
1.4.5.3 ArrayList和LinkedList比较
底层结构 增删的效率 改查的效率
ArrayList 可变数组 较低,涉及数组扩容 较高
LinkedList 双向链表 较高,通过链表追加 较低

1.5 Set接口,不提供索引

  1. Set接口是无序的(添加顺序和取出顺序不一致),原因: 添加元素时,会根据元素的哈希值进行排序;排序后,位置就固定了;
  2. 不允许重复元素,最后只能包含一个null;
    java集合体系结构图,javaSE,java,学习
  3. Set接口的实现类没有索引;
  4. Set接口的遍历方式
  • 迭代器
  • 增强for循环
    java集合体系结构图,javaSE,java,学习
  1. HashSet底层机制: HashMap,即:数组+链表+红黑树

1.5.1 HashSet

1.5.1.1 简单实现数组+链表
  1. 数组+链表
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
1.5.1.2 HashSet底层机制,数组+链表+红黑树

LinkedList、HashSet和HashMap都是在添加元素时初始化大小的,只有Vector是通过无参构造器初始化容量的;
final Object PRESENT = new Object();
final DEFAULT_LOAD_FACTOR=0.75;
final DEFAULT_INITIAL_CAPACITY = 1 << 4;//16

tips:

  1. HashSet的底层是HashMap;
  2. HashSet添加元素时,先得到hash值,根据hash值 &(n-1)得到索引值;
  3. 找到存储数据的数组[表]table,看这个索引位置是否已经有元素;
    (1)如果没有,直接添加;
    (2)如果有,调用equals方法[程序员自己定义]比较,如果相同,放弃添加; 如果不相同,添加到最后;
  4. 在jdk8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认值是8)[即到第九个元素时],并且table数组的大小 >= MIN_TREEIFY_CAPACITY(默认是64),就会转化成红黑树;
    实操:
  5. 调用HashSet无参构造器
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    调用HashMap的构造器,将加载因子赋予0.75
    java集合体系结构图,javaSE,java,学习
  6. 执行第一个add(“java”)方法,(e就是加的元素)
    java集合体系结构图,javaSE,java,学习
    private static final Object PRESENT = new Object(); 起到占位作用;
    java集合体系结构图,javaSE,java,学习
    2.1 进入到put方法; key=e=“java”,value=PRESENT
    java集合体系结构图,javaSE,java,学习
    2.2 进入到hash(key)方法
    如果key=null,返回一个0;
    如果key不等于null,将key的hashCode无符号右移十六位(防止冲突),返回key的哈希值;
    java集合体系结构图,javaSE,java,学习
    2.3 进入到putVal()方法
    java集合体系结构图,javaSE,java,学习
    2.4 如果table的大小为0,进入到resize()方法,给table赋DEFAULT_INITIAL_CAPACITY(16)
    java集合体系结构图,javaSE,java,学习tips:
    (n - 1) & hash 决定key的索引(即应该在table的哪个位置存放)
    table是HashMap里的属性,是存放节点的数组
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    这是HashMap留给子类(比如linkedHashMap)实现的一个方法
    java集合体系结构图,javaSE,java,学习
  7. 执行第二个add(“php”)方法
    java集合体系结构图,javaSE,java,学习
  8. 执行第三个add(“java”)方法
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
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 就是 HashMap的一个属性,类型是 Node[],存放节点的数组
            //if语句表示如果当前这个table是空或者大小为0
            //  就第一次扩容到16
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //(1)根据key得到的哈希值去计算 该key应该存放到table表的哪个索引
            //   并且把这个索引位置的对象赋给辅助变量p
            //(2)接下来就要判断这个索引位置的对象(即p)是否为空
            //如果p为null(表示这个索引位置还没有存放数据),将创建一个Node(key="java",value=PRESENT)
            //放在该位置tab[i] = newNode(hash, key, value, null);
            //   Node(hash,key,value,null)
            //          这个value只起到占位作用[value=PRESENT]
            //          这个哈希值用于比较下一次存放的key和这个key是否相等
            //          null就是next
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                //一个开发技巧tip:在需要局部变量(辅助变量)的时候,再创建
                Node<K,V> e; K k;
                //老韩的理解:
                //如果当前索引位置对应的链表的第一个元素和准备添加进来的key哈希值相同
                //并且满足下列条件之一:
                //  条件一:如果准备加入的key和(即p指向的Node节点处的key)这个索引处的key是同一个对象
                //  条件二:或着p指向的Node节点的key经equals方法和准备加入的key比较后相同(不是同一个对象,但是内容相同)
                //则不能在此处添加
                //自己的理解:
                //就是判断这个索引处的对象是否为空,如果不为空,且两个key哈希值相同,
                //且经equals()比较内容也相同[程序员自己定义标准],则不能在此处添加
                //总结:第一个if语句判断和表的第一个节点是否相同,不同则要考虑表的情况进行添加操作
                //     如果表是红黑树,则执行putTreeVal()方法添加
                //     如果不是红黑树,则判断key和每一个节点是否都相同,如果没有相同的则添加在末尾
                if (p.hash == hash &&
                    //[自己的理解]这个if里的语句就是如果两个即传进来的key和p指向的位置的第一个节点完全一样,则不能在此处添加
                    ((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);
                else {
                    //如果table对应的索引位置已经是一个链表,就使用for循环依次比较
                    //  (1)依次和该链表的每一个节点比较后都不相同,则添加在链表末尾;
                    //     注意在把元素添加到链表末尾后,立即判断 该链表是否已经达到8个节点
                    //     如果达到8个节点,就调用treeifyBin() 对当前这个链表进行树化(转成红黑树)
                    //     注意,在转成红黑树时,还进行一个判断:
                    //     if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                    //          resize();
                    //     如果上面条件成立,对table表扩容。
                    //     只有上面条件不成立时,才转成红黑树;
                    //  (2)依次和该链表的每一个节点比较,如果有相同的情况,直接break
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {//e可以当作一个指针,比较完之后通过p.next指向下一个节点
                            p.next = newNode(hash, key, value, null);//加到链表末尾
                            if (binCount >= TREEIFY_THRESHOLD[8] - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;//从这里退出循环e为空
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;//从这里退出循环e不为空
                        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;
            //size 每加入一个Node(h,k,v,next)节点, size++
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
1.5.1.3 HashSet扩容+红黑树机制
  1. //容量: 0->16->32->64->128
    //临界值:0->12->24->48->96
    HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是12;java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
  2. 如果table数组元素增加到了临界值12,就会扩容到16 * 2=32,新的临界值就是32*0.75=24,依此类推;
  3. 在jdk8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认值是8)[即到第九个元素时],并且table数组的大小 >= MIN_TREEIFY_CAPACITY(默认是64),就会转化成红黑树;否则仍然采用数组扩容机制
  4. 每加入一个Node(h,k,v,next)节点, size就会++
    java集合体系结构图,javaSE,java,学习
public class HashSetIncrement {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        for (int i = 1; i <= 100; i++) {
            hashSet.add(new Dog(i));
        }
        System.out.println(hashSet);
    }
}
class Dog {
    private int age = 1;

    public Dog(int age) {
        this.age = age;
    }

    @Override
    public int hashCode() {
        return 100;
    }
}

练习题

  1. 重写equals方法,两个都勾选,说明只有两个字段都相同equals才返回true;
    java集合体系结构图,javaSE,java,学习
  2. 重写hashCode方法,两个都勾选,说明只有两个字段都相同hashCode才返回true;
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习

1.5.2 LinkedHashSet

java集合体系结构图,javaSE,java,学习

  1. LinkedHashSet是HashSet的子类;
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表;
  3. LinkedHashSet使用双向链表维护元素的次序,所以元素看起来是以插入顺序保存的;(取出顺序和插入顺序一致)
  4. LinkedHashSet根据元素的hashCode值来决定元素的存储位置;
  5. LinkedHashSet不允许重复元素;
    (LinkedHashSet有head和tail);
1.5.2.1 数组+双向链表
  1. 每一个节点都有before和after属性,这样可以形成双向链表;
  2. 在添加一个元素时,先求hash值,再求索引,确定该元素在table中的位置,然后将添加的元素加入到双向链表,如果已经存在(原则和HashSet一致),则放弃添加;这样在遍历LinkedHashSet是也能确保插入顺序和遍历顺序一致;
    tail.after = newElement;
    newElement.before = tail;
    tail = newElement;
  3. 第一次添加时将数组扩容到16;
  4. 数组类型是HashMap$Node[];存放的节点类型是LinkedHashMap$Entry;Entry是一个静态内部类,实现了双向链表,Node只是单向链表;
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    只确定了loadfactor和threshold
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
    LinkedHashSet底层维护的是LinkedHashMap(是HashMap的子类)
    java集合体系结构图,javaSE,java,学习
    LinkedHashMap底层维护的是HashMap:数据+双向链表
    java集合体系结构图,javaSE,java,学习
    数组类型是HashMap$Node, 数组存放的节点类型是LinkedHashMap$Entry ➡(多态数组)
    java集合体系结构图,javaSE,java,学习
    原因:LinkedHashMap.Entry<K,V> extends HashMap.Node<K,V>

1.5.3 TreeSet

java集合体系结构图,javaSE,java,学习

当使用无参构造器创建TreeSet并添加元素的时候,插入顺序和取出顺序不一致(这是因为TreeSet会默认按照传入的key从小到大排序)
原因:创建TreeSet对象时如果传入了一个Comparator对象,就用实现的compare方法比较;如果没有传入Comparator对象,则以添加对象的实现的Comparable接口的compareTo方法比较;
java集合体系结构图,javaSE,java,学习
当传入第一个元素时,若这个元素没有实现Comparable接口,则会报错
java集合体系结构图,javaSE,java,学习
java集合体系结构图,javaSE,java,学习
以后传入的对象如果没有实现Comparable接口,也会报类转换异常ClassCastException
java集合体系结构图,javaSE,java,学习
解决方案:让对象实现Comparable接口,重写compareTo方法;
去重机制:
创建TreeSet对象时如果传入了一个Comparator对象,就用实现的compare方法去除重复元素,如果方法返回0,就认为不应该添加;如果没有传入Comparator对象,则以添加对象的实现的Comparable接口的compareTo方法去除重复元素;

  1. 使用无参构造器创建TreeSet并添加元素
    java集合体系结构图,javaSE,java,学习
  2. 使用TreeSet 提供的一个有参构造器,传入一个比较器(匿名内部类),指定一个排序规则
    java集合体系结构图,javaSE,java,学习
    treeSet底层是treeMap;
    java集合体系结构图,javaSE,java,学习
    treeSet构造器,把传入的实现了 Comparator接口的匿名内部类传给了TreeMap的属性comparator;java集合体系结构图,javaSE,java,学习
    当调用treeSet.add()方法时,在底层会执行到:
    java集合体系结构图,javaSE,java,学习
    能不能加得进去,要看compare()比较的内容,如果内容相等,即cmp返回0,这个key就不会加入
    java集合体系结构图,javaSE,java,学习
    在这里会动态绑定到:匿名内部类的compare()方法
    java集合体系结构图,javaSE,java,学习
    这里是长度相等就相同,所以长度相等的加不进去;
    java集合体系结构图,javaSE,java,学习

2. 双列集合(Map):

  • 指的是在集合里面放的是键值对
  • Map 接口的实现子类是双列集合,存放的是 k-v
  • Map接口没有继承迭代器,不能使用迭代器遍历,也不能使用增强for循环遍历
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习

2.1 Map接口实现类的特点

  1. Map用于保存具有映射关系的数据Key-Value;
  2. Map中的key、value可以是任意引用类型,封装进HashMap的Node内部类中;
    java集合体系结构图,javaSE,java,学习
  3. Map中的数据key不允许重复,原因:根据hash值和equals判断
  4. 当有相同的key时,就相当于替换value值
  5. Map中的value值可以重复;
  6. Map【Hashtable、Properties除外】中的key、value都可以为null,但key只能有一个null,value可以有多个;
  7. 习惯是key放字符串,但是key可以放任意类型
  8. 通过get()方法传入一个key,返回key对应的value,value只有一个;
  9. k-v 最后都是 HashMap$Node node = newNode(hash,key,value,null);
  10. k-v 为了方便程序员的遍历,还会创建 EntrySet 集合,该集合存放的元素的类型是 Entry, 而一个Entry对象就有k,v -> EntrySet<Entry<K,V>>
    java集合体系结构图,javaSE,java,学习
  11. 在EntrySet中,定义的类型是 Map.Entry,但是实际上存放的还是HashMap$Node,这是因为HashMap$Node 实现了 Map.Entry接口
    java集合体系结构图,javaSE,java,学习
  12. 把HashMap$Node 对象 存放到entrySet集合,方便我们的遍历,因为Map.Entry提供了重要的方法
    java集合体系结构图,javaSE,java,学习
    java集合体系结构图,javaSE,java,学习
  13. HashMap没有做同步互斥,没有syntronized,因此是线程不安全的;
@SuppressWarnings({"all"})
public class MapSource {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("No1","scott");
        map.put("No2","zzw");
        map.put(new Person("zzw"),new Car());
        //1.k-v 最后都是 HashMap$Node node = newNode(hash,key,value,null)
        //2.k-v 为了方便程序员的遍历,还会创建 EntrySet 数组,该集合存放的元素的类型是 Entry,但元素的运行类型是Node
        //  而一个Entry对象就有k,v EntrySet<Entry<K,V>>
        //  即:transient Set<Map.Entry<K,V>> entrySet;
        //3.在EntrySet中,定义的类型是 Map.Entry,但是实际上存放的还是HashMap$Node
        //  这是因为HashMap$Node 实现了 Map.Entry接口
        //4.把HashMap$Node 对象 存放到entrySet,方便我们的遍历,因为Map.Entry提供了重要的方法
        //  K getKey();
        //  V getValue();
        Set set = map.entrySet();
        System.out.println(set.getClass());//HashMap$EntrySet 是HashMap的一个内部类
        for (Object obj : set) {
            System.out.print(obj + " ");
            System.out.println(obj.getClass());//HashMap$Node
            //为了从HashMap$Node中取出k-v
            //1.先向下转型
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        Set set1 = map.keySet();
        System.out.println(set1.getClass());//HashMap$KeySet
        Collection values = map.values();
        System.out.println(values.getClass());//HashMap$Values
    }
}
class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
class Car {

}

2.2 Map常用方法,不支持索引

  1. put(key,value) 添加键值对
  2. remove(k,v) remove(k)
  3. get(k)
  4. size() 获取k-v个数
  5. isEmpty() 是否为空
  6. clear() 清空键值对
  7. containsKey():查找键是否存在
    java集合体系结构图,javaSE,java,学习

2.3 Map接口三组遍历方法

java集合体系结构图,javaSE,java,学习

2.3.1 keySet+增强for循环

java集合体系结构图,javaSE,java,学习

2.3.2 keySet+迭代器

java集合体系结构图,javaSE,java,学习

2.3.3 values()+增强for循环

java集合体系结构图,javaSE,java,学习

2.3.4 values()+迭代器

java集合体系结构图,javaSE,java,学习

2.3.5 EntrySet+增强for循环

java集合体系结构图,javaSE,java,学习

2.3.6 EntrySet+迭代器

java集合体系结构图,javaSE,java,学习

2.4 HashMap扩容机制

  1. HashMap底层维护了Node类型的数组table,默认为null;
  2. 当创建HashMap对象时,将加载因子(loadfactor)初始化为0.75;
    java集合体系结构图,javaSE,java,学习
  3. 当添加k,v时,通过key的哈希值得到在table的索引,然后判断该索引处是否有元素。如果没有元素,则直接添加;
    java集合体系结构图,javaSE,java,学习
    如果有元素,则判断该索引处的key是否和准备加入的key相等;
    java集合体系结构图,javaSE,java,学习
    如果key相同,则执行完if语句直接跳到,替换value【e.value = value;】
    java集合体系结构图,javaSE,java,学习
    如果key不相同,需要判断是树结构,还是链表结构,然后做出相应处理;
    树结构
    java集合体系结构图,javaSE,java,学习
    链表结构中,如果每个元素的key都不相同,则会一直循环到链表的最后
    java集合体系结构图,javaSE,java,学习
    添加时,发现容量不对,则需要扩容;
    java集合体系结构图,javaSE,java,学习
  4. 第一次添加,需要将table扩容为16,临界值threshold为12,;
    java集合体系结构图,javaSE,java,学习
    以后再次扩容,则需要将table扩容为2倍;临界值也变为原来的2倍,即24,以此类推;
    java集合体系结构图,javaSE,java,学习
  5. 在java8中,如果在binCount到达TREEIFY_THRESHOLD-1,即一条链表的元素超过TREEIFY_THRESHOLD(默认为8),
    java集合体系结构图,javaSE,java,学习
    个数: 2 3 4 5 6 7 8 9
    binCount: 0 1 2 3 4 5 6 7
    并且table表的大小超过MIN_TREEIFY_CAPACITY(默认为64),就会进行树化,变为红黑树;
    java集合体系结构图,javaSE,java,学习
  1. 如果底层的table 数组为null,或者 length=0,则扩容到16,调用resize()方法;
  2. 取出hash值对应的table的索引位置的Node,如果为null,就直接把加入的k-v创建成一个Node,加入即可;
    tab[i] = newNode(hash, key, value, null);
  3. 如果key相同,则替换value【e.value = value;】 走到e != null,说明那个位置有元素并且元素相同,所以在这里不在添加;这段代码专门替换value值;
    java集合体系结构图,javaSE,java,学习
  4. 剪枝:红黑树删除节点到一定数量之后转为链表;
public class HashMapSource01 {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        for (int i = 1; i <= 12; i++) {
            hashMap.put(new Dog(i),"zze");
        }
        Set entrySet = hashMap.entrySet();
        for(Object entry : entrySet) {
            System.out.println(entry);
        }
    }
}
//所有的Dog对象的hashCode都一样
class Dog {
    private int age;

    public Dog(int age) {
        this.age = age;
    }

    @Override
    public int hashCode() {
        return 100;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "age=" + age +
                '}';
    }
}

2.5 Hashtable扩容机制(数组+链表)

  1. Hashtable存放的元素是键值对,使用方法基本上和HashMap一致;
    java集合体系结构图,javaSE,java,学习
  2. Hashtable的键和值都不能为null,否则会抛出NullPointerException;
    java集合体系结构图,javaSE,java,学习
  3. Hashtable是线程安全的,HashMap是线程不安全的;
  4. Hashtable底层有一个 Hashtable$Entry的数组。第一次初始化大小为11,临界值是8;
    threshold = 11 * 0.75;
    java集合体系结构图,javaSE,java,学习
    Hashtable 底层维护的是一个Hashtable$Entry
    java集合体系结构图,javaSE,java,学习
  5. 第二次扩容
    table大小:11-》23
    临界值:8 -》17
    java集合体系结构图,javaSE,java,学习
  6. 扩容会执行addEntry(hash,key,value,index);java集合体系结构图,javaSE,java,学习
    当数量大于等于临界值时,扩容
    java集合体系结构图,javaSE,java,学习
    大小变为原来的2倍+1;newCapacity = (oldCapacity << 1) + 1;
    java集合体系结构图,javaSE,java,学习

2.5.1 Hashtable与HashMap比较

版本 线程安全(同步) 效率 允许键为null,值为null
HashMap jdk1.2 不安全 效率高 可以
Hashtable jdk1.0 安全 效率低 不可以
Hashtable 扩容机制
容量 11➡23➡47➡95
临界值 8➡17➡35➡71

2.5.2 Properties实现类(继承Hashtable)

  1. Properties类继承Hashtable类并实现了Map接口,也是使用键值对的形式来存储数据;使用特点和Hashtable相似;
  2. Properties还可以用于 从某个后缀名为properties的文件中,加载数据到Properties对象,并进行读取和修改;
  3. Properties不允许键值为null;键或值为空会抛异常NullPointerException
  4. xxx.properties文件常常作为配置文件【IO流】;
  1. put();添加键值对,及修改value值
    java集合体系结构图,javaSE,java,学习
  2. get(key)方法,获取value
  3. remove(); 删除键值对
    remove(Object key)
    boolean remove(Object key, Object value)
    java集合体系结构图,javaSE,java,学习

2.6 treeMap

当使用无参构造器创建TreeSet并添加元素的时候,插入顺序和取出顺序不一致(这是因为TreeSet会默认从小到大升序排序)
第一次添加时,把key、value封装到Entry对象,放入root
java集合体系结构图,javaSE,java,学习
第二次添加时,遍历所有的key
java集合体系结构图,javaSE,java,学习

3. 小结

集合类 扩容机制 红黑树机制
ArrayList 第一次添加时,elementData扩容为10,第二次扩容时,elementData则扩容为原来的1.5倍; -----
Vector 第一次初始化elementData为10,如果需要的elementData大小不够用,就扩容2倍;(可自定义容量增量) -----
LinkedList -----
HashSet(HashMap) 第一次添加时,table数组扩容到16,临界值为12;第二次扩容时,table数组扩容为原来的2倍
容量: 0->16->32->64->128,临界值:0->12->24->48->96
-----
Hashtable 第一次添加时,table数组扩容到11,临界值为8;第二次扩容时,table数组扩容为原来的(2倍+1),临界值变为原来的(2倍+1)
容量: 11->23->47->95,临界值:8->17->35->71
-----

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

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

3.1 Collections集合工具类

3.1.1 Collections.reverse(),反转集合

java集合体系结构图,javaSE,java,学习

3.1.2 Collections.shuffle(),打乱集合顺序

java集合体系结构图,javaSE,java,学习

3.1.3 Collections.sort(list); 默认按照字符串大小升序排列

Comparator接口匿名内部类的用法👉传送门

Collections.sort(list, Comparator comparator); 自定义排序规则
1)我们希望按照字符串的长度大小排序
java集合体系结构图,javaSE,java,学习

3.1.4 swap(list, int, int);将指定集合中的i处元素和j处元素互换

java集合体系结构图,javaSE,java,学习

3.1.5 Collections.max(collection); 根据元素的自然顺序,返回指定集合中的最大值

java集合体系结构图,javaSE,java,学习
Collections.max(collection, Comparator comparator); 自定义排序,返回指定集合中的最大值
java集合体系结构图,javaSE,java,学习

  1. Collections.min(collection); 根据元素的自然顺序,返回指定集合中的最小值
    Collections.min(collection, Comparator comparator); 自定义排序,返回指定集合中的最小值

3.1.6 Collection.frequency(collection,Object); 返回指定集合中指定元素出现的次数

java集合体系结构图,javaSE,java,学习

3.1.7 Collections.copy(List dest, List src); 把src集合中的数据拷贝到dest中去

java集合体系结构图,javaSE,java,学习

3.1.8 Collections.replaceAll(List list, oldVal, newVal);用newVal替换集合中的oldVal

java集合体系结构图,javaSE,java,学习文章来源地址https://www.toymoban.com/news/detail-624456.html

3.2 史上巨坑题

public class Homework06 {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");
        set.add(p1);//HashMap$Node tab[i] = newNode(hash, 1001, "AA", null);
        set.add(p2);//HashMap$Node tab[i] = newNode(hash, 1002, "BB", null);
        p1.name = "CC";
        set.remove(p1);//此时p1的哈希值改变了
        System.out.println(set);//p1,p2
        set.add(new Person(1001, "CC"));//true
        System.out.println(set);
        set.add(new Person(1001, "AA"));//true
    }
}
class Person {
    private int id;
    public String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Person person = (Person) o;
        return id == person.id &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

到了这里,关于Java集合,超详细整理,适合新手入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • GitHub新手用法详解【适合新手入门-建议收藏!!!】

    目录 什么是Github,为什么使用它? 一、GitHub账号的注册与登录 二、 gitbash安装详解 1.git bash的下载与安装 2.git常用命令  3. Git 和 GitHub 的绑定 1. 获取SSH keys  2.绑定ssh密钥 三、通过Git将代码提交到GitHub 1.克隆仓库   2.测试提交代码         GitHub是一个面向开源及私有软件项

    2023年04月24日
    浏览(39)
  • Java新手小白入门篇 JDK安装及环境变量配置(超详细)

    学习Java,必备的就是JDK,所以我们必须得下载安装JDK,才能学习Java,下面我们会介绍 JDK是什么,如何安装并配置。 一、JDK简介 1.名词解释 JVM (Java Virtual Machine) Java虚拟机 作用:加载 .class 文件 并 运行 .class 文件 JRE (Java Runtime Environment) Java运行环境 包含 JVM + 运行Java程序所必

    2024年02月04日
    浏览(48)
  • 推荐几个适合新手入门学习的SQL网站,在线就能练习

    这里整理推荐几个我自己学习时用过的在线学习网站,对新手非常友好,帮助初学者快速入门SQL,在交互式的环境里学习,既不用安装也不用导入数据,在线就能思考和练习。 1.自学SQL网 适合小白学习,这里由浅及深的介绍了SQL的知识,每一个章节是一组相关的SQL知识点且配备着

    2024年02月15日
    浏览(24)
  • CSS新手入门笔记整理:CSS定位布局

    浮动布局比较灵活,但是不容易控制。而定位布局的出现,使得用户精准定位页面中的任意元素成为可能。当然了,由于定位布局缺乏灵活性,这给空间大小和位置不确定的版面布局带来困惑。因此在实际开发中,大家应该灵活使用这两种布局方式,这样才可以更好地满足开

    2024年02月04日
    浏览(33)
  • CSS新手入门笔记整理:CSS3属性表

    属性 属性值 说明 text-shadow 数值 文本阴影 text-stroke 数值 文本描边 text-overflow ellipsis(文本溢出时,显示省略号,隐藏多余的文字) clip(文本溢出时,不显示省略号,裁切多余的文字) 文本溢出 word-wrap normal(自动换行) break-word(强制换行) 强制换行 word-break normal(自动换

    2024年02月03日
    浏览(32)
  • 一个基于SpringBoot开发的RBAC系统,非常适合新手入门JavaWeb代码审计实战的系统,长文警告,要好好学习。

    嗨,大家好,我是闪石星曜CyberSecurity创始人Power7089。 欢迎大家搜索我的微信公众号:闪石星曜CyberSecurity 本文是【炼石计划@Java代码审计】内部圈子原创课程,现分享给大家学习。 如需转载,请详细注明来源。 欢迎大家搜索并添加我的好友【Power_7089】,备注CSDN,邀请你进入

    2024年02月11日
    浏览(36)
  • UE4/5AI制作基础AI(适合新手入门,运用黑板,行为树,ai控制器,角色类,任务)

    目录 制作流程 第一步:创建资产 然后创建一个AIController 之后创建一个黑板和行为树:  第二步:制作 黑板 行为树 任务 运行行为树  结果 第一步直接复制你的人物蓝图,做一个npc: 然后创建一个AIController 之后创建一个 黑板和行为树 :   首先打开你的BP_NPC的pawn类,然后

    2024年02月16日
    浏览(35)
  • ue4/5蓝图与c++混用基础入门的基础操作(适合有蓝图基础和c++基础的新手,创建自己的蓝图)

            首先是最开始的创建项目,用c++模式进行创建。         ue4:         ue5:  创建之后,两个都会自动为你打开vs,不过ue4.26要的是vs2019,ue5要的是vs2022,有时候打不开是缺少一些东西,这些东西在csdn里面可以查到,作者就不细讲了。 在ue5(4是一样的)中,我们可

    2023年04月12日
    浏览(35)
  • 新手入门Jenkins自动化部署入门详细教程

    在实际开发中,我们经常要一边开发一边测试,当然这里说的测试并不是程序员对自己代码的单元测试,而是同组程序员将代码提交后,由测试人员测试; 或者前后端分离后,经常会修改接口,然后重新部署; 这些情况都会涉及到频繁的打包部署; 手动打包常规步骤: 1.提

    2024年02月13日
    浏览(39)
  • 超详细FPGA新手小白入门点亮LED灯

    其实之前早已用过Vivado进行FPGA的开发学习,但由于每次都是浅尝辄止地学了一些时间,加上Vivado软件和FPGA开发流程的复杂性,长时间不用就会遗忘。因此今天还是简单地写个笔记记录一下Vivado软件的一些基本操作,实现一个“hello world工程”:控制LED灯的闪烁。 实验基于的

    2024年02月04日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包