List,Set,Map 三者的区别?
- List: 存储的元素是有序的、可重复的。
- Set: 存储的元素是无序的、不可重复的。
- Map: 使用键值对(kye-value)存储,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
List
- Arraylist: Object[]数组,线程不安全 ,支持快速随机访问,插入和删除受元素位置的影响
- Vector:Object[]数组,线程安全
- LinkedList: 双向链表,线程不安全 ,插入和删除不受元素位置的影响
双向链表: 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点
ArrayList和LinkedList的区别?
- ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
- ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。
- LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
ArrayList为什么扩容为原来的1.5倍
- 添加元素时先用ensureCapacityInternal方法“确保内部容量”,内部容量足够则加入元素,不够则扩容。扩容是用>>位运算,右移动一位。 整体相当于newCapacity =oldCapacity + 0.5 * oldCapacity 。既为原来的1.5倍。再通过复制Arrays.copyOf(elementData, newCapacity),将元素复制过去。
Set
- HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素,线程不安全,可以存储 null 值
- LinkedHashSet:是 HashSet 的子类,其内部是通过 LinkedHashMap 来实现的,能够按照添加元素的顺序进行遍历
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树),能够按照添加元素的顺序进行遍历
Map
-
HashMap底层就是一个数组结构,数组中的每一项又是一个链表。数组+链表结构,新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个Entry其实就是一个key-value的键值对,它持有一个指向下一个元素的引用,这就构成了链表,HashMap底层将key-value当成一个整体来处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry【】数组来保存所有的key-value键值对,当需要存储一个Entry对象时,会根据hash算法来决定在其数组中的位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry对象时,也会根据hash算法找到其在数组中的存储位置, 在根据equals方法从该位置上的链表中取出Entry。
-
HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。默认的初始化大小为 16,之后每次扩充,容量变为原来的 2 倍。非线程安全。可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个。
-
LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
-
Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的,线程安全因为内部的方法基本都经过synchronized 修饰(基本被淘汰)
-
TreeMap: 红黑树(自平衡的排序二叉树)
HashMap的加载因子为什么是0.75
- 加载因子是表示Hsah表中元素的填满的程度。HashMap使用了拉链法处理哈希冲突,底层数据结构是数组和链表。在Hashmap源码注释里,可以看到使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。
HashMap的大小为2的次幂
- HashMap默认的初始化大小为 16(自定义需要为2的幂次方,否则HashMap会采用第一个大于该数值的2的幂作为初始化容量),这样hash 值发生碰撞的概率比较小,因为HashMap获取数组索引的计算方式为 key 的 hash 值按位与运算数组长度减一,长度为 2 的幂时减一的值的二进制位数一定全为 1,这样数组下标 index 的值完全取决于 key 的 hash 值的后几位,因此只要存入 HashMap 的 Entry 的 key 的 hashCode 值分布均匀,HashMap 中数组 Entry 元素的分部也就尽可能是均匀的。
- 之后每次扩充,容量变为原来的 2 倍,用<<位运算。
- JDK1.7之前头插法
HashMap 和 TreeMap 区别
- TreeMap 和HashMap 都继承自AbstractMap
- TreeMap还实现了NavigableMap接口和SortedMap 接口
- 实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力
- 实现SortMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,也可以指定排序的比较器
ConcurrentHashMap 和 Hashtable 的区别
- 底层数据结构: ConcurrentHashMap 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树
- 实现线程安全的方式:ConcurrentHashMap 直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作(只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发),不会存在锁竞争;Hashtable(同一把锁)使用 synchronized 来保证线程安全,存在锁竞争,效率非常低下
HashMap跟ConcurrenHashMap,机制,如何保证并发的
- HashMap在多线程的环境下put的时候,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
- JDK1.7版本的ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,主要实现原理是实现了锁分离的思路解决了多线程的安全问题,put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Segment里面的Entry,然后在遍历entry链表。
- JDK1.8版本的ConcurrentHashMap摒弃了Segment的概念,直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,Node是存储结构的基本单元,继承于HashMap中的Entry,用于存储数据,Node是一个链表,但是只允许对数据进行查找,不允许进行修改;当链表的节点数大于8时会转换成红黑树的结构,通过TreeNode作为存储结构代替Node来转换成黑红树,
如何选用集合?
- 主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用 Map 接口下的集合,需要排序时选择 TreeMap,不需要排序时就选择 HashMap,需要保证线程安全就选用 ConcurrentHashMap。
- 当我们只需要存放元素值时,就选择实现Collection 接口的集合,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet,不需要就选择实现 List 接口的比如 ArrayList 或 LinkedList,然后再根据实现这些接口的集合的特点来选用。
comparable 和 Comparator 的区别
- comparable 接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
- 对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方法
- 例:重写 compareTo 方法实现按年龄来排序
// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
// 像Integer类、String类等都已经实现了Comparable接口,所以不需要另外实现了
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//T重写compareTo方法实现按年龄来排序
@Override
public int compareTo(Person o) {
if (this.age > o.getAge()) {
return 1;
}
if (this.age < o.getAge()) {
return -1;
}
return 0;
}
}
public static void main(String[] args) {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("小红", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
输出:
5-小红
10-王五
20-李四
30-张三
Collections 工具类方法
-
排序操作
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面 -
查找,替换操作
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
快速失败和安全失败
- 相对于迭代器而言
- 快速失败
在用迭代器遍历一个集合对象时,如果遍历过程中进行了修改,则会抛出Concurrent Modification Exception
java.util包下的集合类都是快速失败,不能在多线程下发生并发修改 - 安全失败
在拷贝的集合上进行遍历,对原集合所做的修改不能被检测到,不会触发Concurrent Modification Exception
安全失败只意味着不会抛出异常,不是就是正确的
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发修改
数组(Array)和列表(ArrayList)有什么区别?
- 存储内容
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。 - 空间大小
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的。 - 方法上
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
什么时候应该使用 Array 而不是 ArrayList?
- 如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
- 如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。