秋招常见的面试题-集合

集合

1.List,Set,Map 三者的区别

  • List:(对付顺序的好帮⼿): 存储的元素是有序的、可重复的
  • Set:(注重独⼀⽆⼆的性质): 存储的元素是⽆序的、不可重复的
  • Map:(⽤ Key 来搜索的专家): 使⽤键值对(kye-value)存储,Key 是⽆序的、不可重复的,value 是⽆序的、可重复的,每个键最多映射到 ⼀个值。

2.如何选用集合

根据集合的特点:

需要根据键值获取到元素值时就选⽤ Map 接⼝下的集合
需要排序时选择 TreeMap 
不需要排序时就选择 HashMap 
需要保证线程安全就选⽤ ConcurrentHashMap ,hashtable

只需要放元素值时

需要保证元素唯⼀时选择实现Set 接⼝的集合⽐如 TreeSet 或 HashSet ,
不需要就选择实现 List 接⼝的⽐如 ArrayList 或LinkedList 

3.为什么用集合不用数组

使⽤数组存储对象具有⼀定的弊端

数组的缺点是⼀旦声明之后,⻓度就不可变了
声明数组时的数据类型也决定了该数组存储的数据的类型
数组存储的数据是有序的、可重复的,特点单⼀

集合提⾼了数据存储的灵活性

Java 集合不仅可以⽤来存储不同类型不同数量的对象,还可以保存具有映射关系的数据

4.线程安全的集合代替不安全集合

java.util.concurrent 包中提供了很多并发容器

Arraylist , LinkedList , Hashmap , HashSet , TreeSet , TreeMap , PriorityQueue 都 不是线程安全的

HashMap - ConcurrentHashMap \ HashTble不用
ArrayList - CopyOnWriteArrayList \ Vector不用
LinkedList - ConcurrentLinkedQueue

List

1.Arraylist 和 Vector 的区别

  1. ArrayList 是 List 的主要实现类,底层使⽤ Object[ ]存储,适⽤于频繁的查找⼯作,线程不安全 ;
  2. Vector 是 List 的古⽼实现类,底层使⽤ Object[ ]存储,线程安全的。

2.Arraylist 与 LinkedList 区别?

  1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
  2. 底层数据结构:ArrayList - Object 数组,LinkedList双向链表
  3. 插⼊和删除是否受元素位置的影响:
ArrayList 采⽤数组存储,所以插⼊和删除元素的时间复杂度受元素位置的影响。
LinkedList 删除元素时间复杂度不受元素位置的影响,近似O(1)
在指定位置 ii 插⼊和删除元素的话时间复杂度近似为  o(n)) 因为需要先移动到指定位置再插⼊。
  1. 是否⽀持快速随机访问:LinkedList 不⽀持⾼效的随机元素访问,⽽ ArrayList ⽀持。快速随 机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法)。
  2. 内存空间占⽤: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间

3.在对List做for循环的时候,是否可以删除当前元素

1、for循环遍历list,会漏掉重复的值,要加上i–

for(int i=0;i<list.size();i++){
    if(list.get(i).equals("del"))
        list.remove(i);
    //  i--;
}

删除某个元素后,list的大小发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。因此,这种方式可以用在删除特定的一个元素时使用,但不适合循环删除多个元素时使用。

2.增强for循环,删除完毕马上使用break跳出

for(String x:list){
    if(x.equals("del")){
        list.remove(x);
        //break;
    }
}

这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错。

3.iterator遍历

Iterator<String> it = list.iterator();
while(it.hasNext()){
    String x = it.next();
    if(x.equals("del")){
        it.remove();
    }
}

这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。

总结:

(1)循环删除list中特定一个元素的,可以使用三种方式中的任意一种,但在使用中要注意上面分析的各个问题。

(2)循环删除list中多个元素的,应该使用迭代器iterator方式。

Set

1.Comparable和Comparator区别

  • comparable 接⼝实际上是出⾃ java.lang 包 它有⼀个 compareTo(Object obj) ⽅法⽤来排序
  • comparator 接⼝实际上是出⾃ java.util 包它有⼀个 compare(Object obj1, Object obj2) ⽅法⽤ 来排序
public class ComparatorDemo {
    public static void main(String[] args){
        int[] value = {-5,-2,-1,0,1,2,5};

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < value.length; i++) {
            arrayList.add(value[i]);
        }

        System.out.println("原来数据"+arrayList);

        // void reverse(List list):反转
        Collections.reverse(arrayList);
        System.out.println("反转:"+arrayList);

        // void sort(List list),按⾃然排序的升序排序
        Collections.sort(arrayList);
        System.out.println("自然排序:"+arrayList);

        // 定制排序的⽤法
        Collections.sort(arrayList, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        System.out.println("定制排序Comparator的compara方法:"+arrayList);
    }
}
原来数据[-5, -2, -1, 0, 1, 2, 5]
反转:[5, 2, 1, 0, -1, -2, -5]
自然排序:[-5, -2, -1, 0, 1, 2, 5]
定制排序Comparator的compara方法:[5, 2, 1, 0, -1, -2, -5]

2.比较HashSet、LinkedHashSet 和 TreeSet 三者的异同

HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值

LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历

TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序

Map

1.HashMap 和 Hashtable 的区别

1.线程是否安全: HashMap 是⾮线程安全的,HashTable 是线程安全的,因为 HashTable 内部的⽅法
基本都经过 synchronized 修饰。
2.效率: 因为线程安全的问题,HashMap 要⽐ HashTable 效率⾼⼀点。另外,HashTable 基本被淘
汰,不要在代码中使⽤它;concurrentHashMap
3. 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有
⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出NullPointerException
4.初始容量⼤⼩和每次扩充容量⼤⼩的不同:
- HashMap  默认的初始化⼤⼩为 16。之后每次扩充,容量变为原来的 2 倍
- HasTable 默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1
5.底层数据结构:
-HashMap 数组+链表或者红黑树
-HashTable 数组+链表

2.HashMap和HashSet区别

HashSet 底层就是基于 HashMap 实现的。

HashSet 的 源码⾮常⾮常少,因为除了 clone() 、 writeObject() 、 readObject() 是 HashSet ⾃⼰不得不实现 之外,其他⽅法都是直接调⽤ HashMap 中的⽅法。

HashMapHashSet
实现了 Map 接⼝实现 Set 接⼝
存储键值对仅存储对象
put()添加元素add()添加元素
HashMap 使⽤键 (Key)计算 HashcodeHashSet 使⽤成员对象来计算 hashcode 值

对于两个对象来说 hashcode 可能相同,所以 equals()⽅法⽤来判断对象的相等性

3.hashCode()与 equals():

  1. 如果两个对象相等,则 hashcode ⼀定也是相同的
  2. 两个对象相等,对两个 equals ⽅法返回 true
  3. 两个对象有相同的 hashcode 值,它们也不⼀定是相等的
  4. 综上,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖
  5. hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode(),则该 class 的两个 对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。

4.HashMap 的底层实现

1.8之后:HashMap 底层是 数组和链表或者红黑树

HashMap 通过 key 的hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这⾥的 n 指的是数组的⻓度),如果当前位置存在元素的话,就判断该元素与要存⼊的元素的 hash
值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了防⽌⼀些实现
⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞。

相⽐于 JDK1.8 的 hash ⽅法 ,JDK 1.7 的 hash ⽅法的性能会稍差⼀点点,因为毕竟扰动了 4 次。

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链 表。若遇到哈希冲突,则将冲突的值加到链表中即可。

1.8 解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)时,将链表转化为红⿊树,以减少搜索时间。

TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都⽤到了红⿊树。红⿊树就是为了解 决⼆叉查找树的缺陷,因为⼆叉查找树在某些情况下会退化成⼀个线性结构。

HashMap遍历
  1. 使用迭代器(Iterator)EntrySet 的方式进行遍历;
  2. 使用迭代器(Iterator)KeySet 的方式进行遍历;
  3. 使用 For Each EntrySet 的方式进行遍历;
  4. 使用 For Each KeySet 的方式进行遍历;
  5. 使用 Lambda 表达式的方式进行遍历;
  6. 使用 Streams API 单线程的方式进行遍历;
  7. 使用 Streams API 多线程的方式进行遍历。

4.ForEach KeySet

public class HashMapTest {
    public static void main(String[] args) {
        // 创建并赋值 HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java中文社群");
        // 遍历
        for (Integer key : map.keySet()) {
            System.out.print(key);
            System.out.print(map.get(key));
        }
    }
}

5.Lambda

public class HashMapTest {
    public static void main(String[] args) {
        // 创建并赋值 HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java中文社群");
        // 遍历
        map.forEach((key, value) -> {
            System.out.print(key);
            System.out.print(value);
        });
    }
}

5.ConcurrentHashMap 和 Hashtable 的区别

底层数据结构: 1.8之后都是数组和链表或者红黑树

实现线程安全的⽅式:

  • 1.7 ConcurrentHashMap(分段锁) 对整个桶数组进⾏了分割分段(Segment)

每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。

  • 1.8之后 Node 数组+链表+红⿊树的数据结构来实现,并发控制使⽤ synchronized 和 CAS 来操作。

Hashtable(同⼀把锁) :只有一把全表锁,使⽤ synchronized 来保证线程安全,效率⾮常低下。

synchronized 只锁定当前链表或红⿊⼆叉树的⾸节点,这样只要 hash 不冲突,就不会产⽣并发,效率 ⼜提升 N 倍。

6.Collections ⼯具类

排序

void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按⾃然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前⾯。当distance为负数时,将 list的前distance个元素整体移到后⾯

查找,替换操作

int binarySearch(List list, Object key)//对List进⾏⼆分查找,返回索引,注意List
必须是有序的
int max(Collection coll)//根据元素的⾃然顺序,返回最⼤的元素。 类⽐int
min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最⼤元素,排序规则由
Comparatator类控制。类⽐int min(Collection coll, Comparator c)
void fill(List list, Object obj)//⽤指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第⼀次出现的索
引,找不到则返回-1,类⽐int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), ⽤新元素替换旧元素

同步控制:最好不要⽤下⾯这些⽅法,效率⾮常低

Collections 提供了多个 synchronizedXxx() ⽅法·,该⽅法可以将指定集合包装成线程同步的集合, 从⽽解决多线程并发访问集合时的线程安全问题。

synchronizedCollection(Collection<T> c) //返回指定 collection ⽀持的同步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表⽀持的同步(线程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射⽀持的同步(线程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set ⽀持的同步(线程安全的)set。

Arrays.asList()避坑指南

Arrays.asList() 在平时开发中还是⽐较常⻅的,我们可以使⽤它将⼀个数组转换为⼀个 List 集合。

String[] myArray = { "Apple", "Banana", "Orange" };
List<String> myList = Arrays.asList(myArray);
//上⾯两个语句等价于下⾯⼀条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子津

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值