容器 -- 常见面试题

在这里插入图片描述
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<>(){…});
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值