【Java面试题】Java基础——集合

集合的形式

集合可以有不同的形式,以下是一些常见的集合形式:

1. List:有序集合,元素可以重复。可以使用Collections.sort()方法对List进行排序。

2. Set:无序集合,元素不能重复。可以使用HashSet或者TreeSet等实现类。

3. Map:键值对集合,其中每个键对应唯一的值。可以使用HashMap或者TreeMap等实现类。

4. Queue:队列,按照先进先出(FIFO)的顺序存储元素。可以使用LinkedList或者ArrayDeque等实现类。

5. Stack:栈,按照后进先出(LIFO)的顺序存储元素。可以使用LinkedList或者ArrayDeque等实现类。

以上是一些常见的集合形式,每种形式都有其特定的应用场景和优缺点。在选择集合类型时,需要根据具体的需求来进行选择。

在这里插入图片描述
在这里插入图片描述

List和Set的区别

List:有序
Set:无序
List:可重复
Set:不可重复

Java中的List和Set是两个不同的接口,都是集合框架中的一部分,它们的主要区别在于它们在存储元素时数据是否可以重复。

List是一个有序的集合,它可以存储具有重复元素的多个对象。List中的元素是按照插入顺序排序的,可以通过元素索引进行访问和修改。List也提供了用于添加、删除和更新元素的多个方法,例如add、remove、set和get等。

Set是一个不允许重复元素的集合,可以确保所有元素都是唯一的,不会产生重复。Set实现了equals()和hashCode()方法,以确保集合中的元素不具有相同的内容。Set中的元素不按任何特定顺序进行排序,并且不能通过索引进行访问和修改。

因此,当需要存储具有重复元素的集合时,应使用List,而当需要存储不允许重复元素的集合时,应使用Set。综上所述,List和Set在集合元素的排序和唯一性方面具有不同的用途和实现方式。

ArrayList和LinkedList的区别

数组和链表的区别
查找:谁快?
增删改:谁快?

Java中的ArrayList和LinkedList都是List接口的实现类,它们都可以用来存储一组按顺序排列的对象。两者的区别主要在于它们底层数据结构不同,对于不同的应用场景,会存在更适合的选择。

ArrayList是基于动态数组实现的,内部维护一个可变长度的数组。因此,ArrayList支持高效的随机访问和修改,而不需要遍历整个列表。但是如果要在列表中插入或删除元素,所有元素都必须向右或向左移动,因此ArrayList在大规模数据插入或删除操作的性能上不如LinkedList。

LinkedList是基于双向链表实现的,每个节点(包含元素值和前后指针)都指向它的前一个和后一个元素。与ArrayList不同的是,LinkedList插入和删除操作的效率更高,因为它不需要移动后面的元素。但是在查找元素方面,LinkedList比ArrayList慢,因为它必须从头开始遍历整个列表,才能寻找到元素。

因此,总的来说,当需要快速随机访问列表中的元素且不需要大量插入和删除操作时,建议使用ArrayList;当需要频繁的插入和删除操作,但访问元素时顺序不那么重要时,建议使用LinkedList。

ArrayList和数组的区别

ArrayList和数组都可以用来表示一组元素,两者之间的主要区别在于动态性和灵活性。

数组是一组固定大小的连续内存块,一旦创建,其大小就无法更改。访问数组元素的速度非常快,因为它直接从内存中获取,但是当需要插入或删除元素时,需要将所有的元素依次移动,这会使得操作变得很低效。

ArrayList是一个动态数组,它可以根据需要自动增加大小。在ArrayList中,可以使用add()方法向列表中添加元素,或使用remove()方法从列表中删除元素,不需要手动调整大小。ArrayList还提供了许多其他方便的方法,例如contains()、indexOf()等,可以更方便地操作数据。

因此,与数组相比,ArrayList具有更好的灵活性和动态性。它可以根据需要自动增大或收缩,可以更方便地插入或删除元素,并且提供了许多功能强大的API。但是在访问元素时,ArrayList的性能可能不如数组,因为它需要间接引用,这会导致一定的性能损失。

ArrayList的扩容机制是什么?

ArrayList实现动态扩容的方式是通过增加一个默认的扩容因子来实现的。当ArrayList在执行add()方法时,如果插入元素后的新元素个数超过了当前列表的长度,那么ArrayList会按照以下步骤进行扩容:

  1. 计算新的容量大小,新容量大小为当前容量大小的1.5倍(即扩容因子默认为1.5)。
  2. 创建一个新的数组,长度为新的容量大小。
  3. 把原来数组中的所有元素都复制到新数组中。
  4. 把新数组设置为ArrayList内部的数组对象。

由于该实现方式涉及到数组拷贝等操作,因此在进行大数据量插入时,扩容会非常耗费性能。为了尽量避免扩容,可以在使用ArrayList时,尽量预估需要存储的元素数量,提前指定ArrayList的容量大小,避免频繁扩容。这可以通过调用ArrayList的构造方法,传入容量大小参数来实现:

ArrayList<String> list = new ArrayList<>(100); // 指定容量大小为100

默认情况下,new ArrayList初始化容量为0,存入1个元素时,首次扩容至默认值10,之后按1.5倍扩容(向下取整)

ArrayList有哪些特点

(1)ArrayList是一种变长的集合类,基于定长数组实现,使用默认构造方法初始化出来的容量是10(1.7之后都是延迟初始化,即第一次调用add方法添加元素的时候才将elementData容量初始化为10)。

(2)ArrayList允许空值和重复元素,当往ArrayList中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个更大的数组。ArrayList扩容的长度是原长度的1.5倍

(3)由于ArrayList底层基于数组实现,所以其可以保证在o(1)复杂度下完成随机查找操作。

(4)ArrayList是非线程安全类,并发环境下,多个线程同时操作ArrayList,会引发不可预知的异常或错误。

(5)顺序添加很方便

(6)删除和插入需要复制数组,性能差(可以使用LinkindList)

List和Map的区别

List和Map是Java中常用的集合类,它们有以下区别:

  1. 数据结构不同:List是一个有序的集合,它可以包含重复的元素,而Map是一个无序的键值对集合。
  2. 存储方式不同:List中的元素是对象,而Map中的元素是键值对。
  3. 访问方式不同:List中的元素可以通过索引进行访问,而Map中的元素需要通过键进行访问。
  4. 初始化方式不同:List可以使用Arrays.asList()或者ArrayList.newInstance()等方法进行初始化,而Map可以使用HashMap.newKeyValuePair()或者TreeMap.newNode()等方法进行初始化。
  5. 线程安全性不同:List不是线程安全的,如果多个线程同时对同一个List进行修改操作,可能会导致数据不一致的问题。而Map是线程安全的,可以在多线程环境下进行并发访问。
  6. 性能差异:由于List需要维护有序性,因此在插入、删除、查找等操作时需要进行比较和排序操作,这些操作会带来一定的性能开销。而Map使用哈希表来实现快速的查找操作,因此在查找操作时性能较高。

总之,List和Map都有各自的优缺点和适用场景,需要根据实际情况选择合适的集合类来进行使用。

如何让map存储有序数据

在Java中,Map是一个无序的键值对集合,如果需要存储有序的数据,可以考虑使用LinkedHashMap。

LinkedHashMap继承自HashMap,它可以记录元素的插入顺序或者访问顺序,即元素按照插入或者访问的顺序进行排序。因此,在使用LinkedHashMap时,可以保证元素的顺序与插入或访问的顺序相同。

下面是一个示例代码:

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        // 创建一个空的LinkedHashMap对象
        Map<String, Integer> map = new LinkedHashMap<>();

        // 向LinkedHashMap中添加元素,元素会按照插入顺序进行排序
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);

        // 从LinkedHashMap中获取元素,元素也会按照插入顺序进行排序
        System.out.println(map.get("A")); // 输出1
        System.out.println(map.get("B")); // 输出2
        System.out.println(map.get("C")); // 输出3
    }
}

需要注意的是,由于LinkedHashMap需要维护元素的插入顺序或者访问顺序,因此它的性能可能会比HashMap略低一些。同时,由于LinkedHashMap是线程不安全的,如果需要在多线程环境下使用,需要进行同步处理。

如何创建Map?

在Java中,创建Map可以使用以下几种方式:

1. 使用HashMap构造函数创建Map对象
Map<String, Integer> map = new HashMap<>();

这个示例代码创建了一个键类型为String,值类型为Integer的HashMap对象。需要注意的是,如果没有指定容量,HashMap会根据元素数量自动调整容量大小。

1. 使用Collections.singletonMap()方法创建Map对象
Map<String, Integer> map = Collections.singletonMap("key", "value");

这个示例代码创建了一个只包含一个键值对的Map对象,其中键为"key",值为"value"。需要注意的是,该方法返回的是一个不可变的Map对象。

1. 使用Map.putAll()方法创建Map对象
Map<String, Integer> map = new HashMap<>();
map.putAll(new HashMap<>());


这个示例代码创建了一个空的HashMap对象,并使用Map.putAll()方法将另一个Map对象中的键值对添加到当前Map对象中。需要注意的是,该方法是线程安全的。

1. 使用Stream API创建Map对象
Map<String, Integer> map = Stream.of(new AbstractMap.SimpleEntry<>("key", "value"))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));


这个示例代码使用Stream API创建了一个键类型为String,值类型为Integer的Map对象。需要注意的是,该方法返回的是一个临时性的Map对象,如果需要将其转换为真正的Map对象,可以使用toMap()方法进行转换。

常用的Map有哪些?

在Java中,常用的Map有以下几种:

1. HashMap

HashMap是最常用的Map类型之一,它是基于哈希表实现的。它提供了快速的插入、删除和查找操作,并且支持null键和null值。但是,由于哈希表的结构不稳定,如果发生哈希冲突,会导致链表扩容,从而影响性能。

2. LinkedHashMap

LinkedHashMap是继承自HashMap的一种Map类型,它维护了元素的插入顺序或者访问顺序。因此,在使用LinkedHashMap时,可以保证元素的顺序与插入或访问的顺序相同。但是,由于它需要维护链表结构,所以它的性能可能会比HashMap略低一些。

3. TreeMap

TreeMap是一种基于红黑树实现的有序Map类型。它提供了按照自然顺序或者自定义排序方式进行排序的能力。由于它是基于红黑树实现的,所以它的性能相对较高。但是,由于它的遍历方式比较特殊,所以对于某些场景可能不太适用。

如何在HashMap中插入一个数据

HashMap的put方法

将指定的值与此映射中的指定键相关联,如果Map中已经包含了该键的映射,那么旧的映射值将会被替代,也就是说在put时,如果map中已经包含有key所关联的键值对,那么后续put进来的键值对,将会以相同key为准替换掉原来的那一对键值对。

返回的值则将是之前在map中实际与key相关联的Value值(也就是旧的值),如果key没有实际映射值的话那就返回null。

内部实现时则立马调用了其内部putValue方法,并将put进去(覆盖)之前的结果k-v中的v进行了返回,进行值的覆盖

// 创建一个 HashMap
HashMap<Integer, String> map = new HashMap<>();
// 往 HashMap 添加一些元素
map.put(1, "11");
map.put(2, "22");
map.put(3, "33");
System.out.println("HashMap: " + map);//HashMap: {1=11, 2=22, 3=33}
//如果存储的key已经存在,则直接覆盖数据
map.put(1,"44");
System.out.println("HashMap: " + map);//HashMap: {1=44, 2=22, 3=33}

总结

在 HashMap 中,元素是以键值对的形式存储的,可以通过调用 put(key, value) 方法将元素存储到 HashMap 中。具体的操作流程如下:
1.创建一个新的键值对(Entry)对象,将要存储的键和值作为参数传入。
2.对键进行哈希运算,以得到在底层数组中的位置(桶)。
3.如果该位置为空,则将新的键值对放入该位置。
4.如果该位置已经存在键值对,则进行链式存储。新的键值对将成为链表头部,原有键值对将成为链表的后继节点。
5.如果链表长度达到一个阈值(默认为8),则会将链表转化为红黑树结构,以提高查找效率。
6.如果键已经存在于 HashMap 中,则会将原有的值替换为新的值。

遍历一个 List 有哪些不同的方式?

  1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
  2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
  3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
System.out.println("-----------forEach遍历-------------");
list.parallelStream().forEach(k -> {
  System.out.println(k);
});

System.out.println("-----------for遍历-------------");
for (Student student : list) {
  System.out.println(student);
}

System.out.println("-----------Iterator遍历-------------");
Iterator<Student> iterator = list.iterator();
while (iterator.hasNext()) {
  System.out.println(iterator.next());
}

List集合和Map的有序无序以及重复问题

List和Map都是Java中常用的集合类型,它们都可以存储一组有序或无序的元素。但是,它们在有序和无序以及重复元素方面有一些不同点。

1.List集合的有序性

List集合可以是有序的,也可以是无序的。如果需要保持元素的有序性,可以使用Collections.sort()方法对List进行排序;如果不需要保持有序性,可以使用ArrayList或者LinkedList等实现类。

2.Map集合的有序性

Map集合中的元素是无序的,因为Map是通过键值对来存储数据的,键值对的顺序是不固定的。如果需要按照键名对Map进行排序,可以使用TreeMap或者LinkedHashMap等实现类。

3.List集合的重复元素问题

List集合可以存储重复元素,但是不能保证所有重复元素都会被保留下来。如果需要保留重复元素,可以使用Set集合。

4.Map集合的重复元素问题

Map集合不允许存储重复的键值对,如果尝试插入重复的键值对,会抛出IllegalArgumentException异常。如果需要存储重复的键值对,可以使用HashMap或者ConcurrentHashMap等实现类。需要注意的是,由于HashMap是非线程安全的,所以在多线程环境下使用时需要进行同步处理。

HashMap和TreeMap区别? ★★★

HashMap和TreeMap都是Java中常用的集合类型,它们都可以存储一组有序或无序的元素。但是,它们在实现方式、性能以及使用场景等方面有一些不同点。

1.实现方式

HashMap是基于哈希表实现的,它通过哈希函数将键值对映射到数组的某个位置上,从而实现快速的插入、删除和查找操作。由于哈希表的结构不稳定,如果发生哈希冲突,会导致链表扩容,从而影响性能。

TreeMap是基于红黑树实现的,它通过维护红黑树结构来保证元素的有序性。由于它是基于红黑树实现的,所以它的性能相对较高。但是,由于它的遍历方式比较特殊,所以对于某些场景可能不太适用。

2.性能

在单线程环境下,HashMap的性能比TreeMap要好一些,因为HashMap可以通过哈希函数快速定位元素的位置。但是,在多线程环境下,由于哈希冲突的存在,HashMap的性能会受到影响。

在多线程环境下,TreeMap的性能比HashMap要好一些,因为它可以通过红黑树的性质保证元素的有序性。但是,由于TreeMap需要进行遍历操作,所以在并发访问量较大的情况下,可能会出现性能瓶颈。

3.使用场景

HashMap适用于需要快速插入、删除和查找元素的场景,例如缓存、计数器等。由于HashMap的性能较好,所以在这些场景下使用HashMap可以获得较好的性能表现。

TreeMap适用于需要保持元素有序性的场景,例如按照时间顺序排序或者按照某个属性排序等。由于TreeMap的性能较差,所以在这些场景下使用TreeMap可能会导致性能瓶颈。

总之,HashMap和TreeMap都有各自的优缺点和适用场景,需要根据具体的需求来进行选择。

为什么1.8中引入红黑树?

当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn);

简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

  1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
  2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
  3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

HashMap的底层原理你了解哪些

参考视频:https://www.bilibili.com/video/BV1nJ411J7AA

1.什么是Hash

在说明这个题目之前,咱们可能需要先理解什么是Hash?

hash是一个函数,该函数中的实现就是一种算法,就是通过一系列的算法来得到一个hash值。

这个时候,我们就需要知道另一个东西,hash表,通过hash算法得到的hash值就在这张hash表中,也就是说,hash表就是所有的hash值组成的,有很多种hash函数,也就代表着有很多种算法得到hash值,如上面截图的三种,等会我们就拿第一种来说。

1.直接寻址法。取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)

2.数字分析法。分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

3.平方取中法。取关键字平方后的中间几位作为散列地址。

4.折叠法。将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

5.随机数法。选择一随机函数,取关键字作为随机函数的种子生成随机值作为散列地址,通常用于关键字长度不同的场合。

6.除留余数法。取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生碰撞。

HashMap的初始值为16

2.那么什么是Map?

Map是 Java 中的一个接口,它表示映射表,即一种将键映射到值的数据结构。在 Map 中,每个键最多只能映射到一个值。常见的实现类包括 HashMap,TreeMap和 LinkedHashMap。 Map接口提供了一系列方法来操作映射表,例如 put()用于添加键值对,get()用于获取指定键所对应的值等。
在这里插入图片描述

3.什么是HashMap?

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。另外,HashMap是非线程安全的,也就是说在多线程的环境下,可能会存在问题,而Hashtable是线程安全的。

4.HashMap是如何实现的?

HashMap底层实现JDK<1.8数组+链表 JDK>1.8数组+链表+红黑树;

当链表的长度大于8时,并且数组长度大于64的时候,自动升级为红黑树

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wei *

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

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

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

打赏作者

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

抵扣说明:

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

余额充值