JAVA常见面试题——后端--集合篇

2.1. 在平常工作中常用的集合有哪些

  1. ArrayList

    • 基于动态数组实现,支持动态增长,适用于查找和遍历操作频繁的场景。

  2. LinkedList

    • 基于链表实现,支持高效的插入和删除操作,适用于频繁插入和删除元素的场景。

  3. HashMap

    • 基于哈希表实现,提供快速的插入、删除和查找操作,适用于键值对存储的场景。

  4. HashSet

    • 基于 HashMap 实现,是一组不重复元素的集合,适用于去重或者快速查找元素的场景。

  5. TreeMap

    • 基于红黑树实现的有序 Map 结构,支持按照键的顺序进行排序存储。

  6. TreeSet

    • 基于 TreeMap 实现的有序 Set 集合,元素按照自然顺序排序。

  7. LinkedHashMap

    • 基于链表和哈希表实现的 Map 结构,保持元素的插入顺序。

  8. LinkedHashSet

    • 基于 LinkedHashMap 实现的有序 Set 集合,保持元素的插入顺序。

这些集合类提供了丰富的方法和数据结构,用于解决各种不同的问题和场景。选择合适的集合类取决于你的需求,例如是否需要有序存储、是否允许重复元素、对元素的查找、插入、删除等操作的频率等。

2.2. 集合有哪些,区别是什么,扩容机制,扩容因子

在 Java 中,常见的集合框架包括 List、Set、Map 等。

  1. List:有序集合,可以存储重复元素。

    • ArrayList:基于动态数组实现,支持随机访问,扩容机制为当前容量的 50% 增加。

    • LinkedList:基于链表实现,适合插入和删除操作频繁的场景。

  2. Set:不允许重复元素的集合。

    • HashSet:基于哈希表实现,使用 HashMap 存储数据,扩容机制和 HashMap 相同。

    • TreeSet:基于红黑树实现,元素有序且唯一。

  3. Map:键值对的映射集合。

    • HashMap:基于哈希表实现,扩容因子默认为 0.75,扩容机制为当前容量的两倍。

    • TreeMap:基于红黑树实现的有序 Map,按照键的自然顺序进行排序。

扩容机制是为了应对当集合大小达到一定阈值时,容量不足以存储更多元素时的处理机制。扩容因子则是触发扩容的阈值。当集合中的元素数量达到容量乘以扩容因子时,集合会进行扩容操作,以保证性能和内存的合理利用。

例如,HashMap 默认的扩容因子为 0.75,即当 HashMap 中元素数量达到当前容量的 75% 时,会触发扩容操作,将当前容量翻倍以增大存储空间。这样可以在性能和空间之间取得一个平衡,避免频繁的扩容和浪费内存。

2.3. Arraylist 和 Linkedlist 的区别

ArrayListLinkedList 是 Java 中常见的两种集合实现,它们的主要区别在于底层数据结构和操作特点:

  1. 底层数据结构

    • ArrayList 基于动态数组实现,内部使用数组来存储元素。它支持随机访问(根据索引快速访问元素),但在插入和删除元素时可能需要移动其他元素,影响性能。

    • LinkedList 基于双向链表实现,每个节点都存储了对前后节点的引用。它对于插入和删除操作更加高效,因为只需改变节点的指针,但随机访问效率较低,需要从头或尾开始遍历。

  2. 访问效率

    • ArrayList 支持快速的随机访问,时间复杂度为 O(1),因为可以通过索引直接访问元素。

    • LinkedList 随机访问效率较差,需要从头或尾开始遍历到目标位置,时间复杂度为 O(n)。

  3. 插入和删除操作

    • ArrayList 在中间插入或删除元素时需要移动元素,涉及到数组的拷贝和移动,效率较低,时间复杂度为 O(n)。

    • LinkedList 在链表中间插入或删除元素效率较高,只需改变节点的指针,时间复杂度为 O(1)。

  4. 空间占用

    • ArrayList 在添加元素时可能需要进行扩容,会预留额外的空间,可能会浪费一些空间。

    • LinkedList 每个节点需要额外存储指针,可能会占用更多的空间。

综合来说,如果需要频繁地进行插入和删除操作,而不太需要随机访问元素,LinkedList 可能更适合;而如果需要频繁随机访问元素,ArrayList 可能更适合。

2.4. 有一个业务,里面涉及到很多元素,然后将这些元素存到一个集合中去重,筛选出重复的元素(如果用list去重呢?回答方向使用Stream流)

使用 Stream 流可以非常方便地进行集合元素的去重和筛选操作。假设有一个 List 存储了很多元素,需要对其中的元素进行去重并筛选出重复的元素,可以按照以下步骤进行操作:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
​
public class Main {
    public static void main(String[] args) {
        // 假设有一个存储了很多元素的 List
        List elements = Arrays.asList("A", "B", "C", "A", "B", "D");
​
        // 使用 Stream 流进行去重和筛选
        List distinctDuplicates = elements.stream()
                .collect(Collectors.groupingBy(e -> e)) // 按元素分组
                .entrySet().stream()
                .filter(entry -> entry.getValue().size() > 1) // 筛选出重复元素
                .map(entry -> entry.getKey()) // 获取重复的元素
                .collect(Collectors.toList()); // 收集结果到 List
​
        // 输出结果
        System.out.println("重复的元素:" + distinctDuplicates);
    }
}

这段代码通过 Stream 流的 groupingBy 方法按元素进行分组,然后筛选出出现次数大于 1 的元素,最终获取重复的元素并存储到一个新的 List 中。

2.5. Stream流常见的作用?

Stream 流是 Java 8 引入的一种数据处理方式,它提供了一种更高效、更简洁的方式来操作集合元素。Stream 流的常见作用包括:

  1. 集合数据处理:对集合元素进行过滤、映射、排序、去重、分组等操作,例如使用 filtermapsorteddistinctcollect 等方法。

  2. 数据筛选与转换:可以轻松地从集合中筛选出满足特定条件的元素,并将元素进行转换、提取等操作。

  3. 聚合操作:执行聚合操作,例如求和、求平均值、统计元素个数等,使用 reducecountsumaverage 等方法。

  4. 并行处理:Stream 流支持并行处理,能够充分利用多核处理器提高处理效率,在某些情况下能加速数据处理。

  5. 延迟执行:Stream 流的操作都是延迟执行的,只有遇到终止操作时才会触发中间操作的执行,这种延迟特性可以优化性能。

  6. 函数式编程:Stream 流支持函数式编程风格,可以使用 Lambda 表达式来简化代码、实现更优雅的数据处理。

  7. 处理无限流:Stream 流能够处理无限流,例如 generateiterate 等方法可以生成无限的元素流,并在需要时进行限制或截断。

这些作用使得 Stream 流成为处理集合数据更加便捷、高效的方式,大大简化了数据处理的过程,并且提供了更多的灵活性和可读性。

2.6. List和Set,以及两者与Map的区别、优缺性

List、Set 和 Map 是 Java 集合框架中常见的数据结构,它们之间有着一些明显的区别和适用场景:

List:

  • 特点:有序集合,可以存储重复元素,允许通过索引访问元素。

  • 实现类:常见的实现类有 ArrayList 和 LinkedList。

  • 优点:支持随机访问,可以根据索引快速访问元素;允许存储重复元素。

  • 缺点:在中间插入或删除元素时,可能需要移动其他元素,影响性能。

Set:

  • 特点:不允许重复元素的集合,无序。

  • 实现类:常见的实现类有 HashSet 和 TreeSet。

  • 优点:可以快速判断元素是否存在,不允许存储重复元素。

  • 缺点:无序,不能通过索引访问元素。

Map:

  • 特点:键值对的映射集合,存储键值对,键不能重复,值可以重复。

  • 实现类:常见的实现类有 HashMap 和 TreeMap。

  • 优点:可以根据键快速查找值;键不能重复,值可以重复。

  • 缺点:不允许键重复,值可以重复;无序。

区别和适用场景:

  • 重复元素:List 允许存储重复元素,Set 不允许存储重复元素。

  • 是否有序:List 是有序集合,Set 是无序集合。

  • 索引访问:List 可以通过索引快速访问元素,而 Set 不支持索引访问。

  • 键值对映射:Map 是键值对的映射集合,存储键值对的关联关系。

选择使用场景:

  • 如果需要存储有序的元素并允许重复,使用 List。

  • 如果需要存储不重复的元素,且不关心顺序,使用 Set。

  • 如果需要存储键值对映射关系,使用 Map。

在选择集合类型时,根据需求的特点和操作方式,选择最适合的集合类型能够更高效地处理数据。

2.7对Hash有了解吗?(Hash算法)

哈希算法:用于将任意长度的二进制值串映射为固定长度的二进制值串,映射之后得到的二进制值就是哈希值(散列值)。

哈希函数:也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值、杂凑值或者消息摘要(Message Digest)。它是一种单向密码体质,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过程。

哈希算法特性:

从哈希值不能反向推导出原始数据(所以哈希算法也叫单向算法,不可逆);

对输入数据非常敏感,哪怕原始数据只修改了一个比特,最后得到的哈希值也大不相同;

散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率非常小;

哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值

2.8. 实际应用有哪几种Hash

①hash分类:

  • 加法Hash;

  • 位运算Hash;

  • 乘法Hash;

  • 除法Hash;

  • 查表Hash;

  • 混合Hash

②在实际应用中,哈希函数(hash function)被广泛应用于各种领域和用途。下面是一些常见的哈希应用:

1、场景一:安全加密

日常用户密码加密通常使用的都是 md5、sha等哈希函数,因为不可逆,而且微小的区别加密之后的结果差距很大,所以安全性更好。

2、场景二:唯一标识

比如 URL 字段或者图片字段要求不能重复,这个时候就可以通过对相应字段值做 md5 处理,将数据统一为 32 位长度从数据库索引构建和查询角度效果更好,此外,还可以对文件之类的二进制数据做 md5 处理,作为唯一标识,这样判定重复文件的时候更快捷。

3、场景三:数据校验

比如从网上下载的很多文件(尤其是P2P站点资源),都会包含一个 MD5 值,用于校验下载数据的完整性,避免数据在中途被劫持篡改。

4、场景五:散列函数

前面已经提到,PHP 中的 md5、sha1、hash 等函数都是基于哈希算法计算散列值

5、场景五:负载均衡

对于同一个客户端上的请求,尤其是已登录用户的请求,需要将其会话请求都路由到同一台机器,以保证数据的一致性,这可以借助哈希算法来实现,通过用户 ID 尾号对总机器数取模(取多少位可以根据机器数定),将结果值作为机器编号。

6、场景六:分布式缓存

分布式缓存和其他机器或数据库的分布式不一样,因为每台机器存放的缓存数据不一致,每当缓存机器扩容时,需要对缓存存放机器进行重新索引(或者部分重新索引),这里应用到的也是哈希算法的思想。

③几种Hash算法的扩展应用

1、SimHash

simHash是google用于海量文本去重的一种方法,它是一种局部敏感hash。那什么叫局部敏感呢,假定两个字符串具有一定的相似性,在hash之后,仍然能保持这种相似性,就称之为局部敏感hash。普通的hash是不具有这种属性的。simhash被Google用来在海量文本中去重。

2、GeoHash

GeoHash将地球作为为一个二维平面进行递归分解。每个分解后的子块在一定经纬度范围内拥有相同的编码。以下图为例,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存。

3、布隆过滤器

布隆过滤器被广泛用于黑名单过滤、垃圾邮件过滤、爬虫判重系统以及缓存穿透问题。对于数量小,内存足够大的情况,我们可以直接用hashMap或者hashSet就可以满足这个活动需求了。但是如果数据量非常大,比如5TB的硬盘上放满了用户的参与数据,需要一个算法对这些数据进行去重,取得活动的去重参与用户数。这种时候,布隆过滤器就是一种比较好的解决方案了。

布隆过滤器其实是基于bitmap的一种应用,在1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数,用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难,主要用于大数据去重、垃圾邮件过滤和爬虫url记录中。核心思路是使用一个bit来存储多个元素,通过这样的方式来减少内存的消耗。通过多个hash函数,将每个数据都算出多个值,存放在bitmap中对应的位置上。

HashMap和Hashtable的区别

HashMap 和 Hashtable 都是 Java 中用于存储键值对的集合类,它们之间有一些区别:

  1. 同步性:

    • Hashtable 是线程安全的,所有的方法都是同步的(synchronized),即在多线程环境下使用时,不需要额外的同步措施。

    • HashMap 是非线程安全的,不保证线程安全性。在多线程环境中使用时,需要手动进行同步控制。

  2. null键值的支持:

    • Hashtable 不允许键或值为 null,如果存入 null,会抛出 NullPointerException。

    • HashMap 允许键为 null,值也可以为 null,允许存储键或值为 null 的键值对。

  3. 继承关系:

    • Hashtable 是早期的集合类,实现了 Map 接口和 Dictionary 类,并继承自 Dictionary 类。

    • HashMap 实现了 Map 接口,继承自 AbstractMap 类,不直接继承自 Dictionary。

  4. 性能:

    • 在单线程环境下,HashMap 通常比 Hashtable 的性能更好,因为 Hashtable 的方法都是同步的,在不需要同步的情况下会有一些额外开销。

综上所述,HashMap 是更常用的键值对集合,因为它不限制 null 的使用并且在单线程环境下性能更好。而 Hashtable 在多线程环境下可以提供线程安全,但在性能上可能不如 HashMap

arraylist,hashmap,hashset---能不能存null

在 Java 中,这些集合类对 null 的处理方式如下:

  • ArrayList:可以存储 null 值。ArrayList 是数组实现的动态数组,可以存储任意类型的对象,包括 null。

  • HashMap:允许键和值为 null。在 HashMap 中,键和值都可以存储为 null。但需要注意,若将键存储为 null,则其对应的键值对将存储在哈希表数组的第一个位置(索引为 0)。

  • HashSet:允许存储一个 null 元素。HashSet 是基于 HashMap 实现的,它内部使用 HashMap 来存储元素。因此,HashSet 可以存储一个 null 元素。

需要注意的是,虽然这些集合类允许存储 null,但在使用时应谨慎考虑 null 值可能带来的问题,特别是在使用 null 作为键时可能引发的空指针异常或逻辑错误。

Map中什么是线程安全的

在 Java 中,线程安全的 Map 实现主要是 ConcurrentHashMapConcurrentHashMap 是一个线程安全的哈希表实现,与 Hashtable 不同,它提供了更好的并发性能。

ConcurrentHashMap 的线程安全性主要基于以下几个特性:

  1. 分段锁机制ConcurrentHashMap 内部使用分段锁(Segment)来控制不同部分(段)的访问,将整个 Map 分成多个小的 Segment,每个 Segment 独立加锁,不同线程访问不同 Segment 的数据时可以并行进行,提高了并发性能。

  2. 读写分离ConcurrentHashMap 允许多个线程同时读取,但对于写操作,需要加锁。因此,它的读操作不会阻塞,可以并发进行。

  3. 安全性保障:在保证并发性的同时,ConcurrentHashMap 提供了一定的安全性保障,避免了传统 HashMap 在多线程环境下可能导致的并发修改异常。

除了 ConcurrentHashMap,还有一些线程安全的 Map 实现,如 Collections.synchronizedMap() 可以将普通的 HashMap 包装成线程安全的 Map,但它的并发性能通常不如 ConcurrentHashMap

需要一个保持插入顺序的Map, 应该用哪个实现类?

如果需要保持插入顺序的 Map,可以使用 Java 提供的 LinkedHashMapLinkedHashMap 继承自 HashMap,它在内部使用双向链表维护了插入顺序。

LinkedHashMap 中,每个 Entry(键值对)都连接着上一个和下一个 Entry,这样就形成了一个链表。这个链表定义了键值对的顺序,即按照插入顺序或者访问顺序(LRU 最近最少使用策略)来遍历 Map 的元素。

因此,LinkedHashMap 可以保持插入元素的顺序,遍历时会按照插入的顺序返回键值对。

Java中常见的 io 流?

数据结构划分:字节流、字符流 功能上划分:输入流、输出流 阻塞和非阻塞角度Java中分为: 1).BIO:BloCk IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。 2).NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。 3).AIO:AsynChronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

 线程安全集合有哪些(怎么得到线程安全的list)

在 Java 中,可以通过以下几种方式获取线程安全的集合:

  1. 使用 Collections.synchronizedList 创建线程安全的 List

    List synchronizedList = Collections.synchronizedList(new ArrayList<>());

    这种方式会返回一个线程安全的 List,它对每个方法调用都进行了同步处理。但需要注意,虽然可以保证线程安全,但在高并发情况下性能可能会有所下降。

  2. 使用 java.util.concurrent 包下的并发集合类: Java 并发包提供了许多线程安全的集合类,如 CopyOnWriteArrayListConcurrentLinkedQueue。这些集合类通常在并发环境下表现更好,并且提供更高的并发性能。

    • CopyOnWriteArrayList:适用于读多写少的场景,在写入时会复制一个新的数组,读取不会阻塞,不会抛出并发修改异常。

    List copyOnWriteList = new CopyOnWriteArrayList<>();
  3. 使用 java.util.Collections.synchronizedXXX 创建其他线程安全的集合: 除了 Collections.synchronizedList,还有 synchronizedSetsynchronizedMap 等方法,可以用于创建线程安全的 Set 和 Map。

这些线程安全的集合类可以在多线程环境中安全地被多个线程访问,但需要注意,虽然它们提供了一定程度的线程安全性,但在并发量较高的情况下,仍需谨慎处理并发问题,以确保线程安全性和性能。

你可能感兴趣的:(#,后端,java,开发语言,面试)