目录
一、集合框架图
主要要了解三大种集合,Set、List、Map
二、Set
Set集合通常不能记住元素的添加顺序。实际上Set就是Collection只是行为略有不同(Set不允许包含重复元素)
1、HashSet
(1)底层实现(数组+链表+红黑树)
HashSet 的内部采用了HashMap作为数据存储,HashSet其实就是在操作HashMap的key,
而HashMap是采用哈希表存储数据的,哈希表的结构,jdk1.8之后,就是数组+链表+红黑树
(2)是否有序(无序)
因为HashMap是无序的,因此HashSet也不能保证元素的顺序
(3)是否唯一(唯一)
因为无序,所以唯一
(4)是否线程安全(不安全)
因为HashSet中没有对应同步的操作,因此是线程不安全的
(5)是否支持null(允许有一个null)
支持null元素(因为hashMap也支持null键和null值)
(6)遍历方式(迭代器、增强for循环)
两种遍历方式
- 迭代器
- 增强for循环
public static void main(String[] args){
Set<String> set = new HashSet<String>();
set.add("a1");
set.add("b2");
set.add("c3");
set.add("d4");
// 初始化一个HashSet集合
System.out.println("--------原HashSet集合----------");
System.out.println(set);
System.out.println();
// 方法一:
Iterator<String> iterator = set.iterator();
System.out.println("--------迭代器遍历HashSet----------");
while(iterator.hasNext()){
System.out.print(iterator.next()+",");
}
// 方法二:
System.out.println("--------加强for循环遍历---------");
for (String item : set) {
System.out.print(item+",");
}
}
2、LinkedHashSet
(1)底层实现(数组+双向链表+红黑树)
LinkedHashSet基于LinkedHashMap开发的,其构造函数中调用父类的构造函数,而在它的父类的这个有参构造函数中,new了个LinkedHashMap
这个super就是调用父类构造函数,点进去发现:
(2)是否有序(有序)
在添加一个元素时,先求 hash 值,在求索引,确定该元素在hashtable的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加,和 hashset 一样)
(3)是否唯一(唯一)
唯一
(4)是否线程安全(不安全)
也没有对应同步的操作,因此是线程不安全的
(5)是否支持null(允许有一个null)
支持null元素(因为hashMap也支持null键和null值)
(6)遍历方式(迭代器、增强for循环)
跟HashSet遍历方式一样
3、TreeSet
(1)底层实现(数组+双向链表+红黑树)
TreeSet 底层实际使用的存储容器就是 TreeMap,底层是红黑树,查找和增删效率高
(2)是否有序(有序)
需要实现Comparable接口,并重写compareTo()方法,然后放入TreeSet中的数据会按照一定规则排序
(3)是否唯一(唯一)
(4)是否线程安全(不安全)
(5)是否支持null(不支持null值!!!)
TreeSet中不能放入null元素
(6)遍历方式(迭代器、增强for循环)
(7) 如果要放入TreeSet中的类没有实现Comparable接口重写compareTo(),则会抛出异常
三、List
1、ArrayList
(1)底层实现(数组)
所以查找快,增删慢,因为增删的时候可能涉及到要移动后续的所有数据
(2)是否有序(有序)
按照存放的顺序排序
(3)是否唯一(不唯一)
(4)是否线程安全(不安全)
非常适合用于对元素进行查找,效率非常高
(5)是否支持null(支持,而且可以存放多个null)
(6)遍历方式(迭代器、增强for循环、普通for循环)
- 普通for循环:根据下标,从0,遍历到list.size()-1,通过list的get(下标)方法
- 增强for循环
- iterator迭代器:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("bbba");
list.add(null);
list.add("bbbb");
list.add("aaaa");
list.add(null);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("--------------------");
for (String s : list) {
System.out.println(s);
}
System.out.println("--------------------");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
(7)初始容量和扩容(ArrayList底层数组每次扩容的大小都是1.5倍)
初始0,放入一个元素时是扩容到10,之后都是增加为之前的1.5倍(向下取整)
2、LinkedList
(1)底层实现(双向链表)
(2)是否有序(有序)
按照存放的顺序排序
(3)是否唯一(不唯一)
(4)是否线程安全(不安全)
(5)是否支持null(支持,而且可以存放多个null)
(6)遍历方式(迭代器、增强for循环、普通for循环)
3、Vector
(1)底层实现(数组)
(2)是否有序(有序)
按照存放的顺序排序
(3)是否唯一(不唯一)
(4)是否线程安全(安全)
(5)是否支持null(支持,而且可以存放多个null)
(6)遍历方式(迭代器、增强for循环、普通for循环)
(7)初始容量和扩容(ArrayList底层数组每次扩容的大小都是2倍)
初始0,放入一个元素时是扩容到10,之后都是增加为之前的2倍
四、Map
1、HashMap
HashMap非常重要!!!所以要写得详细一点
(1)put()的实现原理
- 首先将k,v封装到Node对象当中(节点)
- 然后它的底层会调用K的hashCode()方法得出hash值
- 通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖
(2)get()的实现原理
- 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
- 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
(3)红黑树
jdk1.8最重要的就是引入了红黑树的设计
当hash表的长度超过了8,链表结构就会转成红黑树,好处就是避免在最极端的情况下链表变得很长很长,在查询的时候,效率会非常慢
- 红黑树查询:其访问性能近似于折半查找,时间复杂度 O(logn);
- 链表查询:这种情况下,需要遍历全部元素才行,时间复杂度 O(n);
( 对于红黑二叉树而言,主要包括三大基本操作:左旋、右旋、着色。很复杂,感兴趣的可以专门找有关的书或者文章看看)
(4)底层实现(数组+链表+红黑树)
(5)是否唯一(key唯一,value随便)
如果两次put的key是相同的,后面的会覆盖前面的value
(6)是否线程安全(不安全)
(7)是否支持null(支持一个值为null的key)
value随便null不null
(8)初始容量与扩容(初始16,扩容*2)
(9)遍历方式(迭代器、foreach、Lambda、Stream API)
【迭代器(entrySet()或keySet()点iterator),foreach(keySet()或entrySet()),Lambda,Stream API(stream()或parallelStream())】
遍历方式1:entrySet+迭代器+while,然后用next().getKey()和next().getValue()依次获取键和值
遍历方式2:entrySet+foreach,Set集合每一个元素是Node(Node节点中有Key和value),循环获取每个节点node,然后通过node.getKey()和node.getValue()来获取键值
遍历方式3:keySet+迭代器+while,.next()获取所有key,map.get(key)获取对应的value
遍历方式4:keySet+foreach
遍历方式6:Stream API单线程,stream()
遍历方式7:Stream API多线程,parallelStream()
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("bb", "值1");
map.put(null, "值null1");
map.put("aa", "值2");
map.put("aa", "值3");
map.put(null, "值null2");
map.put("cc", "值4");
map.put("cc", null);
System.out.println("===========遍历方式1:entrySet迭代器===========");
Iterator<Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, String> next = iterator.next();
System.out.println(next.getKey() + ":" + next.getValue());
}
System.out.println("===========遍历方式2:entrySet节点(一个节点包含一对键值)===========");
Set<Entry<String, String>> nodes = map.entrySet();
for (Entry<String, String> node : nodes) {
System.out.println(node.getKey() + ":" + node.getValue());
}
System.out.println("===========遍历方式3:KeySet迭代器===========");
Iterator<String> iterator2 = map.keySet().iterator();
while (iterator2.hasNext()) {
String key = iterator2.next();
System.out.println(key + ":" + map.get(key));
}
System.out.println("===========遍历方式4:keySet获取所有key===========");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + ":" + map.get(key));
}
System.out.println("===========遍历方式5:Lambda表达式===========");
map.forEach((key, value) -> {
System.out.println(key + ":" + value);
});
System.out.println("===========遍历方式6:Stream API单线程===========");
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
System.out.println("===========遍历方式7:Stream API多线程===========");
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
}
2、LinkedHashMap(有序)
(1)底层实现(数组+双向链表(用来保持顺序)+红黑树)
(2)是否有序(有序)
(3)是否唯一(key唯一,value随便)
(4)是否线程安全(不安全)
(5)是否支持null(支持一个值为null的key)
value随便null不null
(6)初始容量和扩容(初始16,扩容*2)
(7)遍历方式(同HashMap)
3、HashTable(线程安全&键值都不能null)
(1)底层实现(数组+链表+红黑树)
(2)是否支持null(键值都不能为null)
(3)是否唯一(key唯一,value随便)
(4)是否线程安全(安全)
(5)初始容量和扩容(初始容量11,扩容2n+1)
(6)遍历方式(同HashMap,另外还有keys方法获取所有key,elements方法获取所有element)
通过map.keys().nextElement()依次获取键
通过map.elements().nextElement()依次获取值
void testHashTable() {
Hashtable<String, String> map = new Hashtable<String, String>();
map.put("bb", "值1");
map.put("aa", "值2");
map.put("aa", "值3");
map.put("cc", "值4");
System.out.println("===========补充遍历方式1:keys===========");
Enumeration<String> keys = map.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
System.out.println(key + ":" + map.get(key));
}
System.out.println("===========补充遍历方式2:elements===========");
Enumeration<String> elements = map.elements();
while (elements.hasMoreElements()) {
String value = elements.nextElement();
System.out.println("key未知:"+value);
}
}
4、TreeMap(有序)
(1)底层实现(数组+链表+红黑树)
(2)有序,继承SortedMap类
(3)是否唯一(key唯一,value随便)
(4)是否线程安全(不安全)
(5)是否支持null(键不能为null)
(6)遍历方式(同HashMap)
(7)自定义排序(两种方式)
- 在类中实现Comparable,重写compareTo方法
- 在构造函数中new Comparator,匿名内部类,重写compare 方法
TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
if (s1.age<s2.age){
return 1;
}
if (s1.age>s2.age){
return -1;
}
return 0;
}
});
5、ConcurrentHashMap
ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合; ConcurrentHashMap是一个线程安全,并且是一个高效的HashMap
五、总结
1、是否可重复
- 所有Set都不允许重复
- 所有List都可以重复
- 所有Map的key都不能重复
2、是否允许null
- 所有Set都允许一个null
- 所有List都允许多个null
- HashTable和ConcurrentHashMap键值对都不允许是null,TreeMap不允许键是null,值可以,其余的Map都允许有一个键为null
3、是否线程安全
- Set都不安全
- List中Vector安全
- Map中HashTable,ConcurrentHashMap安全
4、是否有序
- HashSet无序,LinkedHashSet和TreeSet有序(但LinkedHashSet是按照存放的顺序排序,TreeSet是按照指定的规则排序(Comparable接口的compareTo方法))
- List都有序
- LinkedHashMap和TreeMap有序,其余的其实不是百分百有序,会受初始化大小影响(但LinkedHashMap是按照存放的顺序排序,TreeMap是按照指定的规则排序(Comparable接口的compareTo()方法,或者写匿名内部类Comparator类的compare()方法))