一、集合的体系
概述:java中存储对象数据的一种容器,集合只能存储引用类型的数据。用泛型来规定需要操作元素的数据类型,可以在编译阶段约束集合只能操作某种数据类型。集合分为两个家族 MAP和Collection
特点:大小不固定(自动扩容,不用定义长度),启动后可以动态变化,类型也可以选择不固定。
集合非常适合做元素的增删操作。 因为数组增删操作比较慢,因此在进行频繁的增删业务的时候就可以选择集合来存储数据。
分类:分为单列集合,双列集合。和数组相比,大小可变更加灵活。
1. List集合(单列集合):
特点:添加的元素都是有序,可重复,有索引的。
什么是线程不安全:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
1.1. ArrayList:
ArrayList是基于动态数组的数据结构;
有索引;
查询效率快:直接通过索引获取元素;
增删效率慢:每改变数组中的任何一个元素,后面元素的索引都要发生变化;
1.2. LinkedList:
LinkedList是基于链表的数据结构;
无索引;
查询效率慢:因为是双向链表结构,需要从头节点开始不断遍历到目标节点,并逐个比对其中的元素;
增删效率快:链表在插入和删除方面很有优势,因为只需要改变相邻节点之间的指针即可;
ArrayList和LinkedList都是线程不安全的:
底层源码没有synchronized 关键字,也就是同步的意思,没有synchronized 就效率高,所有的线程都可以同时访问,不需要排队,只有Vector是线程安全的,底层源码有synchronized 关键字,只能排队访问;
2. Set集合(单列集合):
特点:添加的元素都是无序,不重复(可以去重),无索引的(没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引获取元素)。
- 无序性:Set集合中的元素是无序的,不能通过下标或者位置来访问元素。
- 元素唯一性:Set集合中的元素是唯一的,不会存在重复的元素。
- 可变性:Set集合的元素可以被增加、删除或者修改,因此是可变的。
- 哈希表实现:Java中的HashSet是通过哈希表来实现的,因此Set集合的插入、查询和删除操作都具有较快的速度。
- 线程不安全:Set集合不是线程安全的,如果需要在多线程环境下使用,需要进行同步处理。
- 支持数学中的集合运算:Set集合支持并集、交集、差集等运算,方便我们进行集合的操作。
- 不允许重复元素:Set集合中不允许重复元素的存在,当试图添加重复元素时,新元素不会被添加进集合中。
- 元素无序性:由于Set集合中元素的存储是无序的,遍历Set集合时获取元素的顺序是不确定的。
- 因为Set集合没有索引方法,所以不能用普通的for循环去遍历,也不能通过索引去获取、删除Set集合里面的元素,所以就需要通过迭代器去遍历获取。所有的集合都可以用迭代器去遍历。
2.1. hashSet:
无序、无索引、不重复;
底层:采用哈希表存储数据,哈希表是一种对于增删改查数据性能都比较好的结构;
哈希表的组成:JDK1.8之前:数组+链表;JDK1.8之后:数组+链表+红黑树;
HashSet底层存储数据:通过调用hashCode方法计算对象的哈希值,然后根据哈希值和数组长度计算出对象应存入哈希表的位置,然后该对象再调用equals方法和当前位置的所有对象依次比较,若存在相同属性值对象,则舍弃该对象不存,若不存在属性值相同对象,则将该对象存入当前位置。
注意:一定要重写hashCode和equals方法,这样才能保证实现不重复
哈希值细节:
根据hashCode方法算出来的int类型的整数
该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
一般情况下,都会重写hashCode方法,利用对象内部的属性值计算哈希值
如果没有重写hashCode方法,不同对象计算出来的哈希值是不同的
如果重写了hashCode方法,不同对象只有属性值相同,计算出来的哈希值就是一样的
小部分情况下,不同属性值或者不同地址值计算出来的哈希值也有可能一样。(哈希碰撞)
HashSet底层原理:
创建一个默认长度16,默认加载因子0.75的数组,数组名table
根据元素的哈希值跟数组的长度计算出应存入的位置
判断当前位置是否为null,如果是null直接存入
如果位置不为null,表示当前位置已经有元素,则调用equals方法比较属性值
一样:表明重复了,不存 ;不一样:存入数组,形成链表(JDK8以前:新元素存入数组,老元素挂在新元素下面;JKD8开始:新元素直接挂在老元素下面)
当元素个数达到16*0.75=12个时,数组会扩容为原来的2倍长度变成32
JDK8以后,当链表长度超过8且数组长度大于等于64时,自动转换为红黑树
如果集合中存储的是自定义对象,必须重写hashCode和equals方法
2.2. treeSet:
不重复、无索引、可排序
可排序:按照元素的默认规则(由小到大)排序
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都比较好
TreeSet集合默认规则:
对于数值类型:Integer,Double,默认是按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
2.3. linkedHashSet:
有序、不重复、无索引
有序指的是存储和取出元素的顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
2.4. SortedSet:
首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,不能在存储1了。
可排序:可以按照大小顺序排列。
Set集合总结:
-
LinkedHashSet集合的特点和原理是怎样的?
有序、不重复、无索引
底层基于哈希表,使用双链表记录添加顺序 -
以后如果要数据去重,我们应该使用哪个?
默认使用HashSet,因为HashSet的效率更高;
如果要求去重且存取有序,才使用LinkedHashSet;
List,Set集合总结:
-
如果想要集合中的元素可重复
用ArrayList集合,基于数组的。(用的最多) -
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表的 -
如果想对集合中的元素去重
用HashSet集合,基于哈希表的。(用的最多) -
如果想对集合中的元素去重,而且保证存取有序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet -
如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序
3. Map集合(双列集合):
特点:
- Map集合是一个双列集合,一个元素包含两个值(一个key,一个value);
- Map集合中的元素,key和value的数据类型可以相同,也可以不同;
- Map集合中的元素,key是不允许重复的,value是可以重复的;
- Map集合中的元素,key和value是一一对应;
3.1. HashMap:
HashMap是Map的实现类;
没有额外需要学习的方法,直接使用Map里面的方法就可以了;
特点都是由键决定的:无序、不重复、无索引;
HashMap跟HashSet地底层原理是一摸一样的,都是哈希表结构;
线程不安全:HashMap是基于哈希表实现的Map,它采用了数组+链表(或红黑树)的数据结构。HashMap中的键值对没有固定的顺序,允许存在一个null键和多个null值。HashMap的性能较好,在大多数场景下都能满足需求。HashMap是非线程安全的,因此在并发场景下需要进行同步处理。
3.2. TreeMap:
TreeMap跟TreeSet底层原理是一样的;
由键决定特性:可排序、不重复、无索引;
可排序:对键进行排序;
注意:默认按照键的从小到大排序,也可以自己规定键的排序规则;
线程不安全:TreeMap是基于红黑树实现的Map,它对键进行排序,因此在遍历时会按照键的自然顺序或者自定义的顺序进行遍历。TreeMap的性能相对较低,适合在键需要排序的场景下使用。
3.3. HashTable:
HashTable,中文意思是哈希表,也叫散列表。HashTable是一种利用哈希函数(Hash Function)进行数据存储的数据结构,通过将键(Key)映射到哈希表中的一个位置来访问记录,以加快查找的速度。一般情况下,哈希表主要包括以下两个部分:哈希函数和哈希表存储数组。哈希函数是将键映射到哈希表中的位置,而哈希表存储数组则用于存储记录。
特点:
- 快速查找:通过哈希函数可以快速定位到存储的数据位置,因此查找速度快。
- 支持高效的插入和删除操作:由于哈希表是通过哈希函数来确定数据存储位置,因此插入和删除数据时只需要计算一次哈希函数即可。所以哈希表的插入和删除操作速度快。
- 冲突问题:哈希函数并不是完美的,有时会出现多个键映射到同一个位置的情况,这种情况称为哈希冲突。为了解决哈希冲突问题,哈希表通常采用拉链法或开放寻址法。
缺点:
- 在多个线程中会发生锁冲突,HashTable是使用一把大锁,锁冲突概率很高
- 扩容的速度很慢
- size属性同步时也会发生锁冲突,效率变慢
线程安全:hashtable的大部分方法都是由关键字synchronized修饰
3.4. LinkedHashMap:
由键决定:有序、不重复、无索引;
这里的有序指的是保证存储和取出的元素顺序一致;
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序;
线程不安全:LinkedHashMap是基于哈希表和链表实现的Map,它可以维护键值对的插入顺序,或者按照最近访问的顺序进行遍历。LinkedHashMap是非线程安全的。
3.5. SortedMap:
SortedMap提供有序的Map实现,是对Map集合中键进行总体排序后的映射。
- SortedMap接口概述:
Map的主要实现有HashMap,TreeMap,HashTable,LinkedHashMap。其中的TreeMap实现了SortedMap接口,保证了有序性。默认的排序是根据Key值进行生序排序,也可以重写comparator方法来根据value进行排序。
由于乱序的数据对查找不利,例如无法使用二分法等降低算法的时间复杂度,如果数据在插入时就排好顺序,查找的性能就会提升很多。SortedMap接口就是为这种有序数据服务的,和SortedSet有异曲同工之妙,都是根据键的自然顺序进行排序,或者由通常在排序映射创建时提供的比较器进行排序。这个顺序在遍历排序映射的集合视图(由entrySet、keySet和values方法返回)时可以被体现出来。
- SortedMap接口使用原则:
SortedMap接口需要数据的键(key)需要支持Comparable接口,或者可以被指定的Comparator接受,即SortedMap使用需要遵守如下原则:
插入到排序映射中的所有键都必须实现Comparable接口(或被指定的比较器接受)。
此外,所有这些键必须是相互可比的,即可以执行k1. compareto (k2)或comparator.compare(k1, k2)操作。
不能对排序映射中的任何键k1和k2抛出ClassCastException。尝试违反此限制将导致违规的方法或构造函数调用抛出ClassCastException异常。
3.6. ConcurrentHashMap:
是Java集合框架中的一个并发容器,它是线程安全的哈希表的实现。它被设计为比Hashtable和SynchronizedMap(通过使用同步方法或块来保证线程安全)更高效和可扩展的替代品。
ConcurrentHashMap具有以下特点:
- 线程安全:ConcurrentHashMap使用锁分段技术来保证线程安全。它将整个数据结构分成多个段(Segment),每个段都有一个锁来控制对该段的访问。这样,不同的线程可以同时访问不同的段,从而提高并发性能。
- 高效性能:ConcurrentHashMap在并发环境下具有良好的性能。它可以支持多个读操作同时进行,而不会阻塞其他线程的写操作。这使得它非常适合于高并发的应用场景。
- 可扩展性:ConcurrentHashMap可以根据需要动态地调整其容量大小。当容量达到预设的阈值时,ConcurrentHashMap会自动进行扩容操作,以保证高效的性能。
- 可调整的一致性:ConcurrentHashMap提供了不同的一致性级别来满足不同应用场景的需求。它可以使用弱一致性(weak consistency)来提高并发性能,也可以使用强一致性(strong consistency)来保证数据的完整性。
总结:ConcurrentHashMap是Java集合框架中的一个线程安全的哈希表实现,它具有高效性能和可扩展性。通过使用锁分段技术,它可以支持高并发的读写操作,并提供不同的一致性级别来满足不同应用场景的需求。