Java集合题--Interview

本文深入解析Java集合框架,包括Collection与Collections的区别、ArrayList与Vector的特点、HashMap的数据结构及扩容机制。探讨快速失败与安全失败机制,以及List、Map、Set接口的使用特性,对比HashSet与TreeSet的区别,阐述LinkedHashMap的实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在这里插入图片描述

Collection和Collections的区别

Collection

是集合的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。
Collection接口是Set接口和List接口的父接口

Collections

Collections是一个集合框架的帮助类。里面包含了一些对集合的排序。
搜索以及序列化的操作,包含有各种有关集合的操作的静态多态方法,此类不能实例化(工具类)
Collections是一个包装类,Collection表示一组的对象,这些对象也称为collection元素。
一些collection允许有重复的元素(List)。而另一些则不允许。

Collections中常用的方法

//将集合中的元素反转
Collections.reverse(list);

//addAll方法可以往集合中添加元素,也可往集合中添加一个集合
Collections.addAll(list,9,20,56);``

//打乱集合中的元素
Collections.shuffle(list);

//按照字符串首字符的升序排列
Collections.sort(list2);

调用代码

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
  
public class TestCollections {  
      
    public static void main(String args[]) {  
        //注意List是实现Collection接口的  
        List list = new ArrayList();  
        double array[] = { 112, 111, 23, 456, 231 };  
        for (int i = 0; i < array.length; i++) {  
            list.add(new Double(array[i]));  
        }  
        Collections.sort(list);  
        for (int i = 0; i < array.length; i++) {  
            System.out.println(list.get(i));  
        }  
        // 结果:23.0 111.0 112.0 231.0 456.0  
    }  
}  

ArrayList和Vector的区别

ArrayList

通过数组实现。允许对数组的随机访问。
当超过初始容量的时候,容量扩增为原来的1.5倍
数组的缺点是:每个元素不能有间隔。当数组大小不满足时,就要将数据复制到新的存储空间中(不适合插入和删除)

Vector

通过数组实现的。支持线程同步,即某一时刻只有一个线程能够写Vector,避免读写不一致
当超过初始容量的时候,容量扩增为原来的2倍,并且可以设置增长空间大小
实现同步花费很高,访问Vector比ArrayList慢很多。

说说ArrayList,Vector,LinkedList存储性能和特性

ArrayList和Vector

他们的底层都是一样的,都是使用数组方式存储,数组的容量大于实际的存储状态。以便以后的删除和增加元素。
实现了Serializable接口允许按序号索引元素,但是涉及插入,删除此类的操作比较慢
Vector:此方法由于添加了syncchronized修饰。因此Vector是线程安全的容器。
		但是性能上较ArrayList差。因此已经是Java遗留的容器了

LinkedList

使用双向列表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,
这种链式存储方法与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行向前或向后遍历,
但是插入数据时只需要记录本项的前后项即可,所以插入速度较快

附:

Vector属于遗留容器,(Hashtable,Dictionary,BitSet,Stack,Properties都是遗留容器)
由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作。
可以通过Collections中的synchronizedList方法将其转换成线程安全的容器后再使用

快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

快速失败(fail-fast)

再用迭代器遍历一个集合对象,如果遍历过程中对象的内容进行了修改(增加,删除,修改)。则会抛出Concurrent Modification Exception。
原理:迭代器在遍历的过程中,会使用一个modCount变量。集合在遍历期间如果内容发生变化,
	就会改变modCount的值。迭代器使用hasNext()/next()遍历下一个元素之前,
	都会检测modCount变量是否为expectedmodCount值,否则抛出异常,终止遍历。

注意:

这里异常抛出条件是检测到modCount!=expectedmodCoun这个条件。如果集合发生变化时修改modCount值
刚好又被设置为expectedmodCount值,则异常不会抛出(不能依赖这个判断)
场景:java.util包下面的集合类都是快速失败的,不能在多线程并发修改(迭代过程中被修改)

安全失败(fail-safe)

采用安全失败机制的集合容器。在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容(在拷贝上遍历)
原理:由于是基于集合的拷贝进行遍历,所以在遍历过程中对原有集合所作的修改不能被迭代器检测。
缺点:迭代器不能访问到修改后的内容。即:迭代器开始那一刻遍历的都是集合的拷贝对象。
	遍历的期间原集合发生的修改迭代器是不知道的。

场景:
	java.util.concurrent包下的容器都是安全失败,可以在多线程下使用。并发修改

Hashmap的数据结构

HashMap是我们使用非常多的collection。基于Map接口实现。

提供了三个构造函数:

  • HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。

数据结构
HashMap:使用数组+链表来实现(数组的每一项都是一条链,其中参数initialCapacity就代表了该数组的长度。在这里插入图片描述
储存的实现

public V put(K key, V value) {
        //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
        if (key == null)
            return putForNullKey(value);
        //计算key的hash值
        int hash = hash(key.hashCode());                  ------(1)
        //计算key hash 值在 table 数组中的位置
        int i = indexFor(hash, table.length);             ------(2)
        //从i出开始迭代 e,找到 key 保存的位置
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //判断该条链上是否有hash值相同的(key相同)
            //若存在相同,则直接覆盖value,返回旧value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;    //旧值 = 新值
                e.value = value;
                e.recordAccess(this);
                return oldValue;     //返回旧值
            }
        }
        //修改次数增加1
        modCount++;
        //将key、value添加至i位置处
        addEntry(hash, key, value, i);
        return null;
    }

插入步骤:

  1. 首先判断key是否为null;若为null,则直接调用putForNullkey()方法
  2. 若不为空计算key的hash值。根据hash值搜索s数组中的索引位置。
  3. 如果table数组在该位置处有元素,则通过比较,若存在该key,则覆盖。否则保存该key

读取的时候通过key的hash值找到table数组中的索引处的Entry,如何返回key对应的value即可。

hashMap什么时候进行扩容?

当HashMap中的元素越来越多的时候,就会进行扩容(碰撞几率越来越高),为了提高查询效率
那么具体在什么情况进行扩容呢?
->当HashMap中的元素超过数据大小*loadFactor时,就会进行扩容。而loadFactor默认值为0.75。
上面也有提到,在初始化HashMap的时候,可以指定HashMap的loadFactor的大小。
比如 HashMap默认大小为16,loadFactor默认大小为0.75 .在大小为16*0.75=12的时候就会进行扩容

List,Map,Set三个接口,存取元素时,各有什么特点?

List与Set都是单列元素的集合,它们有一个共同的父接口collection

Set

存元素:add方法有一个boolean的返回值。当集合中没有某个元素,此时成功假如元素后,返回true
	当集合中含有某个元素equals相等的元素时。此时add方法无法加入该集合,返回false

取元素:不能随机取,只能通过Iterator接口取得所有的元素,再逐一遍历各个元素

List

存元素:多次调用add(Object)方法时,根据先进先出。也可以调用add(int index,Object)方法(插队

取元素:①Iterator接口取得所有,逐一遍历各个元素
		②调用get(index,i)来说明取第几个

Map

存元素:put(Object key,Object value),每次存储一对key/value,不能存储重复key,按equals比较相等
取元素:get(Object key)根据key获得相应的value
	可以获得key的集合,也可以获得value的集合
	还可以获得key和value组合成的Map.Entry对象的集合。

Set的元素里面是不能重复的,那么用什么方法来区分重复与否呢?是用 ==还是equals()?它们有何区别呢?

是用equals来区别重复的,因为Set集合里面存放的是一个个对象的引用,而equals比较的是引用的大小(比较符合)
“==”:用来判断是否为同一个对象。
“equals”:用来判断是否引用了同一个对象的

heap和stack有什么区别

栈堆的概念

堆栈是两种数据结构。堆栈是一种数据项按序排列的数据结构。只能在一端堆数据项进行删除和插入。(在单片机中应用)。堆是个特俗的存储区,主要功能是暂时存放数据和地址。通常用来保护端点和现场。

栈和堆的区别

一、 堆栈空间分配的区别

  1. 栈:由操作系统自动分配释放,存放函数的参数值,局部变量值等。
  2. 堆:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS收回。

二、堆栈缓存方式的区别

  1. 栈使用的是一级缓存,它们通常是被调用时处于存储空间中,调用完毕立即释放
  2. 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定,所以调用这些对象速度要相对低一些

三、堆栈数据结构区别:

  1. 堆:可以被看成是一棵树,如:堆排序
  2. 栈:一种先进后出的数据结构

Java集合类框架的基本接口有哪些?

总共由两大接口:Collection和Map,一个元素集合,一个是键值对集合;其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合;而ArrayList和LinkedList实现了List接口,HashSet实现了Set接口。这几个都比较常用。HashMap和HashTable实现了Map接口;并且HashTable是线程安全的,但是HashMap性能更好

在这里插入图片描述

HashSet和TreeSet有什么区别?

HashSet

  • 不能保证元素的排列顺序,顺序有可能发生变化
  • 集合元素可以是null,但只能放入一个null
  • HashSet基层是采用HashMap实现的
  • HashSet底层是哈希表实现的

当想HashSet中插入一个对象时,HashSet会先调用对象的hashCode()方法来获取到该对象的hashCode值,如何根据hashCode值来决定对象在HashSet中的存储位置,如果相等,那就不插入,如果不等,会调用equals方法,如果equals返回true,说明已经存在,就不能再插入,如果为false,则可以插入。

TreeSet

  • 数据是排序好的,不允许放入null值
  • 通过TreeMap实现的,只不过Set用的是Map的key
  • 底层实现是采用二叉树(红-黑树)的数据结构

底层采用红-黑树的数据结构。使用这种数据结构可以重Set中获取有序的序列。但是前提条件是:元素必须实现Comparable接口。当往Set中插入一个新的元素的时候,首先会遍历Set中已经存在的元素。根据返回的结果,决定插入位置。进而也就保证了元素的顺序。并调用compareTo ()方法。

HashSet的底层实现是什么?

先看一下HashSet的源码
在这里插入图片描述
对于HashSet而言,它是基于HashMap实现的。HashSet底层使用的是HashMap来保存的元素。因此,HashSet的实现比较简单。不保证Set的迭代顺序。特别是它不保证顺序恒久不变,此类允许使用null元素。

HashSet的插入与删除都涉及到HashMap的插入与删除,判断HashMap的key

LinkedHashMap的实现原理?

输出与插入顺序是一致的。
原理:

  • 在LinkedHashMap中,是通过双联表的结构来维护节点的顺序的。,实际上在内存中的情况如下图所示,每个节点都进行了双向的连接,维持插入的顺序(默认)
  • LinkedHashMap是HashMap的亲儿子,直接继承HashMap类。LinkedHashMap中的节点元素为Entry<K,V>,直接继承HashMap.Node<K,V>。

为什么集合类没有实现Cloneable和Serializable接口?

克隆

  • 克隆是把一个对象里面的属性值,复制给另一个对象。而不是对象引用的复制

实现Serializable序列化的作用

  • 将对象的状态保存在存储媒体中一边可以在以后重写创建出完全相同的副本
  • 按值将对象从一个应用程序域法相另一个应用程序域

实现Serializable接口的作用就是可以把对象存到字节流,然后可以恢复。所以你想你的对象没有序列化,怎么才能在网络传输呢?要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。如果你不需要分布式应用,那就没必要实现序列化

Iterator和Listlterator的区别是什么?

在这里插入图片描述

  • Iterator 可用来遍历 Set 和 List 等集合,但是 ListIterator 只能用来遍历 List 。
  • ListIterator有add方法,可以向List中添加对象,而Iterator不能。
  • Iterator 对集合只能是前向遍历, ListIterator 既可以前向也可以后向。
  • ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
  • ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  • 都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

数组(Array)和列表(ArrayList)有什么区别?什么时候应该是有Array而不是ArrayList?

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
  • Array大小是固定的,ArrayList的大小是动态变化的。
  • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。

空间大小比较:

  • Array:它的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
  • ArrayList的空间是动态增长的,如果空间不够,它会创建一个空间比原空间大约0.5倍的新数组,然后将所有元素复制到新数组中,接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。(比较麻烦的地方)。

适用场景
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里,但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList。

Java集合类框架的最佳实践有哪些?

在这里插入图片描述

  1. 根据应用需要正确选择要使用的集合类型对性能非常重要,比如:假如知道元素的大小是固定的,那么选用Array类型而不是ArrayList类型更为合适。
  2. 有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。
  3. 为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
  4. 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。
  5. 编程的时候接口优于实现
  6. 底层的集合实际上是空的情况下,返回为长度是0的集合或数组而不是null。

Comparable和Comparator接口是干什么的?列出它们有何区别?

  • Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
  • Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和Comparator相等。只有当输入参数也是一个Comparator并且输入参数和当前Comparator的排序结果是相同的时候,这个方法才返回true。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值