1. list集合的哪一种遍历方式要快一些
下面时间是自己测试所得,不同情况时间不一致,但相对大小关系应该不变
- ArrayList
普通for循环:3ms 迭代器:6ms - LinkedList
普通for循环:6295ms 迭代器:28ms
原因:接口RandomAccess中内容是空的,只是作为标记用。ArrayList实现了该接口,可随机访问,而LinkedList 没有。利用instanceof 来判断哪个实现了RandomAccess(通过语句 list instanceof RandomAccess)。
2. List,Set,Map三者的区别?
List:
- 可以允许重复的对象。
- 是一个有序容器,保持了每个元素的插入顺序。
- 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了
使用索引的随意访问,而 LinkedList 则用于添加或删除元素场合。
Set:
- 不允许重复对象
- 无序容器,元素在集合中的位置是由元素的hashcode决定,即位置是固定的,但这个位置用户不可控制,所以对于用户来说其元素还是无序的)。
- 常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口, TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
Map:
- Map不是collection的子接口或者实现类。Map是一个接口,双列数据的集合。
- Map 的 每个 Entry 都持有两个对象,可能会持有相同的值对象但键对象唯一。
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
3. Arraylist 与 LinkedList 区别?
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,都不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构。
- 初始化容量: ArrayList 可通过构造器初始化容量。LinkedList 容量随元素个数的变化而动态变化。
- 是否支持快速随机访问: LinkedList 不支持随机元素访问,而 ArrayList 支持。
- 内存空间占用: ArrayList的空间浪费主要在list列表的结尾会预留一定的容量空间,而 LinkedList的空间花费在它的每一个元素都消耗更多的空间(因为要存放直接后继和直接前驱以及数据)。
4. Vector与 ArrayList 的比较
-
Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
-
Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。
Vetor 的 grow()扩容函数下:
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
5. HashMap 和 Hashtable 的区别
-
线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过synchronized 修饰。
-
效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。
-
key能否为null: HashMap可以,但这样的键只能有一个,HashTable则不能。
-
初始容量大小和每次扩充容量大小的不同 :
①不指定容量初始值,Hashtable 默认大小为11,每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。每次扩充,容量变为原来的2倍。
②指定初始值, Hashtable 直接用给定值,而 HashMap 会将其扩充为2的幂次方。 -
底层数据结构: JDK1.8 以后的 HashMap当链表长度大于阈值(默认为8)时,将链表转化为红黑树。Hashtable 没有这样的机制。
6. 解决哈希冲突的三种方法
-
拉链法:hashMap:查找时间复杂度0(1)
-
开放地址法:插入元素时,先通过哈希函数判断,若冲突,就以当前地址为基准,根据探查序列去寻找下一个地址。
常用的探查序列的方法:
1). 线性探查:冲突发生时,顺序查看表中下一个单元,知道找出一个空单元或擦边全表
eg. ThreadLocal内的ThreadLocalMap
2). 再平方探查:冲突发生时,在原哈希值基础上先加1 * 1, 若仍存在则减1 * 1,随之是2 * 2,3 * 3 ,直至不发生冲突。
3). 伪随机探测:冲突发生时,在原来基础上+ 随机生成一个数 -
再哈希法:对有冲突的哈希值再次进行哈希处理。
7. HashMap 和 HashSet区别
HashSet 底层就是基于 HashMap 实现的, 其value有个统一的值
private static final Object PRESENT = new Object();
定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。因为set.remove()是使用 HashMap 实现,而map.remove()会返回value,如果value是null,HashSet无法判断是否移除成功。
除了 clone()、writeObject()、readObject()自己实现,其他都是调用 HashMap 中的方法。
8. HashMap中key 为 null 时,存放在哪里?
null作为key时被放在了table下标为0的位置.
key == null 时,通过hash函数返回0,第一次处理key时,是在put 函数的 if 语句中.
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
因hash为0, 上式为if ((p = tab[i = 0]) == null) --> tab[0] = newNode(hash, key, value, null);
9. HashMap,LinkedHashMap,TreeMap 有什么区别及其各自应用场景?
LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;
TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)
应用场景:
- 一般情况下,使用最多的是 HashMap。
- HashMap:在 Map 中插入、删除和定位元素时;
- TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;
- LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下。
10. HashMap 的长度为什么是2的幂次方
为了能让 HashMap 存取高效,常经过hash定位,但hash范围很大,所以在使用之前,先做对数组的长度取模运算,首先想到采用%取余的操作来实现。但是,取余(%)操作中,如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash % len == (len - 1) & hash 的前提是 len 是2的n次方)。采用位操作 &,可大大提高运算效率。
11. HashMap 多线程操作导致死循环问题:JDK1.8之前的情况(采用的是头插法)
JDK1.7 扩容关键代码
for(Entry<K, V> e: table){
while(e != null){
Entry<K ,V> next = e.next;
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
并发下的reHash过程:假设有两个线程。分别用红色和浅蓝色标注。
1)当线程一执行到扩容方法tranfer()时挂起,而此时线程二执行完成,则:
2)线程一被调度回来执行,先是执行 newTalbe[i] = e; 然后是e = next,导致了e指向了key(7),而下一次循环的next = e.next导致了next指向了key(3),线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。
![]() | ![]() |
---|
3)环形链接出现:e.next = newTable[i] 导致 key(3).next 指向了 key(7)
注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。
12. ArrayList中扩容 区分 System.arraycopy( ), Arrays.copyOf();
Arrays.copyOf的构造方法:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
可知copyOf()内部利用arrayCopy()实现,首先在内部创建了一个新的数组,然后调用arrayCopy()向其复制内容,返回copy。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
13. Arrays.asList()避坑指南
//返回由指定数组支持的固定大小的列表,只可用来遍历。
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
注1: 该方法将数组与List列表连接起来,当更新其中一个时,另一个也自动更新。
注2: 传递的数组必须是对象数组,不能是基本类型,当传递的是基本类型时,输出的是数组对象的引用
int[] myArray = { 1, 2, 3 };
List myList = Arrays.asList(myArray);
System.out.println(myList.size()); //1
System.out.println(myList.get(0)); //数组地址值
System.out.println(myList.get(1)); //报错:ArrayIndexOutOfBoundsException
int[] array=(int[]) myList.get(0);
System.out.println(array[0]); //1
注3: Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类, 得到的集合长度是不可变的,所以这个内部类不支持add()、remove()、clear()方法。
List myList = Arrays.asList(1, 2, 3);
myList.add(4); //运行时报错:UnsupportedOperationException
System.out.println(myList.getClass()); //class java.util.Arrays$ArrayList
14.comparable 和 Comparator的区别
- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
- comparator接口是 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
- Comparable 是在集合内部实现的排序,若实现该接口,可通过Collections.sort(list)排序
- Comparator 是在集合外部实现的排序,Collections.sort(list, new Comparator<>(){…});