文章目录
一、 集合框架
![](https://img-blog.csdnimg.cn/cda95720eece4b23bf9b66b102fb3c05.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、Vector、HashSet、LinkedHashSet、HashMap、LinkedHashMap …
1.集合和数组的区别
- 数组声明了它容纳的元素的类型,而集合不声明。
- 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
- 数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
- 数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
参考文献
集合中只能存储引用数据类型,想要存储8种基本数据类型(byte、int、short、long、float、double、boolean、char),需要将其装换成基本数据类型所对应的包装类(Byte、Integer、Short、Long、Double、Boolean、Character)
2.Collection集合的公共方法
![](https://img-blog.csdnimg.cn/de72fbe118b64b40a21c44790dad2910.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
3.常用集合的分类
Collection 接口的接口
|——List 接口:元素按进入先后有序保存,可重复
|——————├ LinkedList,实现类,适应于写多的场景,线程不安全
|——————├ ArrayList ,实现类, 适应于读多的场景, 线程不安全
|——————├ Vector,实现类,数组 ,线程安全
|————————├Stack extends Vector
|——Set 接口:不可重复
|——————├HashSet 使用hash表(Node数组)存储元素 ,无序
|————————├ LinkedHashSet 链表维护元素的插入次序 ,extends HashSet
|——————├TreeSet 底层实现为二叉树,元素排好序
Map 接口
|——————├Hashtable 接口实现类, 同步, 线程安全
|——————├HashMap 接口实现类 ,没有同步, 线程不安全-
|————————├ LinkedHashMap 双向链表和哈希表实现
|——————├TreeMap 红黑树对所有的key进行排序
4.集合遍历方式
- forEach(Consumer action)
- Iterator迭代器
- 增强for循环
二、常见集合类详解
2.1 ArrayList
原理解析
![](https://img-blog.csdnimg.cn/e58c34a20e6b44f3adcc5b303a73ec9c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制。ArrayList 继承了 AbstractList ,并实现了 List 接口。
ArrayList 底层的数据结构是数组,长度可以根据初始容量以及加载因子动态改变,有序,可重复,线程不安全,由于有序以及索引的存在,访问快,插入或者删除慢,适合读多写少的场景。
重要方法
//1.数字排序
ArrayList<Integer> myNumbers = new ArrayList<Integer>();
Collections.sort(myNumbers);
for (int i : myNumbers) {
System.out.println(i);
}
//2.字母排序
ArrayList<String> sites = new ArrayList<String>();
Collections.sort(sites); // 字母排序
for (String i : sites) {
System.out.println(i);
}
2.2 LinkedList
原理解析
![](https://img-blog.csdnimg.cn/2eed92e061e04f76be39fa0dc3753957.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
有序,可重复,线程不安全
在实现中采用链表数据结构。插入和删除速度快,访问速度慢。
2.3 Vector
Vector是JDK1.2出现集合框架之前的就存在的一种古老集合,不推荐使用,集合框架出现之后,将其作为List接口的实现之一。
Vector相比于ArrarList,是线程安全的,相对的来说性能较低。Stack是Vector的子类,用于模拟栈这种数据结构,同理也是线程安全的,同时也是一种比较古老的集合,不推荐使用。
解决多线程并发访问集合时的线程安全问题通常使用Collections集合工具类提供的方法,Collections提供了基本每种集合类型的同步方法: synchronizedXxx() 方法,以解决多个线程同时操作一个集合的并发问题:
//通过如下的方法保证List的线程安全性
List list2 = Collections.synchronizedList(list1);
System.out.println(list2);
2.4 HashSet
![](https://img-blog.csdnimg.cn/c182ac55d47b47ce9670b9b5ce00a9b1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_16,color_FFFFFF,t_70,g_se,x_16)
HashSet的特点:无序,不可重复,值可以为null且只能有一个,线程不安全;
HashSet类按照哈希算法来存取集合中的对象。ArrayList底层基于数组这种数据结构,查找速度快,存储删除慢;LinkedList底层是基于链表这种数据结构,存储删除快,查找慢;HashSet是数组和链表两种数据结构的组合,同时综合了两者的优点。
原理解析
HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75的HashMap。封装了一个HashMap对象来存储所有的集合元素,所有放在 HashSet中的集合元素实际上由 HashMap的key来保存,而 HashSet中的 HashMap的 value则存储了一个static、final类型的空对象。
![](https://img-blog.csdnimg.cn/972cc3ec599c4caf9c9e90b5c122e68d.png)
HashSet添加元素
当把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值(hashcode() ->java.lang.Object类中的固有方法,计算对象的散列码,这个散列码标识了对象在内存中位置)来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现,直接将对象添加到相应位置;但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同,如果两者相同,则覆盖旧元素。自定义的对象添加到hashset集合中时,要重写hashcode()以及equals()方法,自定义元素相等的规则。
添加自定义对象到hashset集合测试:
package myproject.collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Objects;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
//认为只要username相同就是同一个对象
private String username;
private String nickname;
}
package myproject.collection;
import java.util.HashSet;
public class TestHashSet {
public static void main(String[] args) {
HashSet<User> hashSet = new HashSet<User>();
User user1 = new User("a","hh");
User user2 = new User("a","ww");
User user3 = new User("b","zs");
User user4 = new User("c","ls");
hashSet.add(user1);
hashSet.add(user2);
hashSet.add(user3);
hashSet.add(user4);
System.out.println("集合中总共有"+hashSet.size()+"个对象:");//hashset的size->3
System.out.println(hashSet);
System.out.println("user1的hashcode:"+user1.hashCode());
System.out.println("user2的hashcode:"+user2.hashCode());
}
}
测试结果:
集合中总共有4个对象:
[User(username=c, nickname=ls), User(username=a, nickname=hh), User(username=a, nickname=ww), User(username=b, nickname=zs)]
user1的hashcode:12532
user2的hashcode:13012
测试结果并不符合我们的预期,我们想把username相同看成是同一个人,所以需要修改hashcode()以及equals()方法,如下所示:
package myproject.collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Objects;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
//认为只要username相同就是同一个对象
private String username;
private String nickname;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(username);
}
}
测试结果:
集合中总共有3个对象:
[User(username=a, nickname=hh), User(username=b, nickname=zs), User(username=c, nickname=ls)]
user1的hashcode:128
user2的hashcode:128
Hashset不能存储重复的对象,这里hashSet中要存储的是自定义的User对象,我们需要重写User中的hashcode以及equals方法,定义User对象的相等条件,否则按照默认的方式进行判断的话,只有地址相等,两个user才相等,显然不符合我们定义的相等规则User对象的名字相等即相等。
参考文献
HashSet查询元素
调用对象的hashcode()方法,找到对象的存在的桶,如果存在多个对象,对链表中的元素逐个遍历。
2.5 LinkedHashSet
原理解析
LinkedHashSet 继承自 HashSet, 唯一的区别是 LinkedHashSet 内部使用的是 LinkHashMap。 这样做的意义或者好处就是 LinkedHashSet 中的元素顺序是可以保证的, 也就是说遍历序和插入序是一致的。
2.6 TreeSet
基于TreeMap实现, 支持排序(自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序),非线程安全的。
2.7 HashMap
![](https://cdn.nlark.com/yuque/0/2021/png/22083525/1640243730480-afae3856-0bfd-466b-b8a4-06d380eb9567.png)
![](https://img-blog.csdnimg.cn/41a69f0190574648a8e543ddfae451ac.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjAyMeW5tOWFiG5ld-S4quWvueixoQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
HashMap特点:无序,不可重复,key和value值都可以为空,HashMap 根据key的值进行数据的检索,所以key不能重复,也就是key值只能有一个是空,线性不安全。
原理解析
HashMap 的主干是Node类型的数组,Node类包含的属性包含key,value,hash,next等,其中key,value属性保存键值对的值,hash值是对key进行hash后的结果,通过对HashMap 集合的size取模得到当前键值对在Node数组中的位置,方便元素的查找;当hash值发生碰撞时,将会把数组中的Node节点当做头结点维护一个链表,next属性指向下一个Node节点。
HashMap底层实现原理
2.8 LinkedHashMap
![](https://cdn.nlark.com/yuque/0/2021/png/22083525/1640243789153-d5e8c3bb-7b8c-4b33-b1cb-1456882a536c.png)
- LinkedHashMap继承HashMap,是HashMap的子类;
- LinkedHashMap的特点:有序,不可重复,线程不安全。HashMap 是无序的,LinkedHashMap通过维护一个额外的双向链表保证了迭代顺序,LinkedHashMap是HashMap+LinkedList的结合 。
原理解析
将所有 Entry 节点链入一个双向链表的 HashMap。 在 LinkedHashMap 中, 所有 put 进来的
Entry 都保存在哈希表中, 但由于它又额外定义了一个以 head 为头结点的双向链表, 因此
对于每次 put 进来 Entry, 除了将其保存到哈希表上外, 还会将其插入到双向链表的尾部。
2.9 Hashtable
HashTable的特点:无序,不能重复,key和value值都不能是null,线程安全,相当于Vector与ArrayList之间的关系,同样是一个古老的类,不推荐使用。
原理解析
HashTable底层与HashMap一样通过Hash表实现,关键的put、get等方法都是加了synchronized锁,线程安全,相对效率较低。
2.10 TreeMap
TreeMap的特点:相对于HashMap,TreeMap是有序的,不可重复,线程非安全;TreeMap 的查询、 插入、 删除效率均没有 HashMap 高, 一般只有要对 key 排序时才使用TreeMap;TreeMap 的 key 不能为 null, 而 HashMap 的 key 可以为 null。
原理解析
TreeMap底层是一个红黑树数据结构,每一个节点即是一个key-value键值对,需要根据key对节点进行排序。红黑树数据结构可以使得TreeMap中的所有键值对处于有序状态。排序包含自然排序和定制排序两种,自然排序中的key必须是同一个类的对象,同时实现Comparable接口;定制排序创建TreeMap时,需要传入Comparator对象,自己定义排序的规则。