1.常见的集合
Collection接口和Map接口是所有集合的父接口;Collection接口的子接口有List接口和Set接口
Collection接口:
List接口:ArrayList,LinkedList,Stack,Vector
Set接口:HashSet,TreeSet,LinkedHashSet
Map接口:HashMap,TreeMap,HashTable,LinkedHashMap,ConcurrentHashMap,Properties(常用于读取配置文件)
2.常见集合的底层实现的结构
Collection类型集合:
List接口:有序,元素可重复,支持下标和迭代器遍历元素
- ArrayList:数组实现,非线程安全
- LinkedList:双向链表实现,非线程安全
- Stack:数组实现,先进后出FILO,线程安全
- Vector:数组实现,先进先出FIFO,线程安全
Set接口:大部分无序,部分有序,仅支持迭代器遍历元素,加入Set集合的对象需要重写equals()方法
- HashSet:HashMap实现,非线程安全
- TreeSet:TreeMap实现,非线程安全,有序
- LinkedHashSet:LinkedHashMap实现,非线程安全
Map类型集合:部分有序,key-value类型的集合
- HashMap:数组+链表实现,jdk1.8链表长度大于8时链表转为红黑树,非线程安全
- HashTable:数组+链表实现,线程安全
- TreeMap:红黑树实现,有序,非线程安全
- LinkedHashMap:HashMap实现,有序,非线程安全
- ConcurrentHashMap:数组+链表实现,线程安全
- Properties:线程安全
3.HashMap与HashTable的区别
HashMap:非线程安全,效率高,key和value可以为null;
HashTable:线程安全,底层相关函数使用synchronized同步锁修饰,效率低于HashMap,key与value都不能为null。
4.集合排序实现方式
- comparable方式:重写compareTo方法,也叫自然排序
- comparator方式:重写compare方法,按需编写排序规则的类,因此也叫定制排序
import java.util.*; /** * @Description * @Author YMJ * @DateTime 2020-07-26 20:46 * @Version V1.0.0 */ public class CollectionSort { public static class Student implements Comparable<Student>{ String username; int age; public Student(String username, int age) { this.username = username; this.age = age; } @Override public int compareTo(Student o) { if (this.username.compareTo(o.username)<0){//按名字降序排列 return 1; } if (this.age > o.age){//按年龄从大到小排序 return -1; } return 0; } @Override public String toString() { return "Student{" + "username='" + username + '\'' + ", age=" + age + '}'; } } public static class CollectionSortRule implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { if(o1.username.compareTo(o2.username)<0){//按名字自然排序 return -1; } if((o1.username.compareTo(o2.username) == 0) && (o1.age < o2.age)){//按年龄从小到大排序 return -1; } return 0; } } public static void main(String[] args) { List<Student> list = new ArrayList(); Student s1 = new Student("yu",22); Student s2 = new Student("ming",23); Student s3 = new Student("ju",21); Student s4 = new Student("ju",26); list.add(s1); list.add(s2); list.add(s3); list.add(s4); System.out.println("使用Comparable进行排序:名字降序,年龄降序"); Collections.sort(list); list.forEach(System.out::println); System.out.println("使用Comparator自定义排序类进行排序:名字升序,年龄升序"); list.sort(new CollectionSortRule()); list.forEach(System.out::println); System.out.println("使用Comparator自定义匿名内部排序类进行排序:名字降序,年龄降序"); list.sort(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { if (o1.username.compareTo(o2.username) > 0) {//按名字降序排列 return -1; } if (o1.username.compareTo(o2.username) == 0) {//按年龄从大到小排序 return o2.age - o1.age; } System.out.println(o2.username.compareTo(o1.username)); return 0; } }); list.forEach(System.out::println); } }
使用Comparable进行排序:名字降序,年龄降序 Student{username='yu', age=22} Student{username='ming', age=23} Student{username='ju', age=26} Student{username='ju', age=21} 使用Comparator自定义排序类进行排序:名字升序,年龄升序 Student{username='ju', age=21} Student{username='ju', age=26} Student{username='ming', age=23} Student{username='yu', age=22} 使用Comparator自定义匿名内部排序类进行排序:名字降序,年龄降序 Student{username='yu', age=22} Student{username='ming', age=23} Student{username='ju', age=26} Student{username='ju', age=21}
5.HashMap(jdk1.7)知识点
hashmap是基于hash函数来存取数据的,hashmap存储的是键值对Entry(key-value);其数据结构是数组加链表的形式。
- put的工作原理
put(key,value)时,首先对key对象进行hash计算得到器hashcode(即散列码),然后通过indexfor方法让hashcode与(数组长度lenght-1)相与&得到bucket位置。如果此位置上没有元素,那么就直接插入这个键值对;如果有元素(hash冲突),那么就去调用当前位置上键值对的key对象与待插入键值对的key对象做equal比较,如果键对象相同则比较其值对象,如果值不同则在此位置插入值以更新其值,如果值相同则不做任何操作;如果此位置上的键对象与待插入键值对的键对象不同,则它会插入到此位置链表的头结点上。
- get的工作原理
get(key)时计算key对象的hashcode并通过indexfor计算器bucket位置,找到后遍历此位置链表上的所有节点上的键值对,将节点上的key对象与指定的key对象进行equal比较,直到找到相等的后返回对应的值对象。
- hashmap的扩容原理
默认的hashmap初始数组大小为16,其负载因子为0.75,也就是在一般情况下元素个数到达16*0.75=12时就会扩容,它是重新生成一个数组,大小为原来数组的2倍,(在jdk1.8中链表节点长度大于8时会生成红黑树),这一步叫做resize;然后对原来数组中的对象再一次进行hash计算,这一步叫做rehash,然后正在进行indexfor计算出bucket位置,将此元素插入到新数组当中
- 在多线程中重新调整hashmap大小引发的问题
会产生条件竞争,在hashmap重新调整大小时链表表中的数据顺序会倒转过来,因为hashmap每次往链表插入数据插入的是头部(jdk1.8及之后则是采用尾插法),这是为了防止尾部遍历,条件竞争会产生死循环,因为hashmap本来就不是线程安全的,所以不推荐在多线程下使用hashmap,可以选择使用concurrenthashmap或则hashTable
6.concurrentHashMap(jdk1.7)知识点
concurrentHashMap1.7采用数据结构是segment数组,每个segment的数据结构是hashEntry数据,hashEntry的数据结构是链表,segment继承ReentrantLock,所以在写入时需要获取锁进行操作。(分段锁+数组+链表)
- GET操作
key通过两次hash计算在segment数组的位置,在进行一次hash计算出在hashEntry数组中的位置,后面的操作和hashmap一致。整个get过程不需要加锁,除非读到的值为空才会加锁重读(可能某个线程删除节点导致),使用volatile多线程读取的正确性。
- PUT操作
key通过两次hash计算在segment数组的位置,在进行一次hash计算出在hashEntry数组中的位置,比对过程和hashMap一致,在插入前会先获取锁,首先判断当前这个segment的hashEntry数组是否需要扩容,不需要则进行插入操作,需要的话则进行扩容后在插入(只会扩容当前所在的segment里的hashEntry数组)。
- 删除操作
删除节点的前面所有节点复制一遍然后用头插法插入链表,原因是每个节点的next是final的不可修改。