Java——12个关于Java中集合的面试题

简介: Java——12个关于Java中集合的面试题

文章目录:


1.请问 ArrayListHashSetHashMap 是线程安全的吗?如果不是怎么获取线程安全的集合?

2.ArrayList内部用什么实现的?

2.1 无参构造源码分析

2.2 有参构造源码分析(参数为容量)

2.3 有参构造源码分析(参数为集合)

3.并发集合和普通集合的区别?

4.List MapSet 有什么区别?

5.HashMap HashtableTreeMap 有什么区别

6.Collection Collections 有什么区别?

7.ArrayList LinkedListVector 有什么区别?

8.HashSet TreeSet 有什么区别?

9.List a=new ArrayList() ArrayList a =new ArrayList() 的区别?

10.请用两个队列模拟堆栈结构?

11.Map中的keyvalue可以为null

12.HashMap排序题

1.请问 ArrayList、HashSet、HashMap 是线程安全的吗?如果不是怎么获取线程安全的集合?


通过以上类的源码进行分析,每个方法都没有加锁,显然都是非线程安全的。在集合中Vector HashTable是线程安全的。打开源码会发现其实就是把各自核心方法添加上了synchronized 关键字。Collections工具类提供了相关的 API,可以让上面那3个不安全的集合变为安全的。

Collections.synchronizedCollection(c); //Collection
Collections.synchronizedList(list); //ArrayList
Collections.synchronizedMap(m); //HashMap
Collections.synchronizedSet(s); //HashSet

上面几个方法都是Collections工具类中的静态方法,都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实原理非常简单,就是将集合的核心方法添加上了synchronized关键字。


2.ArrayList内部用什么实现的?


因为数组在创建的时候长度是固定的,那么就有个问题我们往ArrayList中不断的添加对象,它是如何管理这些数组呢?通过源码可以看到ArrayList内部是用Object[]实现的。接下来我们分别分析ArrayList的构造以及add()remove()clear()方法的实现原理。

2.1 无参构造源码分析


这里的 elementData 是一个 Object类型的数组,而我们new一个ArrayList的时候,调用这个无参构造,elementData 就被赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而它是一个 private static final修饰的Object数组。


2.2 有参构造源码分析(参数为容量)

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

该构造函数传入一个 int 值,该值作为数组的长度值。如果该值小于 0,则抛出一个运行时异常 IllegalArgumentException。如果等于 0,则使用一个空数组。如果大于 0,则创建一个长度为该值的新数组。


2.3 有参构造源码分析(参数为集合)

/**
 * Constructs a new instance of {@code ArrayList} containing the elements of
 * the specified collection.
 *
 * @param collection the collection of elements to add.
 */
public ArrayList(Collection<? extends E> collection) {
    if (collection == null) {
        throw new NullPointerException("collection == null");
    }
    Object[] a = collection.toArray();
    if (a.getClass() != Object[].class) {
        Object[] newArray = new Object[a.length];
        System.arraycopy(a, 0, newArray, 0, a.length);
        a = newArray;
    }
    array = a;
    size = a.length;
}

如果调用构造函数的时候传入了一个Collection的子类,那么先判断该集合是否为 null,为 null 则抛出空指针异常。如果不是则将该集合转换为数组 a(借助toArray()方法),然后将该数组a赋值为成员变量 array,将该数组的长度作为成员变量 size

综合ArrayList源码中的这三个构造器,得出结论:ArrayList内部就是使用数组来实现的!!!


3.并发集合和普通集合的区别?


并发集合常见的有ConcurrentHashMapConcurrentLinkedQueueConcurrentLinkedDeque等。并发集合位于java.util.concurrent包下,是jdk1.5之后才有的,在 java 中有普通集合、同步(线程安全)的集合、并发集合。

普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。


4.List 和 Map、Set 有什么区别?



结构特点:List Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashCode 决定,位置是固定的(Set 集合根据 hashCode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 Set 中的元素还是无序的);

实现类:List 接口下的实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。Map 接口下的实现类(HashMap:基于hash表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;Hashtable:线程安全,低效,不支持 null 值和  null 键;LinkedHashMap:是HashMap 的一个子类,保存了记录的插入顺序;SortedMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。Set 接口下的实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals() hashCode()方法;LinkedHashSet继承与 HashSet,同时又基于LinkedHashMap 来进行实现,底层使用的是LinkedHashMp)。

区别:List集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;Map中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;Set集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet类,可以按照默认顺序,也可以通过实现 java.util.Comparator接口来自定义排序方式。


5.HashMap 和 Hashtable、TreeMap 有什么区别?


·       HashMap底层是哈希表数据结构,是非线程安全的,HashMapMap的一个实现类,是将键映射到值的对象,不允许键值重复。允许空键和空值;由于非线程安全,HashMap的效率要较 Hashtable 的效率高一些。

·       Hashtable底层也是哈希表数据结构,是线程安全的一个集合,不允许 null 值作为一个 key 值或者value 值;Hashtable中所有的方法都带有sychronized关键字,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步。 

·       TreeMap集合底层是一个二叉树,实现了SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序。


6.Collection 和 Collections 有什么区别?


·       Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有ListSet

·       Collections则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。


7.ArrayList 和 LinkedList、Vector 有什么区别?


·       ArrayList底层的数据结构是数组,是非线程安全的。支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList O(n)

·       Vector底层结构也是数组,其中所有的方法都使用了synchronized来实现线程同步、线程安全,由于效率较低,现在使用较少。

·       LinkedList底层的数据结构是双向链表,是非线程安全的。对于这种数据结构而言,对数据的增删效率高,而对数据的检索效率低。

·       ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。如果集合数据是对于集合随机访问 get setArrayList 绝对优于 LinkedList,因为 LinkedList 要移动指针。如果集合数据是对于集合新增和删除操作 add removeLinkedList 比较占优势,因为ArrayList要移动数据。

·       当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。


8.HashSet 和 TreeSet 有什么区别?


·       HashSetSet集合的一个实现类,其底层实现是HashMapkey(实际上是new了一个HashMap集合,实际上也是将元素存储到HashMap集合中了)。HashSet存储元素的顺序并不是按照存入时的顺序(和List不同)而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法如果 equals结果为true HashSet就视为同一个元素。如果equals false就不是同一个元素。哈希值相同equalsfalse的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。

·       TreeSet底层实际上是TreeMap(二叉树),new一个TreeSet的时候实际上是new了一个TreeMap,存储数据的时候,实际上是将数据存到了TreeMap集合中,是使用二叉树的原理对新add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。


9.List a=new ArrayList() 和 ArrayList a =new ArrayList() 的区别?


·       List list = new ArrayList(); 这句创建了一个 ArrayList 的对象后赋给了List,此时它是一个 List 对象了,有些 ArrayList 中存在的方法但是 List 就没有这样的属性和方法了,它就不能再用了。

·       ArrayList list=new ArrayList(); 创建一对象则保留了ArrayList 的所有属性。所以需要用到 ArrayList 独有的方法的时候不能用前者。实例代码如下:


10.请用两个队列模拟堆栈结构?


入栈:a 队列为空,b队列为空。例:将”abcde”需要入栈的元素先放 a 中,a 进栈为 ”abcde” 出栈:a 队列目前的元素为”abcde”。将 a 队列依次加入 Arraylist 集合 a 中。以倒序的方法,将 a 中的集合取出,放入 b 队列中,再将 b 队列出列。代码如下: 

import java.util.*;
/**
 *
 */
public class TestList {
    public static void main(String[] args) {
        Queue<String> queue1=new LinkedList<>(); //a队列
        Queue<String> queue2=new LinkedList<>(); //b队列
        ArrayList<String> arrayList=new ArrayList<>(); //arrayList集合是中间参数
        //向a队列中添加元素
        queue1.add("a");
        queue1.add("b");
        queue1.add("c");
        queue1.add("d");
        queue1.add("e");
        System.out.print("进栈:");
        //遍历a队列,将其依次加入arrayList集合中
        for (String q : queue1) {
            arrayList.add(q);
            System.out.print(q + " ");
        }
        System.out.println();
        //以倒序的方式取出,存入b队列
        for (int i=arrayList.size()-1;i>=0;i--) {
            queue2.add(arrayList.get(i));
        }
        //打印出栈队列b
        System.out.println("============");
        System.out.print("出栈:");
        for (String q : queue2) {
            System.out.print(q + " ");
        }
    }
}


11.Map中的key和value可以为null?


HashMap对象的 keyvalue 值均可为 null

Hahtable对象的 keyvalue 值均不可为 null。且两者的的 key 值均不能重复,若添加 key 相同的键值对,后面的 value 会自动覆盖前面的 value,但不会报错。

代码片段1如下: 

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
 *
 */
public class TestMap {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        Map<String,String> stringMap=new Hashtable<>();
        //将HashMap的key、value设置为null
        map.put(null,null);
        System.out.println("HashMap的[key]和[value]均可以为null:" + map);
        //将HashTable的key、value设置为null
        stringMap.put(null,null);
        System.out.println(stringMap);
    }
}

输出结果中,HashMap可以正常输出,而Hashtable则是报了空指针异常


再来看代码片段2

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
 *
 */
public class TestMap {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        Map<String,String> stringMap=new Hashtable<>();
        //将HashMap的key、value设置为null
        map.put(null,null);
        System.out.println("HashMap的[key]和[value]均可以为null:" + map);
        //将HashTable的key、value设置为null
        try {
            stringMap.put(null,"hello");
            System.out.println(stringMap.get(null));
        }catch (Exception e) {
            System.out.println("【ERROR】:hashtable 的[key]不能为 null");
        }
        try {
            stringMap.put("hello",null);
            System.out.println(stringMap.get("hello"));
        }catch (Exception e) {
            System.out.println("【ERROR】:hashtable 的[value]不能为 null");
        }
    }
}

此时将Hashtable中的key或者value设置为 null,可以正常的被try/catch语句块捕捉。


12.HashMap排序题


已知一个 HashMap<IntegerUser>集合, User nameString)和 ageint)属性。请写一个方法实现对HashMap 的排序功能,该方法接收HashMap<IntegerUser>为形参,返回类型为 HashMap<IntegerUser>,要求对 HashMap 中的 User age 倒序进行排序。排序时 key=value 键值对不得拆散。

注意:要做出这道题必须对集合的体系结构非常的熟悉。HashMap本身就是不可排序的,但是该题偏偏让HashMap排序,那我们就得想在API中有没有这样的 Map 结构是有序的,我们不难发现其中LinkedHashMap就具有这样的结构,是链表结构有序的,更可喜的是他是  HashMap的子类,我们返回LinkedHashMap<IntegerUser>即可,还符合面向接口编程的思想。

但凡是对集合的操作,我们应该保持一个原则就是能用JDK中的API就用JDK中的 API,比如排序算法我们不应该去用冒泡或者选择,而是首先想到用 Collections 集合工具类。

package com.szh.java01;
import java.util.*;
/**
 *
 */
class User {
    private String name;
    private Integer age;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class HashMapTest {
    public static void main(String[] args) {
        HashMap<Integer,User> userHashMap=new HashMap<>();
        userHashMap.put(1,new User("张三",25));
        userHashMap.put(2,new User("李四",28));
        userHashMap.put(3,new User("王五",21));
        System.out.println(userHashMap);
        HashMap<Integer,User> sortHashMap=sortHashMap(userHashMap);
        System.out.println(sortHashMap);
    }
    private static HashMap<Integer, User> sortHashMap(HashMap<Integer, User> userHashMap) {
        //首先拿到userHashMap的键值对集合
        Set<Map.Entry<Integer,User>> entrySet=userHashMap.entrySet();
        //将Set集合转为List集合,为了使用工具类的排序方法
        List<Map.Entry<Integer,User>> entryList=new ArrayList<Map.Entry<Integer,User>>(entrySet);
        //使用Collections集合工具类对entryList进行排序,排序规则使用匿名内部类实现
        Collections.sort(entryList, new Comparator<Map.Entry<Integer, User>>() {
            @Override
            public int compare(Map.Entry<Integer, User> o1, Map.Entry<Integer, User> o2) {
                return o2.getValue().getAge() - o1.getValue().getAge();
            }
        });
        //创建一个新的有序的HashMap子类的集合
        LinkedHashMap<Integer,User> linkedHashMap=new LinkedHashMap<>();
        //将entryList中的数据存入其中
        for (Map.Entry<Integer,User> entry : entryList) {
            linkedHashMap.put(entry.getKey(),entry.getValue());
        }
        return linkedHashMap;
    }
}

目录
打赏
0
0
0
0
85
分享
相关文章
Java大厂面试高频:Collection 和 Collections 到底咋回答?
Java中的`Collection`和`Collections`是两个容易混淆的概念。`Collection`是集合框架的根接口,定义了集合的基本操作方法,如添加、删除等;而`Collections`是一个工具类,提供了操作集合的静态方法,如排序、查找、同步化等。简单来说,`Collection`关注数据结构,`Collections`则提供功能增强。通过小王的面试经历,我们可以更好地理解这两者的区别及其在实际开发中的应用。希望这篇文章能帮助你掌握这个经典面试题。
65 4
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
67 3
java面试-基础语法与面向对象
本文介绍了 Java 编程中的几个核心概念。首先,详细区分了方法重载与重写的定义、发生阶段及规则;其次,分析了 `==` 与 `equals` 的区别,强调了基本类型和引用类型的比较方式;接着,对比了 `String`、`StringBuilder` 和 `StringBuffer` 的特性,包括线程安全性和性能差异;最后,讲解了 Java 异常机制,包括自定义异常的实现以及常见非检查异常的类型。这些内容对理解 Java 面向对象编程和实际开发问题解决具有重要意义。
41 15
Java社招面试中的高频考点:Callable、Future与FutureTask详解
大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
210 60
|
10天前
|
java常见的集合类有哪些
Map接口和Collection接口是所有集合框架的父接口: 1. Collection接口的子接口包括:Set接口和List接口 2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等 3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等 4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
144 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
66 13
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
114 16
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
112 9
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
97 12

热门文章

最新文章