阅读完需:约 27 分钟
集合概述
集合:集合是Java中提供的一种容器,可以用来存储多个数据。


集合和数组的区别:
(1)数组长度的是固定的,集合的长度是可变的。
(2)数组中存储的都是同一类型的元素。集合存储的都是对象,对象的类型可以不一致。
Java集合类主要由两个根接口Collection和Map派生出来的。Collection有三个子接口: List、Set、Queue(Java5新增的队列)。Java集合大致也可分成List、Set、Queue、Map四种接口体系,注意:Map不是Collection的子接口。
Map接口:双列数据,保存具有映射关系“key-value对”
Collection接口
Collection接口:单列数据,定义了存取一组对象的方法的集合。
Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和 Queue集合。
Collection接口的常用方法

Collection子接口之一:List接口
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。List集合默认按照元素的添加顺序设置元素的索引,可以通过索引(类似数组的下标)来访问指定位置的集合元素。
List接口的实现类主要有:ArrayList、LinkedList和Vector。
List接口的常用方法
返回值类型 | 方法名及描述 |
---|---|
boolean | add(E e) 将指定的元素追加到此列表的末尾(可选操作)。 |
void | add(int index, E element) 将指定的元素插入此列表中的指定位置(可选操作)。 |
boolean | addAll(Collection c) 按指定集合的迭代器(可选操作)返回的顺序将指定集合中的所有元素附加到此列表的末尾。 |
boolean | addAll(int index, Collection c) 将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。 |
void | clear() 从此列表中删除所有元素(可选操作)。 |
boolean | contains(Object o) 如果此列表包含指定的元素,则返回true。 |
E | get(int index) 返回此列表中指定位置的元素。 |
int | hashCode() 返回此列表的哈希码值。 |
int | indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 |
boolean | isEmpty() 如果此列表不包含元素,则返回 true 。 |
Iterator | iterator() 以正确的顺序返回该列表中的元素的迭代器。 |
int | lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
E | remove(int index) 删除该列表中指定位置的元素(可选操作)。 |
E | set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素。 |
int | size() 返回此列表中的元素数。 |
(1)List接口的实现类之一:ArrayList
ArrayList是List接口的主要实现类,ArrayList是一个动态数组,允许任何符合规则的元素插入包括null。 它能快速随机访问存储的元素,支持随机访问, 查询速度快, 增删元素慢。
ArrayList的JDK1.8之前与之后的实现区别:
JDK1.7:直接创建一个初始容量为10的数组
JDK1.8:一开始先创建一个长度为0的数组,当添加第一个元素时再创建一个初始容量为10的数组
(2)List接口的实现类之二:LinkedList
LinkedList是List接口的另一个实现,除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口。
LinkedList内部以链表的形式保存集合中的元素,所以随机访问集合中的元素性能较差,但在频繁的插入或删除元素时有较好的性能。
(3)List接口的实现类之三:Vector
Vector大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
面试题:
ArrayList和LinkedList的区别
都是线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。大多数清空下使用 ArrayList而不是Vector,因为同步完全可以由自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。
Collection子接口之二:Set接口
Set接口也是Collection的子接口,set接口没有提供额外的方法。
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则会添加操作失败。
Set集合判断两个对象是否相同是根据 equals() 方法,而不是使用 == 运算符。
(1)Set接口的实现类之一:HashSet
HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet不是线程安全的
- 集合元素可以是null
HashSet集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。
HashSet添加元素的原理如下:
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值,通过某种散列函数决定该对象在HashSet底层数组中的存储位置。
如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,则添加失败。如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接存储。
如果两个元素的equals() 方法返回true,但它们的 hashCode() 返回值不相等,hashSet将会把它们存储在不同的位置,但依然可以添加成功。
重写hashCode() 方法的基本原则
在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法的返回值应该相等。
对象中用作equals()方法比较的实例变量,都应该用来计算hashCode值。
(2)Set接口的实现类之二:LinkedHashSet
LinkedHashSet是HashSet的子类。
LinkedHashSet也是根据元素的hashCode值来决定元素的存储位置。但它同时使用双向链表维护元素的次序,元素的顺序与添加顺序一致。
由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
LinkedHashSet不允许集合元素重复。
(3)Set接口的实现类之三:TreeSet
TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据元素。
TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
自然排序:
TreeSet会调用集合元素的compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口。
实现Comparable的类必须实现compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比 较大小。
Comparable的一些典型实现类:
- BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小进行比较
- Character:按字符的 unicode值来进行比较
- Boolean:true 对应的包装类实例大于 false 对应的包装类实例
- String:按字符串中字符的 unicode 值进行比较
- Date、Time:后边的时间、日期比前面的时间、日期大
因为只有相同类的两个实例才会比较大小,所以向TreeSet中添加的应该是同一个类的对象。
对于TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj) 方法比较返回值。
当需要把一个对象放入TreeSet 中,重写该对象对应的equals() 方法时,应保证该方法与compareTo(Object obj) 方法有一致的结果。
对于TreeSet集合而言,它判断两个对象是否相等的标准是:两个对象通过compareTo(Object obj)方法比较是否返回0,如果返回0则相等。
定制排序
TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素,希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
Map接口
Map与Collection并列存在。用于保存具有映射关系的数据:key-value
Map中的key和value都可以是任何引用类型的数据
Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,须重写hashCode()和equals()方法
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties
(1)Map接口的实现类之一:HashMap
HashMap是Map接口使用频率最高的实现类。允许使用null键和null值,与HashSet一样,不保证映射的顺序。
所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
HashMap判断两个key相等的标准是:两个key通过equals() 方法返回true, hashCode值也相等。
HashMap判断两个value相等的标准是:两个value通过equals()方法返回true。
HashMap可以使用null值为key或value
HashMap源码中的重要常量
DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16
MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树。
UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size:HashMap中存储的键值对的数量
modCount:HashMap扩容和结构改变的次数。
HashMap的存储结构
JDK8之前版本:
JDK8之前HashMap的存储结构是数组+链表结构(即为链地址法) ,当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head。
JDK8之后版本:
JDK8之后HashMap的存储结构是数组+链表+红黑树实现。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
(2)Map接口的实现类之二:LinkedHashMap
LinkedHashMap 是 HashMap的子类,在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。 该链表负责维护Map的迭代顺序,与插入顺序一致,因此性能比HashMap低,但在迭代访问Map里的全部元素时有较好的性能。
(3)Map接口的实现类之三:TreeMap
TreeMap存储Key-Value对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value对处于有序状态。底层采用红黑树的数据结构。
TreeMap也有两种排序方式,自然排序和定制排序。
(4)Map接口的实现类之四:Hashtable
HashMap线程不安全,Hashtable是线程安全的。
HashMap可以使用null值为key或value,Hashtable不允许使用null作为key和value。
Hashtable实现原理和HashMap相同,底层都使用哈希表结构,查询速度快。
Hashtable和HashMap一样也不能保证其中Key-Value对的顺序。
(5)Map接口的实现类之五:Properties
Properties类是Hashtable 的子类,该对象用于处理属性文件。
由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型 。
Map的一些使用方式
怎么在java 8的map中使用stream
Map有key,value还有表示key,value整体的Entry。
创建一个Map:
Map<String, String> someMap = new HashMap<>();
获取Map的entrySet:
Set<Map.Entry<String, String>> entries = someMap.entrySet();
获取map的key:
Set<String> keySet = someMap.keySet();
获取map的value:
Collection<String> values = someMap.values();
上面我们可以看到有这样几个集合:Map,Set,Collection。
除了Map没有stream,其他两个都有stream方法:
所以要将map转化为set或者Collection来操作!!!
Stream<Map.Entry<String, String>> entriesStream = entries.stream();
Stream<String> valuesStream = values.stream();
Stream<String> keysStream = keySet.stream();
我们可以通过其他几个stream来遍历map。
使用Stream获取map的key
我们先给map添加几个值:
someMap.put("jack","20");
someMap.put("bill","35");
上面我们添加了name和age字段。
如果我们想查找age=20的key,则可以这样做:
Optional<String> optionalName = someMap.entrySet().stream()
.filter(e -> "20".equals(e.getValue()))
.map(Map.Entry::getKey)
.findFirst();
log.info(optionalName.get());
因为返回的是Optional,如果值不存在的情况下,我们也可以处理:
optionalName = someMap.entrySet().stream()
.filter(e -> "Non ages".equals(e.getValue()))
.map(Map.Entry::getKey).findFirst();
log.info("{}",optionalName.isPresent());
上面的例子我们通过调用isPresent来判断age是否存在。
如果有多个值,我们可以这样写:
someMap.put("alice","20");
List<String> listnames = someMap.entrySet().stream()
.filter(e -> e.getValue().equals("20"))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
log.info("{}",listnames);
上面我们调用了collect(Collectors.toList())将值转成了List。
使用stream获取map的value
上面我们获取的map的key,同样的我们也可以获取map的value:
List<String> listAges = someMap.entrySet().stream()
.filter(e -> e.getKey().equals("alice"))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
log.info("{}",listAges);
上面我们匹配了key值是alice的value。
map集合取并集,差集,交集
1.获取两个map的并集
/**
取Map集合的并集
@param map1 大集合
@param map2 小集合
@return 两个集合的并集
*/
public static Map<String, Object> getUnionSetByGuava(Map<String, Object> map1, Map<String, Object> map2) {
//获取map的key
Set bigMapKey = map1.keySet();
Set smallMapKey = map2.keySet();
//Sets.union--union()方法返回两个集合的并集,包含所有集合的元素,重复元素只会出现一次。
Set differenceSet = Sets.union(bigMapKey, smallMapKey);
Map<String, Object> result = Maps.newHashMap();
for (String key : differenceSet) {
//map1.containsKey(key) -- 用来验证是否存在此KEY值,KEY值必须是全符合,包含也是失败返回false。
if (map1.containsKey(key)) {
result.put(key, map1.get(key));
} else {
result.put(key, map2.get(key));
}
}
return result;
}
set.union() 返回两个集合的并集
union()方法返回两个集合的并集,包含所有集合的元素,重复元素只会出现一次。
语法:
set.union(set1,set2)
参数:
set1必填参数,合并的目标集合
set2选填参数,其他要合并的集合,多个集合之间用逗号隔开。
返回值:
返回一个新的集合。
MAP使用containsKey和containsValue方法,验证键值对是否存在此KEY或VAL值。
containsKey
containsKey
boolean containsKey(Object key)如果此映射包含指定键的映射关系,则返回 true。更确切地讲,当且仅当此映射包含针对满足 (key==null ? k==null : key.equals(k)) 的键 k 的映射关系时,返回 true。(最多只能有一个这样的映射关系)。
参数:
key - 测试是否存在于此映射中的键
返回:
如果此映射包含指定键的映射关系,则返回 true
抛出:
ClassCastException - 如果该键对于此映射是不合适的类型(可选)
NullPointerException - 如果指定键为 null 并且此映射不允许 null 键(可选)
用来验证是否存在此KEY值,KEY值必须是全符合,包含也是失败返回false。
例子:
package demo;
import java.util.HashMap;
import java.util.Map;
public class fordemo
{
public static void main(String[] args)
{
Map<String, String> paramMap=new HashMap<String, String>();
paramMap.put("bc", "aa");
paramMap.put("a", "bb");
System.out.println(paramMap.containsKey("b"));--返回false
System.out.println(paramMap.containsKey("a"));--返回true
}
}
containsValue
containsValue
boolean containsValue(Object value)如果此映射将一个或多个键映射到指定值,则返回 true。更确切地讲,当且仅当此映射至少包含一个对满足 (value==null ? v==null : value.equals(v)) 的值 v 的映射关系时,返回 true。对于大多数 Map 接口的实现而言,此操作需要的时间可能与映射大小呈线性关系。
参数:
value - 测试是否存在于此映射中的值
返回:
如果此映射将一个或多个键映射到指定值,则返回 true
抛出:
ClassCastException - 如果该值对于此映射是不合适的类型(可选)
NullPointerException - 如果指定值为 null 并且此映射不允许 null 值(可选)
用来验证是否存在此Value值,Value值必须全部符合,包含也是返回false。
例子:
package demo;
import java.util.HashMap;
import java.util.Map;
public class fordemo
{
public static void main(String[] args)
{
Map<String, String> paramMap=new HashMap<String, String>();
paramMap.put("1", "b");
paramMap.put("2", "b");
paramMap.put("3", "ab");
paramMap.put("4", "cc");
System.out.println(paramMap.containsValue("b"));--返回true
System.out.println(paramMap.containsValue("a"));--返回false
System.out.println(paramMap.containsValue("cc"));--返回true
}
}

2.获取两个map差集
/**
取Map集合的差集
@param bigMap 大集合
@param smallMap 小集合
@return 两个集合的差集
*/
public static Map<String, Object> getDifferenceSetByGuava(Map<String, Object> bigMap, Map<String, Object> smallMap) {
Set bigMapKey = bigMap.keySet();
Set smallMapKey = smallMap.keySet();
//difference -- difference()函数用户返回两个集合的差集,即返回的在第一个集合但不在第二个集合中的元素
Set differenceSet = Sets.difference(bigMapKey, smallMapKey);
Map<String, Object> result = Maps.newHashMap();
for (String key : differenceSet) {
result.put(key, bigMap.get(key));
}
return result;
}
difference:
描述:
difference()函数用户返回两个集合的差集,即返回的在第一个集合但不在第二个集合中的元素
语法:
set1.difference(set2)
实例:
x={'a','b','c'}
y={'a','d','e'}
print(x.difference(y))
#输出
{'b', 'c'}

3.获取连个map交集
/**
取Map集合的交集(String,String)
@param map1 大集合
@param map2 小集合
@return 两个集合的交集
*/
public static Map<String, Object> getIntersectionSetByGuava(Map<String, Object> map1, Map<String, Object> map2) {
Set bigMapKey = map1.keySet();
Set smallMapKey = map2.keySet();
Set differenceSet = Sets.intersection(bigMapKey, smallMapKey);
Map<String, Object> result = Maps.newHashMap();
for (String key : differenceSet) {
result.put(key, map1.get(key));
}
return result;
}
intersection:
描述:
intersection()方法用于返回两个或更多集合中都包含的元素,即交集。
语法:
set.intersection(set1,set2,...)
参数:
- set1–必需,要查找相同元素的集合
- set2–可选,其他要查找相同元素的集合,可以多个,多个逗号隔开
返回值:
- 返回一个新的集合
实例:
x={'a','b','c'}
y={'a','d','e'}
print(set.intersection(x,y))
#输出
{'a'}

java8中map新增方法
getOrDefault
如果Map中不存在该key,可以提供一个默认值,方法会返回改默认值。如果存在该key,返回键对应的值。
java8之前的写法:
Map<String, String> map = new HashMap<>();
String value = "D";
if(map.containsKey("d")) {
value = map.get("d");
}
java8:
String value = map.getOrDefault("d", "D");
forEach
forEach遍历map,对map中的每个映射执行action指定的操作
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
// 遍历
map.forEach((k, v)-> {
System.out.println(k + "=" + v);
map.put(k, k + v);
});
// 输出
// a=A
// b=B
map.forEach((k, v)-> {
System.out.println(k + "=" + v);
});
// 输出
// a=aA
// b=bB
putIfAbsent
putIfAbsent(K key, V value)只有在不存在key值的映射或者映射值为null,才将value值赋值给key。否则不做修改。该方法将条件判断和赋值合二为一。
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
String e = map.putIfAbsent("e", "E");
String b = map.putIfAbsent("b", "E");
System.out.println("返回e="+e); //返回e=null
System.out.println("键e="+map.get("e"));//键e=E
System.out.println("返回b="+b);//返回b=B
System.out.println("键b="+map.get("b"));//键b=B
remove
只有在当前Map中key映射的值等于value时才删除该映射,否则什么也不做。
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
map.remove("a", "B");
map.remove("b", "B");
System.out.println(map.get("a")); // A
System.out.println(map.get("b")); // null
replace(K key, V value)
只有在当前Map中包含key,才用value去替换原来的值,否则什么也不做。
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
map.replace("c", "1");
map.replace("b", "1");
System.out.println(map.get("c")); // null
System.out.println(map.get("b")); // 1
replace(K key, V oldValue, V newValue)
只有在当前Map中key的映射存在且映射的值等于oldValue时才用newValue去替换原来的值,否则什么也不做。
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
map.replace("a", "1", "2");
map.replace("b", "B", "2");
System.out.println(map.get("a")); // A
System.out.println(map.get("b")); // 2
replaceAll
作用是对Map中的每个映射执行function指定的操作,并用function的执行结果替换原来的value
Map<String, String> map = new HashMap<>();
map.put("a", "A");
map.put("b", "B");
map.replaceAll((k, v) -> v.toLowerCase());
map.forEach((k, v)-> {
System.out.println(k + "=" + v);
});
// a=a
// b=b
merge
如果Map中key对应的映射不存在或者为null,则将value关联到key上;否则执行remappingFunction,如果执行结果为null则删除key的映射,否则用该结果跟key关联。
Map<String, String> map = new HashMap<>();
map.put("e", "E");
map.merge("f", "F", String::concat);
map.merge("e", "F", String::concat);
System.out.println("map.get(\"f\")="+map.get("f")); // map.get("f")=F
System.out.println("map.get(\"e\")="+map.get("e")); // map.get("e")=EF
compute
Map<String, String> map = new HashMap<>();
map.put("b", "B");
String val = map.compute("b", (k, v) -> null);
String val2 = map.compute("c", (k, v) -> v + "+v");
System.out.println(val); // null
System.out.println(val2); // null+v
computeIfAbsent
当Map中不存在key值的映射或映射值为null时,调用mappingFunction,并在mappingFunction执行结果非null时,将结果赋值给key。
List<String> list = Lists.newArrayList("a", "b", "b", "c", "c", "c", "d", "d", "d", "f", "f", "g");
Map<String, List<Integer>> positionsMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
positionsMap.computeIfAbsent(list.get(i), k -> Lists.newArrayListWithCapacity(1)).add(i);
}
System.out.println(positionsMap);
// {a=[0], b=[1, 2], c=[3, 4, 5], d=[6, 7, 8], f=[9, 10], g=[11]}