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

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——关于Java中集合的面试题相关的知识,希望对你有一定的参考价值。

文章目录:

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

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

2.1 无参构造源码分析

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

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

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

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

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

6.Collection 和 Collections 有什么区别?

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

8.HashSet 和 TreeSet 有什么区别?

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

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

11.Map中的key和value可以为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.并发集合和普通集合的区别?

并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并发集合位于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底层是哈希表数据结构,是非线程安全的,HashMap是Map的一个实现类,是将键映射到值的对象,不允许键值重复。允许空键和空值;由于非线程安全,HashMap的效率要较 Hashtable 的效率高一些。
  • Hashtable底层也是哈希表数据结构,是线程安全的一个集合,不允许 null 值作为一个 key 值或者value 值;Hashtable中所有的方法都带有sychronized关键字,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步。 
  • TreeMap集合底层是一个二叉树,实现了SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序。

6.Collection 和 Collections 有什么区别?

  • Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

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

  • ArrayList底层的数据结构是数组,是非线程安全的。支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
  • Vector底层结构也是数组,其中所有的方法都使用了synchronized来实现线程同步、线程安全,由于效率较低,现在使用较少。
  • LinkedList底层的数据结构是双向链表,是非线程安全的。对于这种数据结构而言,对数据的增删效率高,而对数据的检索效率低。
  • ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。如果集合数据是对于集合随机访问 get 和 set,ArrayList 绝对优于 LinkedList,因为 LinkedList 要移动指针。如果集合数据是对于集合新增和删除操作 add 和 remove,LinkedList 比较占优势,因为ArrayList要移动数据。
  • 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

8.HashSet 和 TreeSet 有什么区别?

  • HashSet是Set集合的一个实现类,其底层实现是HashMap的key(实际上是new了一个HashMap集合,实际上也是将元素存储到HashMap集合中了)。HashSet存储元素的顺序并不是按照存入时的顺序(和List不同)而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equals结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。 哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。
  • 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队列 为空。例:将”a,b,c,d,e”需要入栈的元素先放 a 中,a 进栈为 ”a,b,c,d,e” 出栈:a 队列目前的元素为”a,b,c,d,e”。将 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 对象的 key、value 值均可为 null。

Hahtable 对象的 key、value 值均不可为 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<Integer,User>集合, User 有 name(String)和 age(int)属性。请写一个方法实现对HashMap 的排序功能,该方法接收 HashMap<Integer,User>为形参,返回类型为 HashMap<Integer,User>,要求对 HashMap 中的 User 的 age 倒序进行排序。排序时 key=value 键值对不得拆散。

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

但凡是对集合的操作,我们应该保持一个原则就是能用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;
    }

}

 

以上是关于Java——关于Java中集合的面试题的主要内容,如果未能解决你的问题,请参考以下文章

Java中集合的理解

Java 9中集合的重载便利工厂方法有啥意义

Java中集合的概述

Java中集合的嵌套

java中集合的扩容

Java中集合的一些初始化写法