java集合

一、单列集合

1.实现顺序
在这里插入图片描述
2.解释
2.1 单列集合的主要实现为list和set,他们的实现子类都是单列集合
2.2 collection实现子类可以存放多个元素,每个元素可以是object
2.3 collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的

1.1 ArrayList

1.底层是由数组来实现数据存储的,基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrayList,线程不安全的原因是没有用锁(synchronized)来保证线程安全;
2.ArrayList中维护了一个Object类型的数组elementData,transient:不被序列化
3.当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
注:>>右移相当于除以2,<<左移相当于乘以2,图中则为1.5倍

4.如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍;
在这里插入图片描述

1.2 Vector

1.Vector底层也是一个对象数组Object[] elementData
2.Vector是线程同步的,即线程安全,原因为操作方法带有Synchronized
3.在开发中,考虑线程安全就用Vector
4.默认长度为10,无参构造长度为10
在这里插入图片描述
在这里插入图片描述

1.3 LinkedList

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

如何选择ArrayList和LinkedList:
1)如果我们改查的操作多,选择ArrayList
2)如果我们增删的操作多,选择LinkedList
3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
4) 在一个项目中,根据业务灵活选择,也可能这样,,一个模块便用的是ArrayList,另外一个模块是LinkedList.

1.4TreeSet

TreeSet 是 Java 中 Set 接口的实现之一,它使用红黑树(TreeMap 实现)作为底层数据结构,以保持元素的有序性。以下是关于 TreeSet 的一些重要特点和底层实现细节:
底层数据结构:

1.TreeSet 使用红黑树来存储元素。
红黑树是一种自平衡的二叉搜索树,确保树的高度保持在对数级别,从而保证插入、删除和查找的平均时间复杂度为 O(log n),其中 n 是树的节点数。

2.有序性:
TreeSet 会根据元素的自然顺序或者使用提供的比较器(Comparator)来维护元素的有序性。
默认情况下,元素按照其自然顺序进行排序。如果元素实现了 Comparable 接口,或者在创建 TreeSet 时提供了比较器,就会按照指定的顺序排序。

3.性能特点:
插入、删除和查找操作的平均时间复杂度为 O(log n)。
TreeSet 适用于需要有序集合的场景,以支持按顺序遍历元素。

4.不允许重复元素:
TreeSet 不允许包含重复的元素。如果尝试插入一个已经存在的元素,插入操作会被忽略,不会导致集合中存在重复元素。

5.遍历:
TreeSet 提供了按照顺序遍历元素的方法,如 iterator()、descendingIterator() 等。

1.5 HashSet

HashSet 是 Java 中 Set 接口的实现之一,它使用哈希表(HashMap 实现)作为底层数据结构。以下是关于 HashSet 的一些重要特点和底层实现细节:

1.底层数据结构:
HashSet 使用哈希表来存储元素。
哈希表是一个数组,每个数组元素对应一个桶,每个桶可以存储一个元素或者一个链表(在解决哈希冲突时)。

2.无序性:
HashSet 不保证元素的顺序,即插入元素的顺序和遍历元素的顺序不一定相同。
元素在哈希表中的位置由元素的哈希码决定,而哈希码通常与元素的插入顺序无关。

3.性能特点:
插入、删除和查找操作的平均时间复杂度为 O(1)。
哈希表通过哈希函数将元素映射到桶,以提高对元素的快速访问。

4.不允许重复元素:
HashSet 不允许包含重复的元素。如果尝试插入一个已经存在的元素,插入操作会被忽略,不会导致集合中存在重复元素。

5.遍历:
HashSet 提供了按照顺序遍历元素的方法,如 iterator()。

二、双列集合

1.实现顺序
在这里插入图片描述

2.1 HashMap

1.HashMap底层为数组+链表+红黑树,在put数据中,HashMap首先会根据key去计算Hash值,会输出一个数组索引,引用于定位数据的存储位置。理想情况下,哈希函数应该将键均匀地分散到数组的不同位置,以减少冲突的可能性。

2.哈希表使用一个数组来存储键值对。数组的每个位置称为(Bucket),每个桶存储一个键值对。当存在不同的数据经过hash函数计算后得到相同的索引,这是就出现了hash冲突也就是hash碰撞,HashMap默认处理这种问题的方式是链地址法,每个数组位置上存储一个链表或其他数据结构,用于存储多个键值对。当发生冲突时,新的键值对可以添加到链表中。(注:桶中的数据结构就是单链表);

3.在jdk1.8之后,引入了红黑树,默认阈值是8,也就是当一个桶中的链表长度达到8时,HashMap 会将该链表转换为红黑树。
如果 HashMap 的容量小于64,即数组长度小于64时,不会进行链表到红黑树的转换。这是为了避免在较小的数组上使用红黑树造成不必要的空间和时间开销。
只有在数组容量大于等于64时,且链表长度达到阈值的情况下,才会进行转换。红黑树在 HashMap 中的容量是2的幂次方。如果链表被转换为红黑树,而红黑树的容量超过了64,那么红黑树会被重新转换为链表。这是为了避免在红黑树过大时引起过多的旋转操作,而影响性能。
总的来说:转换的条件是:
链表长度超过阈值(默认为8)。
HashMap 的容量大于等于64。
桶中的链表长度达到8。
只有在满足这三个条件的情况下,HashMap 才会将链表转换为红黑树;

4.HashMap的扩容操作: 扩容操作包括创建一个新的数组,其大小通常是原数组的两倍,然后将原数组中的键值对重新计算哈希值并放入新数组中。这一过程是比较耗时的,但它确保了新的数组有足够的空间来容纳更多的键值对。重新哈希: 在扩容过程中,需要对原数组中的每个元素重新计算哈希值,并将其插入到新数组中的正确位置。这个过程被称为重新哈希。
在无参构造中,默认容量为16,负载因子为0.75,超过16*0.75=12,则会进行扩容,数组扩容为原来的两倍,进行重新哈希;

5.HashMap的初始化操作并不是通过无参构造进行的,是通过put()方法进行的:计算哈希值并得到桶位置,获取桶位置的元素,如果桶位置没有元素,创建新节点,如果桶位置已经有元素,可能是链表或红黑树,将新节点插入到链表或红黑树中。

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)
            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;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            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;
    }

6.在 Java 8 中,HashMap 的桶内结构经过了优化,与哈希算法和桶的数量等因素有关,遍历时的顺序可能不再是按照插入的顺序。
如果需要有序的存取,可以考虑使用 LinkedHashMap,它保留了元素的插入顺序。LinkedHashMap 继承自 HashMap,在内部使用双向链表维护插入顺序

2.2LinkedHashMap

LinkedHashMap 是 Java 中的一个有序映射表类,它继承自 HashMap,在 HashMap 的基础上额外维护了一个双向链表,用于保持插入元素的顺序。因此,LinkedHashMap 提供了按照插入顺序或者访问顺序(最近最少使用,LRU)来遍历元素的功能。
以下是 LinkedHashMap 的一些重要特点:

1.有序性: LinkedHashMap 保持了元素的插入顺序或者访问顺序。插入顺序是指元素按照插入的顺序进行遍历,而访问顺序是指元素按照被访问的顺序进行遍历。

2.双向链表: 在 LinkedHashMap 内部,使用了一个双向链表来维护元素的顺序。每个节点包含了前一个节点和后一个节点的引用,这样在遍历时可以方便地按照顺序访问。

3.按插入顺序遍历: 默认情况下,LinkedHashMap 按照插入顺序遍历元素。

4.按访问顺序遍历: 通过构造函数中的参数设置,可以使 LinkedHashMap 按照访问顺序遍历元素,即最近访问的元素会被放在链表的末尾,最早访问的元素会被放在链表的开头。这个功能可用于实现最近最少使用(LRU)缓存策略。

5.性能: 由于额外维护了链表结构,LinkedHashMap 在空间和性能上相对于普通的 HashMap 有一些额外的开销。但在实际应用中,这个开销通常是可以接受的,特别是在需要保持元素顺序的场景中。

LinkedHashMap 提供了一个构造函数,允许创建一个按照访问顺序排序的Map,即LRU缓存。通过设置构造函数的accessOrder参数为true,可以开启按访问顺序排序:

Map<K, V> lruCache = new LinkedHashMap<>(capacity, 0.75f, true);

总的来说,LinkedHashMap 在 HashMap 的基础上引入了额外的链表结构,提供了更多对元素顺序的控制。选择使用哪个取决于具体的需求,如果需要保持顺序或者实现LRU缓存,LinkedHashMap 是一个更适合的选择。如果对顺序没有特殊要求,而且希望更节省内存,HashMap 可能更合适。

2.3TreeMap

特性:
1.有序性: TreeMap 保持了元素的有序性。默认情况下,它会按照键的自然顺序(实现了 Comparable 接口的对象)进行排序,或者可以通过构造函数提供的比较器来指定排序规则。

2.红黑树: TreeMap 的底层实现采用了红黑树数据结构。红黑树是一种自平衡的二叉搜索树,它确保了在树的插入和删除操作中保持了良好的平衡,从而保证了操作的高效性。

3.性能: TreeMap 的基本操作(插入、删除、查找)的时间复杂度是对数级别的,因为红黑树的高度是对数级别的。相比于 HashMap,它在有序性的保持上具有优势,但在无序插入和查找的性能上可能略逊一筹。

4.键的比较: 在 TreeMap 中,元素的顺序是由键的比较规则决定的。如果键实现了 Comparable 接口,那么 TreeMap 将按照这个接口中定义的比较方法来排序;否则,可以在构造函数中提供一个自定义的比较器。

5.迭代顺序: TreeMap 提供了按照升序或者降序遍历键的方法。通过 ascendingKeySet() 和 descendingKeySet() 方法可以获取按照升序或降序的键集合。

2.4 ConcurrentHashMap

1.线程安全性: ConcurrentHashMap 被设计为在多线程环境中安全地进行并发操作。它使用了一种称为分段锁(Segment)的机制,每个分段相当于一个小的独立的哈希表,不同分段的操作可以并行执行,从而提高了并发性能。

2.分段锁: ConcurrentHashMap 使用了分段锁的方式来提高并发性能。每个分段都拥有自己的锁,不同分段之间的操作是相互独立的,可以并行执行。这种设计降低了锁的粒度,减小了锁的争用,从而提高了并发度。

3.并发度: ConcurrentHashMap 的并发度是通过设置分段的数量来控制的。默认情况下,分段的数量是16,但你也可以在构造函数中指定分段的数量。

4.无阻塞操作: ConcurrentHashMap 的许多操作都是无阻塞的,这意味着在进行操作时,不需要锁住整个哈希表,而只是锁住相关的分段,从而减小了锁的粒度,提高了并发度。

5.不允许null键值: ConcurrentHashMap 不允许键或值为null。如果试图插入null键或值,会抛出NullPointerException。

6.不保证有序性: 与 TreeMap 不同,ConcurrentHashMap 不保证元素的有序性。它是基于哈希表实现的,元素的存储顺序不一定与插入顺序相同。

三、遍历【List】
1.1 iterator执行原理

Iterator iterator = list.iterator();//得到一个集合的迭代器,方法为顶级接口Iteable里面的,返回一个迭代器接口类型对象(理解为顺序表,数据结构为指针
hasNext();//判断是否还有下一个元素,为Iterator接口里面的方法
while(iterator.hasNext()){
System.out.println(iterator.next());
//next()作用:1.下移 2.将下移以后集合的位置上的元素返回,为Iterator接口里面的方法
}
itetator重置
iterator = list.iterator();//即可完成重新遍历,将指针回到初始位置

1.2 增强for
for(Object customer: list){
sout;
//底层仍然是iterator迭代器
//可以理解成简化版的迭代器
}
1.3 普通for

注:增强for也可以在数组中时候用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值