【Java面试】第四天

在这里插入图片描述

🌟个人主页:时间会证明一切.


ArrayList、LinkedList与Vector的区别?

List主要有ArrayList、LinkedList与Vector几种实现。这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组。

LinkedList 是一个双向链表,在添加和删除元素时具有比ArrayList更好的性能,但在get与set方面弱于ArrayList。当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义。

Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%。

而 LinkedList 还实现了Queue和Deque接口,该接口比List提供了更多的方法,包括offer(),peek(),poll()等。

注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

ArrayList 是如何扩容的?

首先,我们要明白ArrayList是基于数组的,我们都知道,申请数组的时候,只能申请一个定长的数组,那么List是如何通过数组扩容的呢?ArrayList的扩容分为以下几步:

  1. 检查新增元素后是否会超过数组的容量,如果超过,则进行下一步扩容
  2. 设置新的容量为老容量的1.5倍,最多不超过2^31-1 (Java 8中ArrayList的容量最大是Integer.MAX_VALUE - 8,即2^31-9。这是由于在Java 8中,ArrayList内部实现进行了一些改进,使用了一些数组复制的技巧来提高性能和内存利用率,而这些技巧需要额外的8个元素的空间来进行优化。)
  3. 之后,申请一个容量为1.5倍的数组,并将老数组的元素复制到新数组中,扩容完成

Set是如何保证元素不重复的

在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。

  1. TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值;底层基于TreeMap
  2. HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束;底层基于HashMap

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。

HashSet,TreeSet,LinkedHashSet,BitSet有何区别

  1. 功能不同:HashSet是功能最简单的Set,只提供去重的能力;LinkedHashSet不仅提供去重功能,而且还能记录插入和查询顺序;TreeSet提供了去重和排序的能力;BitSet不仅能提供去重能力,同时也能减少存储空间的浪费,不过对于普通的对象不太友好,需要做额外处理
  2. 实现方式不同:HashSet基于HashMap,去重是根据HashCode和equals方法的;LinkedHashSet是基于LinkedHashMap,通过双向链表记录插入顺序;TreeSet是基于TreeMap的,去重是根据compareTo方法的;BitSet基于位数组,一般只用于数字的存储和去重
  3. 其实BitSet只是叫做Set而已,它既没有实现Collection接口,也和Iterable接口没有什么关系,但是是名字相似而已

什么是BitSet?有什么作用?

顾名思义,BitSet是位集合,通常来说,位集合的底层的数据结构是一个bit数组,如果第n位为1,则表明数字n在该数组中。
举个例子,如果调用BitSet#set(10),业务语意是把10放到BitSet中,内部的操作则是通过把二进制的第十位(低位)置为1。这样,就代表BitSet中包含了10这个数字。
不过,对于Java中的BitSet来讲,因为Java不知道bit类型,所以它的底层结构并不是一个bit类型数组,但是也不是一个byte类型数组,而是一个long类型的数组,这样设置的目的是因为long有64位,每次可以读取64位,在进行set或者or操作的时候,for循环的次数会更少,提高了性能。

它最大的好处就是对于多个数字来说,降低了存储空间,如正常情况下,将每一个int类型(32bit)的数字存储到内存中需要 4B * (2^31-1) = 8 GB,但是如果用BitSet的话,就会节省到原来的1/32。

一个整型占4个字节,一共有2^31-1个。

BitSet常见的使用例子往往和大数相关:

  1. 现在有1千万个随机数,随机数的范围在1到1亿之间。求出将1到1亿之间没有在随机数中的数
  2. 统计N亿个数据中没有出现的数据
  3. 将N亿个不同数据进行排序等

但是BitSet也有缺点,譬如集合中存储一些差值比较大的数,如1亿和1两个数,就会导致内存的严重浪费

你能说出几种集合的排序方式?

Java.util包中的List接口继承了Collection接口,用来存放对象集合,所以对这些对象进行排序的时候,要么让对象类自己实现同类对象的比较,要么借助比较器进行比较排序。

举例:学生实体类,包含姓名和年龄属性,比较时先按姓名升序排序,如果姓名相同则按年龄升序排序。

实现Comparable

第一种:实体类自己实现Comparable接口比较

public class Student implements Comparable<Student>{ 
    private String name; 
    private int age; 
    @Override 
    public int compareTo(Student o) {
        int flag = this.name.compareTo(o.name); 
        if(flag == 0) { 
        	flag = this.age - o.age; 
        } 
        return flag; 
    } 
}
Collections.sort(students);

借助Comparator

第二种:借助比较器进行排序。

public class Student { 
    private String name; 
	private int age; 
}
Collections.sort(students, (o1, o2) -> {
	int flag = o1.getName().compareTo(o2.getName()); 
    if(flag == 0) { 
        flag = o1.getAge() - o2.getAge(); 
    } 
    return flag; 
}); 

也可以直接优化成:

Collections.sort(students, Comparator.comparing(Student::getName).thenComparingInt(Student::getAge));

通过Stream

第三种:借助Stream进行排序,借助Stream的API,底层还是通过Comparable实现的。

public class Student { 
    private String name; 
	private int age; 
}
// 如果Student实现了Comparable
students.stream().sorted().collect(Collectors.toList());
// 如果Student没有实现Comparable

students.stream().sorted((o1, o2) -> {
	int flag = o1.getName().compareTo(o2.getName()); 
    if(flag == 0) { 
        flag = o1.getAge() - o2.getAge(); 
    } 
    return flag; 
}).collect(Collectors.toList());

知识扩展

有了Comparable为什么还需要Comparator?

Comparable用于使某个类具备可排序能力。如之前的Student类,实现该接口后覆盖其compareTo方法,即可具备可排序的能力。
但是仍然存在一些二方库的类没有实现Comparable,但是调用方也需要比较的,此时就需要使用Comparator接口。
Comparator是一个比较器接口,可以用来给不具备排序能力的对象进行排序。如上述代码中对不具备排序能力的Student进行排序

compareTo和equals的使用场景有何区别?

compareTo常用于排序和BigDecimal等数值的比较
equals则是常用于业务语义中两个对象是否相同,如String常常通过equals来比较是否字面意义相同

既然Set是无序的,还怎么排序?

这里说的是两个语境的不同,Set的无序,指的是插入顺序是无序的。虽然Set的插入顺序是无序的,Set也可以基于SortedSet要求对象实现Comparable来对Set中的元素进行排序。

Set真的是插入无序的吗?

并不是,Set有一个实现类是LinkedHashSet,它引用LinkedHashMap,通过双向链表记录了每个node的插入顺序和查询顺序(可选),以此来达到Set的插入有序性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值