Java 集合
1.讲一下Java 中主要有哪些集合
Java 集合常用的是两种类型的容器,一种是集合Collection ,另一中是图 Map,存储key-value 键值对
Collection 又有三个子类型: List 、Set、Queue ,再下面就是抽象类,以及具体的实现.
常用的是ArrayList, LinkedList ,HashSet , LinkedHashSet ,TreeSet ,HashMap ,LinkedHashMap , TreeMap ,Hashtable
2. ArrayList 与 LinkedList 的区别
ArrayList 采用的是数组的形式存储数据,是非线程安全的,也有线程安全的存储是Vector ,在新增元素的方法上加了Synchronized 锁,线程安全但是也降低了效率。
LinkedList 存储采用的是双向链表的数据结构。在操作增加、删除较多的环境中采用LinkedList 效率较高,在使用下标查询、更新较多的环境中使用ArrayList ,各有优缺点。
ArrayList 数组结构在存储数据时,未指定容量,默认的容量是 10 ,在增加扩容:是原来长度的1.5倍,使用Arrays.copy() 复制整个数组,相比Vector 则扩容 1 倍
LinkedList 双向链表,可以头插尾插,因此可以当作队列、栈使用,其没有同步方法,在多线程清空下,使用Collections.synchronizedList ()工具类使其变为同步容器
3.HashMap 的底层实现
HashMap 数据结构采用数组+链表的结构存储。存储的过程是根据 key 值获取hash值,再计算下标 index = h%length ,其中length 为数组长度
若该 index 已经存在值,则发生哈希碰撞。可以通过开放地址法或者拉链法解决冲突。HashMap 中采用的是拉链法解决冲突
上图是 jdk1.8 才使用的红黑树结构,当碰撞的节点个数到达 8 时,将链表转换为 红黑树结构。同时删除节点小于6时将转换为链表结构
HashMap 扩容:当数组中存储的容量到达总容量*加载因子(0.75)时,将对数组扩容为原来的 2 倍
4. HashMap 的容量为何是 2的幂次方倍
这个是因为计算机的特性决定的,计算机在取余操作是非常慢的,但是按位 & 操作非常快,在计算索引 index = h%length = h &(length - 1),需要的前提条件是 是2的幂次方。 这样加快下标计算
5.HashMap 扩容是如何被触发的
当HashMap 的 size > loadFactory*capacity 即发生扩容,size 是数组节点与链表节点的总和。扩容是一个非常耗费性能的操作,如果数组的长度发生变化,那么所有的节点的索引都需要重新计算。
jdk 1.8 将此部分进行了优化,在扩容后链表中的元素存储是到过来的,因为是头插的方式。在多线程的情况下要使用 ConcurrentHashMap 容器
6. HashMap jdk1.7 与 jdk1.8 扩容的区别
HashMap 为了减少碰撞最好是将元素均匀的分布在每个数组节点,下标的计算直接决定 HashMap 的离散性能
Hash 算法三部: 取key 的 HashCode 、高位参与运算、取模操作
这里在第二步:jdk1.8 进行了优化,将hashCode() 的高16位与低16位异或,将更均匀的分布在数组上,同时保证操作不会有较大的开销
第三步优化:此处取余运算较慢,优化位按位 & 。前提条件就是数组长度length 必须是2的幂次方
在扩容后插入节点,jdk1.8 中的下标是 index 或者 index+old_length上,好处是不需要重新计算,效率更高
jdk1.7 使用头插在多线程的条件中,或形成一个环,jdk1.8 没有采用头插的方式,不会出现次情况。如下是jdk1.8 插入
7.ConcurrentHashMap 使用什么保证线程安全
ConcurrentHashMap 底层数据结构与HashMap 相同,是多线程安全的
jdk1.7 采用的是Segment +HashEntity 来进行实现的,jdk1.8 放弃了segment,采用 Node + CAS + synchronized 保证线程安全
8.ConcurrentHashMap jdk1.7 与1.8 的区别
jdk1.7 中 ComcurrentHashMap 是Segment 数组与 HashEntity 组成,Segment 将一个大的tabele 分成多个小的table 加锁
每个Segment 大小默认是 16
jdk1.8 降低了锁的粒度,jdk 1.7的锁的粒度是 Segment ,内部是HashEntity ,jdk1.8 锁的粒度就是HashEntity
jdk1.8 中ConcurrentHashMap 因为才有了数据+链表+红黑树的结构,其容量也必须是2的幂次方。默认初始容量 16
9.遍历Map几种方式
方式一:遍历Map
Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); }
方式二:遍历key 或者value
Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //遍历map中的键 for (Integer key : map.keySet()) { System.out.println("Key = " + key); } //遍历map中的值 for (Integer value : map.values()) { System.out.println("Value = " + value); }
注意:HashMap 允许key 一个null , HashTable key-value 均不能为null,ConcurrentHashMap key-value 也均不为null
10.ArrayList 添加与删除元素的方法? 如何对ArrayList 进行排序
ArrayList 添加的方法 add() ,删除元素的方法 remove()
集合有排序的方法 sort(Collector<T super E> c) ,需要传入一个比较器,重写compare()方法实现排序