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;
    }
}

相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
19 2
|
10天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
16天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
9天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
18 2
|
9天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
13天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
12天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
37 4
|
13天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
53 4
|
13天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
13天前
|
Java 开发者