文章目录
- 一、集合分类
- 二、遍历集合方式
- 三、单列集合
- 3.1 List
- 3.1.1 ArrayList底层分析
- 3.1.2 LinkedList底层分析
- 3.2 Set
- 3.2.1 HashSet(无序)底层分析
- 3.2.2 LinkedHashSet(有序,存取一致)
- 3.2.3 TreeSet(可排序)
- 四、双列集合Map
- 4.1 HashMap(无序)
- 4.2 LinkedHashMap(有序)
- 4.3 TreeMap(可排序)
一、集合分类
- 单列集合(List Set Queue)
- 双列集合(Map)
注意点:Collection是一个接口,Collections是一个工具类,Map不是Collection的子接口。
二、遍历集合方式
创建一个List集合:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
- 传统for循环下标索引(只适合List)
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
缺点:只有List能使用;通过get方法获取元素效率较低。
- iterator迭代器循环(读取时不能对元素进行添加删除)
// 获取迭代器
Iterator<String> iterator = list.iterator();
// hasNext()判断迭代器当前指向是否存在值,next()得到当前迭代器指向的值并后移一位
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
注意点:使用迭代器时不能对集合进行添加或删除元素。
- 增强for循环(对增强for循环内的值进行修改是不会影响集合本身的)
for(String s: list) {
s = "12";
System.out.println(s);
}
for(String s: list) {
System.out.println(s);
}
注意点:
第一次增强for循环输出的是三次12,第二次增强for循环输出的是1 2 3,显然第一次循环对集合的修改不会影响集合本身的值;
同样,增强for循环是基于iterator实现的,因此在使用时不能添加或删除元素。
- lambda表达式
list.forEach(s -> System.out.println(s));
总结:传统的通过索引取值的方式只适合List且效率较低;
通过迭代器获取元素效率较高,并且增强for循环简化了获取迭代器的步骤,推荐使用;
lambda表达式代码最简单,但可读性差,推荐在代码简单时使用。
三、单列集合
3.1 List
List特点:有序(存和取顺序一致),可重复,有索引。
List分为ArrayList和LinkedList。
3.1.1 ArrayList底层分析
1.开始创建一个新数组时,数组大小size为0
2.向数组添加一个元素时,数组大小初始化size为10
3.当添加一个元素时,超过了数组大小10,数组进行扩容,扩容为原来大小的1.5倍,也就是15
4.当一次添加多个元素时,超过了数组大小15,数组进行扩容,此时不再扩大为原来的1.5倍,而以新添加的元素大小为准
例如添加100个元素,此时数组大小为15+100=115
注意点:ArrayList是一个动态数组,初始大小为10。当添加第一个元素时,数组大小才为10,否则为空。当添加单个元素时,超过数组大小则数组扩容为原来的1.5倍,当添加多个元素时,超过数组大小则以添加的元素多少进行扩容。
数组(Array)和ArrayList的区别:
1.数组可以包含基本数据类型和对象,ArrayList只能包含对象;
2.数组大小是固定的,ArrayList是动态变化的;
3.ArrayList提供了更多方法和特性。
3.1.2 LinkedList底层分析
LinkedList底层是一个双向链表。链表结点由前一个地址值,当前元素,下一个地址值三部分组成,还存储了链表中的第一个和最后一个结点地址。
由于LinkedList是一个双向链表,可以实现队列和栈。
3.2 Set
Set特点:无序(输入的顺序和输出的顺序不一定一样),不可重复,无索引。
Set下的实现类如下图所示,Set分别被HashSet、TreeSet实现,HashSet下还有一个LinkedHashSet类。
预备知识:平衡二叉树(任意结点的左右子树高度不超过1)、红黑树(是一个二叉查找树但不是高度平衡,满足特有的红黑规则)
hashCode方法默认根据地址值计算哈希值,一般需要重写,重写根据对象属性值计算哈希值,那么只要对象属性值相同,计算出来的哈希值就相同。
hashCode计算出来的哈希值可能会出现重复的情况。
3.2.1 HashSet(无序)底层分析
特点:无序,不可重复,无索引。
底层结构:哈希表(JDK8以前:数组+链表,JDK8以后:数组+链表+红黑树)
实现:
1.创建一个默认长度为16,默认加载因子为0.75的数组。
2.根据哈希值和数组大小计算存入位置。idx =h & (length - 1)。
3.判断存入位置是否为空,若为空,直接存入。
4.若不为空,调用equals比较属性值是否一样。
5.若一样,则不用存入,若不一样,则形成链表存入。
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
6.若超过16*0.75=12数组大小,则扩容数组大小两倍为32。
7.JDK8以后,当链表长度超过8且数组长度大于等于64时,链表转换为红黑树。
为什么HashSet是无序的?
当HashSet取元素时,按数组遍历,再根据数组中的链表遍历,因此存取是无序的。
为什么HashSet无法通过索引取值?
由于HashSet是由数组+链表+红黑树组成的,因此不方便使用索引读取。
HashSet是如何保证无重复元素的?
通过HashCode和equals保证HashSet内部元素是不重复的。
HashSet默认创建长度16,加载因子0.75的数组。
3.2.2 LinkedHashSet(有序,存取一致)
特点:有序,不可重复,无索引。
继承HashSet。
实现:
基于哈希表,额外使用双链表记录结点存储顺序。
3.2.3 TreeSet(可排序)
特点:可排序,不可重复,无索引。默认输出数值从小到大进行排序,字符字符串按ASCII码表中的数字升序进行排序)。
实现:底层是红黑树(不需要重写hashCode和equals),增删改查性能较好。
四、双列集合Map
HashSet、LinkedHashMap、TreeSet底层都是基于Map实现的,也就是Map中的底层结构是和Set类似的,只是Map中是根据Key值判断是否存在某元素。我们只要熟悉了Set,Map的原理和Set都是类似的。
4.1 HashMap(无序)
原理类似HashSet。
通过键计算哈希值,如果存入位置不为空,判断属性值是否一样,若一样,替换原来的值。
4.2 LinkedHashMap(有序)
同上,基于双链表实现输入和输出一致。
4.3 TreeMap(可排序)
同上,基于红黑树实现输出的升序。