【年后找工作】Java八股必备知识 -- 集合篇

1、常见集合类

集合相关类和接口都在java.util中,主要分为3种:List(列表)、Map(映射)、Set(集)。
在这里插入图片描述
在Java中,集合相关的类和接口主要分为三种:List(列表)、Set(集)、Map(映射)。它们都位于java.util包下。

List:列表是一个有序的集合,可以包含重复元素。常见的List实现类有ArrayList、LinkedList和Vector。

  • ArrayList:基于动态数组实现的列表,支持随机访问和快速增删元素。
  • LinkedList:基于双向链表实现的列表,适合频繁插入、删除操作。
  • Vector:与ArrayList类似,但是是线程安全的,支持同步访问。

Set:集是一个不允许重复元素的无序集合。常见的Set实现类有HashSet、TreeSet和LinkedHashSet。

  • HashSet:基于哈希表实现的集,具有快速查找的特点。
  • TreeSet:基于红黑树实现的有序集合,可以按照自然顺序或者自定义比较器排序。
  • LinkedHashSet:基于哈希表和双向链表实现的有序集合,保持插入顺序或访问顺序。

Map:映射是一种键值对的集合,每个键都唯一,用于存储和检索键值对。常见的Map实现类有HashMap、TreeMap和LinkedHashMap。

  • HashMap:基于哈希表实现的映射,键和值都可以为null。
  • TreeMap:基于红黑树实现的有序映射,可以按照键的自然顺序或者自定义比较器排序。
  • LinkedHashMap:基于哈希表和双向链表实现的有序映射,保持插入顺序或访问顺序。

2、ArrayList和LinkedList有什么区别?

ArrayList 和 LinkedList 是 Java 中常用的两种 List 集合实现,它们在内部数据结构和性能特点上有一些区别:

  1. 内部数据结构:

    • ArrayList:基于动态数组实现,通过数组存储元素,支持随机访问(根据索引快速访问元素)。在插入或删除元素时,需要移动后续元素,所以插入和删除操作效率较低。

    • LinkedList:基于双向链表实现,每个节点都包含对前一个和后一个节点的引用。由于是链表结构,插入和删除元素时只需改变相邻节点的引用,效率比 ArrayList 高。但对于随机访问,需要从头或尾开始遍历链表,效率较低。

  2. 随机访问效率:

    • ArrayList 支持通过索引进行快速随机访问,时间复杂度为 O(1)。
    • LinkedList 不支持直接通过索引进行快速访问,需要从头或尾依次遍历链表,时间复杂度为 O(n)。
  3. 插入和删除操作效率:

    • ArrayList 在中间插入或删除元素时,需要移动后续元素,时间复杂度为 O(n)。
    • LinkedList 在中间插入或删除元素时,只需改变相邻节点的引用,时间复杂度为 O(1)。

综上所述,
如果需要频繁插入、删除元素且不需要频繁随机访问元素,可以选择使用 LinkedList;
如果需要频繁随机访问元素,可以选择使用 ArrayList。

3、你能说出几种集合的排序方式?

在 Java 中,有多种方式可以对集合进行排序。下面是几种常见的排序方式:

  • 实现Comparable

通过实现 Comparable 接口的 compareTo() 方法,使集合中的元素具备可比较性,然后使用 Collections.sort() 或 Arrays.sort() 方法进行排序。例如,对于整数列表排序,可以直接使用 Collections.sort(list)

//实体类自己实现Comparable接口比较
public class Student implements Comparable<Student>{ 
    private String name; 
    private int age; 
    @Override 
    public int compareTo(Student o) {
        int flag = this.name.compareTo(o.name); 
        if(flag == 0) { 
        	flag = this.age - o.age; 
        } 
        return flag; 
    } 
}
Collections.sort(students);



//整数列表排序,可以直接使用 Collections.sort(list)。
public class NaturalOrderingExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        // 使用 Collections.sort() 进行自然排序
        Collections.sort(numbers);
        
        System.out.println(numbers);  // 输出:[1, 2, 5, 8]
    }
}
  • 借助Comparator
    第二种:借助比较器进行排序。
public class Student { 
    private String name; 
	private int age; 
}
Collections.sort(students, (o1, o2) -> {
	int flag = o1.getName().compareTo(o2.getName()); 
    if(flag == 0) { 
        flag = o1.getAge() - o2.getAge(); 
    } 
    return flag; 
}); 
  • 通过Stream

借助Stream进行排序,借助Stream的API,底层还是通过Comparable实现的。

public class Student { 
    private String name; 
	private int age; 
}
// 如果Student实现了Comparable
students.stream().sorted().collect(Collectors.toList());
// 如果Student没有实现Comparable

students.stream().sorted((o1, o2) -> {
	int flag = o1.getName().compareTo(o2.getName()); 
    if(flag == 0) { 
        flag = o1.getAge() - o2.getAge(); 
    } 
    return flag; 
}).collect(Collectors.toList());
  • TreeSet
    TreeSet 和 TreeMap 是基于红黑树实现的有序集合和映射。它们会根据元素的自然顺序或自定义的比较器进行排序。只需将元素添加到 TreeSet 或 TreeMap 中,它们会自动按照排序顺序进行存储。
import java.util.Set;
import java.util.TreeSet;

public class TreeSetSortingExample {
    public static void main(String[] args) {
        Set<Integer> numbers = new TreeSet<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        System.out.println(numbers);  // 输出:[1, 2, 5, 8]
    }
}

4、ArrayList的扩容机制了解吗?

ArrayList是基于数组的集合,数组的容量是在定义的时候确定的,如果数组满了,再插入,就会数组溢出。所以在插入时候,会先检查是否需要扩容,如果当前容量+1超过数组长度,就会进行扩容。

ArrayList的扩容是创建一个1.5倍的新数组,然后把原数组的值拷贝过去。
在这里插入图片描述

5、有了Comparable为什么还需要Comparator?

Comparable用于使某个类具备可排序能力。如之前的Student类,实现该接口后覆盖其compareTo方法,即可具备可排序的能力。
但是仍然存在一些二方库的类没有实现Comparable,但是调用方也需要比较的,此时就需要使用Comparator接口。
Comparator是一个比较器接口,可以用来给不具备排序能力的对象进行排序。如上述代码中对不具备排序能力的Student进行排序

6、快速失败(fail-fast)和安全失败(fail-safe)了解吗?

快速失败(fail—fast):快速失败是Java集合的一种错误检测机制
在系统设计中,快速失效(fail-fast)系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。
其实,这是一种理念,说白了就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报。

public int divide(int dividend,int divisor){
    if(divisor == 0){
        throw new RuntimeException("divisor can't be zero");
    }
    return dividend/divisor;
}

上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。

这样做的好处就是可以预先识别出一些错误情况,一方面可以避免执行复杂的其他代码,另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。

在Java中,集合类中有用到fail-fast机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。

在集合类中,为了避免并发修改,会维护一个expectedModCount属性,他表示这个迭代器预期该集合被修改的次数。还有一个modCount属性,他表示该集合实际被修改的次数。在集合被修改时,会去比较modCount和expectedModCount的值,如果不一致,则会触发fail-fast机制,抛出ConcurrentModificationException。

安全失败(fail—safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改,比如CopyOnWriteArrayList类。

fail-safe 机制是为线程安全的集合准备的,可以避免像 fail-fast 一样在并发使用集合的时候,不断地抛出异常

7、什么是Copy-On-Write

在了解了CopyOnWriteArrayList之后,不知道大家会不会有这样的疑问:他的add/remove等方法都已经加锁了,还要copy一份再修改干嘛?多此一举?同样是线程安全的集合,这玩意和Vector有啥区别呢?
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为了避免Copy出N个副本出来,导致并发写。
但是,CopyOnWriteArrayList中的读方法是没有加锁的。
在这里插入图片描述

//没有加锁
public E get(int index) {
    return get(getArray(), index);
}

//add() 方法的源码
public boolean add(E element) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 获取锁
    try {
        Object[] currentArray = getArray(); // 获取当前数组
        int currentSize = currentArray.length;

        Object[] newArray = Arrays.copyOf(currentArray, currentSize + 1); // 创建新数组,长度比原数组多1
        newArray[currentSize] = element; // 将元素添加到新数组的最后

        setArray(newArray); // 将新数组设置为当前数组

        return true;
    } finally {
        lock.unlock(); // 释放锁
    }
}


//remove() 方法的源码:
public boolean remove(Object element) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 获取锁
    try {
        Object[] currentArray = getArray(); // 获取当前数组

        int index = indexOf(element, currentArray); // 查找元素在数组中的索引
        if (index < 0) {
            return false; // 如果元素不存在,直接返回 false
        }

        int newSize = currentArray.length - 1;
        Object[] newArray = new Object[newSize]; // 创建新数组,长度比原数组少1

        System.arraycopy(currentArray, 0, newArray, 0, index); // 复制原数组中索引之前的元素
        System.arraycopy(currentArray, index + 1, newArray, index, newSize - index); // 复制原数组中索引之后的元素

        setArray(newArray); // 将新数组设置为当前数组

        return true;
    } finally {
        lock.unlock(); // 释放锁
    }
}

总结:
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,当然,这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的,并非强一致性。
所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器。而Vector在读写的时候使用同一个容器,读写互斥,同时只能做一件事儿。

8、有哪几种实现ArrayList线程安全的方法?

fail-fast是一种可能触发的机制,实际上,ArrayList的线程安全仍然没有保证,一般,保证ArrayList的线程安全可以通过这些方案:

  1. 使用 Collections 工具类的 synchronizedList() 方法:可以通过 Collections.synchronizedList(new ArrayList<>()) 方法来获取一个线程安全的 ArrayList。它会返回一个线程安全的包装器,对所有访问集合的操作都进行了同步处理。例如:

    List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
    

    这种方式适用于在多线程环境下对 ArrayList 进行读写操作。

  2. 使用 CopyOnWriteArrayList 类:CopyOnWriteArrayList 是 Java 并发包中提供的线程安全的集合类,它通过复制整个数组来实现并发安全性。它适用于读多写少的场景。例如:

    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    

    使用 CopyOnWriteArrayList 可以在多线程环境下同时进行并发读取和修改操作,而无需额外的同步措施。

  3. 使用 Lock 锁机制:可以使用显示的 Lock 锁机制来保证线程安全。通过在访问 ArrayList 的代码块中加锁和解锁,确保同一时刻只有一个线程可以访问 ArrayList。例如:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreadSafeArrayList {
        private List<String> list = new ArrayList<>();
        private Lock lock = new ReentrantLock();
    
        public void addItem(String item) {
            lock.lock();
            try {
                list.add(item);
            } finally {
                lock.unlock();
            }
        }
    
        // 其他操作方法类似...
    }
    

    在这种方式中,需要手动加锁和解锁,确保每个访问 ArrayList 的代码段只有一个线程可以执行。

总的来说,根据具体的需求和场景选择合适的线程安全方式。如果只是读取多写入少,建议使用 CopyOnWriteArrayList;如果需要对整个 ArrayList 进行同步,可以使用 Collections 工具类的 synchronizedList() 方法;如果需要更细粒度的控制,可以使用 Lock 锁机制。

9、遍历的同时修改一个List有几种方式?

我们知道,在foreach的同时修改集合,会触发fail-fast机制,要避免fail-fast机制,有如下处理方案:
在遍历的同时修改一个 List 是一个常见的并发操作,如果不采取适当的措施可能会导致 ConcurrentModificationException 异常或者出现意外的结果。以下是几种常见的方式可以在遍历的同时修改一个 List:

  1. 使用 Iterator 的 remove() 方法:可以使用 Iterator 的 remove() 方法来删除元素,这种方式是安全的。在使用 Iterator 迭代器进行遍历时,可以调用 Iterator 的 remove() 方法来删除元素,而不会抛出 ConcurrentModificationException 异常。示例代码如下:
List<String> list = new ArrayList<>();
// 假设 list 中已经有元素

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    if (condition) {
        iterator.remove(); // 安全地删除元素
    }
}
  1. 使用 ListIterator 的 remove() 方法:类似于 Iterator,ListIterator 也提供了 remove() 方法来删除元素,可以在遍历的同时安全地删除元素。示例代码如下:
List<String> list = new ArrayList<>();
// 假设 list 中已经有元素

ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
    String element = listIterator.next();
    if (condition) {
        listIterator.remove(); // 安全地删除元素
    }
}
  1. 使用 CopyOnWriteArrayList:CopyOnWriteArrayList 是一个线程安全的 List 实现,在遍历的同时进行修改是安全的。因为 CopyOnWriteArrayList 在修改时会复制一个新数组,原数组不会被修改,所以遍历和修改不会相互影响。示例代码如下:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 假设 list 中已经有元素

for (String element : list) {
    if (condition) {
        list.remove(element); // 安全地删除元素
    }
}
  1. 使用并发控制手段:通过使用锁(例如 ReentrantLock)、同步块或者其他并发控制手段来保证在遍历时修改 List 的线程安全性。确保在修改操作时适当加锁,以防止多个线程同时修改 List 导致数据不一致。这种方式需要谨慎处理,以避免死锁等问题。
  2. 通过Stream的过滤方法,因为Stream每次处理后都会生成一个新的Stream,不存在并发问题,所以Stream的filter也可以修改list集合。
public List<String> streamRemove() { 
    List<String> students = this.getStudents();
    return students.stream()
        .filter(this::notNeedDel)
        .collect(Collectors.toList());
}

6 . 通过removeIf方法,实现元素的过滤删除。removeIf() 方法用于删除所有满足特定条件的数组元素

arraylist.removeIf(this::needDel);

总的来说,在遍历的同时修改一个 List,推荐使用 Iterator 或 ListIterator 提供的安全删除方法,或者使用 CopyOnWriteArrayList 进行并发安全的遍历和修改。

10、Set是如何保证元素不重复的

在Java的Set体系中,主要有两种实现类:HashSet和TreeSet,它们都通过不同的方式来保证元素的不重复性。

  1. HashSet

    • HashSet是基于哈希表实现的,其中的数据是无序的。在向HashSet中添加元素时,首先计算元素的hashCode值,然后根据该值找到元素在内存中的位置。如果这个位置没有元素,就直接添加进去;如果已经存在元素,则会通过equals方法判断是否相等,如果相等则不添加,否则找到一个空位添加。
    • HashSet允许放入null值,但只能放入一个null。HashSet中的元素不能重复,类似于数据库中的唯一约束。
  2. TreeSet

    • TreeSet是基于红黑树实现的,其中的数据是自动排好序的。树结构使得TreeSet中的元素按照自然顺序排序或者根据提供的Comparator进行排序。
    • TreeSet不允许放入null值。在插入元素时,会通过compareTo()方法来判断元素的大小关系,从而保证元素的唯一性。如果两个元素通过compareTo()判断相等,则新元素不会被插入。

综上所述,HashSet通过哈希表实现,依靠hashCode和equals方法来保证元素的不重复性;而TreeSet通过红黑树实现,依靠compareTo方法来保证元素的唯一性。这两种Set实现类都能有效地确保集合中元素的唯一性。

11、ArrayList、LinkedList与Vector的区别?

List主要有ArrayList、LinkedList与Vector几种实现。这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。
下面是ArrayList、LinkedList和Vector之间的区别总结:

特性ArrayListLinkedListVector
数据结构动态数组双向链表动态数组
访问速度get和set操作较快get和set操作较慢get和set操作较快
添加/删除添加/删除元素较慢添加/删除元素较快添加/删除元素较慢
线程安全性非线程安全非线程安全线程安全
扩容策略当容量不足时,增长50%每次请求当前大小的双倍空间每次请求当前大小的双倍空间
接口实现ListList, Queue, DequeList

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组。

LinkedList 是一个双向链表,在添加和删除元素时具有比ArrayList更好的性能,但在get与set方面弱于ArrayList。当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义。

Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%。

而 LinkedList 还实现了Queue和Deque接口,该接口比List提供了更多的方法,包括offer(),peek(),poll()等。

注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

11、ArrayList的序列化是怎么实现的?

ArrayList 的序列化是通过实现 Serializable 接口并重写 writeObject 和 readObject 方法来实现的。

在默认的序列化机制中,ArrayList 内部的元素数组被声明为 transient,表示在序列化过程中会被忽略。因此,ArrayList 在序列化时需要自定义 writeObject 和 readObject 方法来实现元素的序列化和反序列化。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

12、为什么底层数组要使用transient

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

所以,为了避免Java自带的序列化机制造成的空间浪费,把数组定义为transient,然后重写writeObject和readObject来实现序列化操作。

13、hash冲突通常怎么解决?

当涉及到解决哈希冲突时,通常会采用以下常见的方法:

  1. 开放定址法

    • 开放定址法是一种处理冲突的方法,当发生冲突时,它会寻找下一个空的散列地址,并将记录存入。常见的开放寻址技术包括线性探测、二次探测和双重散列。然而,这种方法可能导致"聚集"问题,从而降低哈希表的性能。
  2. 链地址法

    • 链地址法是最常用的解决哈希冲突的方法之一。在链地址法中,每个哈希桶指向一个链表,当发生冲突时,新的元素将被添加到这个链表的末尾。在 Java 中,HashMap 就是通过这种方式来解决哈希冲突的。在 Java 8 之前,HashMap 使用链表来实现;而从 Java 8 开始,当链表长度超过一定阈值时,链表会转换为红黑树,以提高搜索效率。
  3. 再哈希法

    • 当哈希地址发生冲突时,再哈希法会使用其他的函数计算另一个哈希函数地址,直到冲突不再产生为止。这种方法需要额外的计算,但可以有效降低冲突率。
  4. 建立公共溢出区

    • 在建立公共溢出区的方法中,哈希表会被分为基本表和溢出表两部分,发生冲突的元素都会被放入溢出表中。
  5. 一致性哈希

    • 一致性哈希主要用于分布式系统中,如分布式缓存。它通过将数据均匀分布到多个节点上来减少冲突。

14、HashMap的数据结构是怎样的?

HashMap 是 Java 中最常用的 Map 实现,它采用了哈希表的数据结构来存储和管理键值对。在 HashMap 中,每个键都会通过哈希函数映射到一个桶(bucket)中,而这个桶实际上就是一个链表或红黑树,每次添加或查找元素时,都需要先根据键值计算出哈希值,然后再根据哈希值找到对应的桶,最后在桶中查找或添加元素。以下是 HashMap 的主要数据结构:
在这里插入图片描述

  1. Node 数组

    • 在 HashMap 内部,实际上是通过一个 Node 数组来存储键值对的。每个 Node 对象包含了 key、value 和 next 三个属性,其中 key 和 value 分别表示键和值,而 next 则是一个指向下一个 Node 对象的引用,这个引用用来实现链表或红黑树。
  2. 链表或红黑树

    • 每个桶实际上就是一个链表或红黑树,用来存储哈希值相同的键值对。当桶中的元素较少时,使用链表来存储;当桶中的元素较多时,使用红黑树来存储以提高查找效率。
  3. 负载因子

    • HashMap 中的负载因子用来衡量哈希表的使用情况,它等于哈希表中的元素个数除以桶的数量。当负载因子大于一定阈值时,就需要对哈希表进行扩容操作,以避免链表或红黑树过长导致查找效率降低。
  4. 容量

    • HashMap 中的容量指的是哈希表中桶的数量,它是在创建哈希表时指定的,默认值为 16。

总之,HashMap 采用了哈希表的数据结构来实现键值对的存储和查找,并且通过链表或红黑树来解决哈希冲突的问题,以提高查找效率。

15、HashMap、Hashtable和ConcurrentHashMap的区别?

特性HashMapHashtableConcurrentHashMap
线程安全非线程安全线程安全线程安全(通过分段锁实现)
继承关系继承 AbstractMap 类继承自 Dictionary 类继承 AbstractMap 类,实现 ConcurrentMap 接口
允许 null 值key 和 value 均可为 nullkey 和 value 均不可为 nullkey 和 value 均不可为 null
初始容量和扩容机制默认初始容量为 16,加载因子为 0.75默认初始容量为 11,加载因子为 0.75默认初始容量为 16,加载因子为 0.75

HashMap、Hashtable 和 ConcurrentHashMap 是 Java 中常用的 Map 实现,它们之间有一些明显的区别:

  1. 线程安全性:

    • HashMap 是非线程安全的。
    • Hashtable 是线程安全的,其方法都是同步的。
    • ConcurrentHashMap 在 JDK 1.8 之前使用分段锁实现线程安全,JDK 1.8 引入了新的实现方式,使用 CAS+synchronized,即锁分离,提高了并发性能。
  2. 继承关系:

    • Hashtable 继承自 Dictionary 类,是较早期的类。
    • HashMap 继承自 AbstractMap 类,实现了 Map 接口。
    • ConcurrentHashMap 继承自 AbstractMap 类,并实现了 ConcurrentMap 接口。
  3. null 值的处理:

    • Hashtable 不允许 key 和 value 为 null。
    • HashMap 中可以有 null 的 key 和 value。
    • ConcurrentHashMap 也不允许 key 和 value 为 null。
  4. 默认初始容量和扩容机制:

    • HashMap 默认初始容量为 16,加载因子为 0.75。
    • Hashtable 默认初始容量为 11,加载因子为 0.75。
    • ConcurrentHashMap 默认初始容量为 16,加载因子为 0.75,采用分段锁机制进行扩容,避免整体锁竞争。

本文参考自二哥的Java进阶之路、三分恶 作者老三、Hollis Java 8Gux、ChatGPT。1


  1. https://www.yuque.com/hollis666/axzrte/gxi0rc
    https://javabetter.cn/sidebar/sanfene/collection.html ↩︎

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值