面试准备-打卡第十三天-Java篇

174 阅读4分钟

常见的集合有哪些?

Java的集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue,因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。

注意:Collection是一个接口,Collections是一个工具类,Map不是Collection的子接口

image.png

image.png

  • List代表了有序可重复集,可直接根据元素的索引来访问;
  • Set代表了无序不可重复集合,只能根据元素本身来访问;
  • Queue是队列集合;
  • Map代表的是存储key-value对的集合,可根据元素的key来访问value;

它们的实现类分别有:ArrayList、LinkedList、ArrayQueue、HashSet、TreeSet、HashMap、TreeMap等实现类

线程安全的集合有哪些?线程不安全的呢?

线程安全的:

  • Hashtable:比HashMap多了个线程安全
  • ConcurrentHashMap:是一种高效但是线程安全的集合
  • Vector:比ArrayList多了个同步化机制
  • Stack:栈,也是线程安全的,继承于Vector

线程不安全的:

  • HashMap
  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • TreeMap

ArrayList和LinkedList的异同点?

相同点:

  • ArrayList和LinkedList都是不同步的,也就是不保证线程安全

不同点:

  • ArrayList的底层数据结构是数组,支持下标访问,查询数据快,默认初始值大小为10,容量不足时会进行扩容
  • LinkedList的底层数据结构是链表,将元素添加到链表的末尾,无需扩容

ArrayList和LinkedList分别适用于哪些场景?

对于随机index访问的get和set方法,ArrayList的速度要优先于LinkedList,因为ArrayList直接通过数组下标找到元素;LinkedList要移动指针遍历每个元素直到找到为止

新增和删除元素,LinkedList的速度要优于ArrayList,因为ArrayList在新增和删除元素时,可能扩容和复制数组,而LinkedList的新增和删除操作只需要修改指针即可。

因此,ArrayList适用于查询多,增删少的场景,而LinkedList适用于查询少,增删多的场景

ArrayList和Vector的区别?

  • Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。

说一下ArrayList的扩容机制?

ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。然后把ArrayList的地址指向新数组,默认情况下,新的容量会是原容量的1.5倍

以JDK1.8为例

public boolean add(E e) {
    //判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将e添加到数组末尾
    elementData[size++] = e;
    return true;
    }

// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部

private void ensureCapacityInternal(int minCapacity) {
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


private void grow(int minCapacity) {
        // 获取elementData数组的内存空间长度
        int oldCapacity = elementData.length;
        // 扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //校验容量是否够
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //若预设值大于默认的最大值,检查是否溢出
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 调用Arrays.copyOf方法将elementData数组指向新的内存空间
         //并将elementData的数据复制到新的内存空间
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Array和ArrayList有什么区别?

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型
  • Array大小是固定的,ArrayList的大小是动态变化的
  • ArrayList提供了更多的方法和特性,比如:add(),reoveall(),iterator()等等

如何返回一个线程安全的List?

可以用Collections.synchronizedList()返回一个线程安全的List 或者用CopyOnWriteArrayList