简介:
本期文章将汇总java数据结构相关的高频面试题,给最近需要找工作的朋友们总结一波,帮助大家全面掌握数据结构的核心知识点,提升竞争力,为你的面试之旅保驾护航!
说一下树的分类和理解
树是⼀种数据结构,它是由n(n>=1)个有限结点组成⼀个具有层次关系的集合。
1. ⼆叉树:
树中包含的各个节点的度不能超过 2 ,也就是任意节点最多只有两个分叉的树
①. ⼆叉排序树(⼆叉查找树)
⼆叉查找树是⼆叉树的衍⽣概念,也称为⼆叉搜索树
②. 平衡⼆叉树(也叫AVL树)
当且仅当任何节点的两棵⼦树的⾼度差不⼤于1的⼆叉树就是平衡树
③. 红⿊树(也叫RB树)
红⿊树也是⼀种⾃平衡⼆叉树,在平衡⼆叉树的基础上每个节点⼜增加了⼀个颜⾊的属性,节 点的颜⾊只能是红⾊或⿊⾊
2. 多叉树
①. B - Tree (读音是B树,并非b减树)
是⼀种⾃平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插⼊数据及删除的动作,都在对数时间内完成。
②. B + Tree
B+树就是基于B树的变种,多⽤数据库和⽂件系统,B+树上的叶⼦结点存储关键字以及相应记录的地址,叶⼦结点以上各层作为索引使⽤, Mysql的索引就是基于B+树实现的
b-tree和b+tree的区别
B+树就是基于B树的变种
B+树相对于B树的优势
每个节点存储更多的KEY,树的⾼度更低,时间复杂度越⼩,查询越快
数据在叶⼦节点,每次查询都要查询到叶⼦节点,查询速度⽐较稳定
叶⼦节点构成构成了链表结构,⽅便区间查询和排序
说一下ES用到了什么数据结构
ES是使用了数据索引存储结构,它是通过为关键字建立索引,通过索引找到对应的数据,这种索引也叫倒排索引,可以实现快速检索
Mysql为什么使用B+树:
以最小的IO找到更多的记录数,如果是B树,由于每个节点要存储Key和Value(数据),那么每个节点能存储的Key是很少的,
而B+树每个节点只存储Key,它可以存储更多的Key, 每个节点 存储的Key越多,路数越多,节点就越少,需要耗时的IO就越少,查找性能就越⾼,
且B+树 的叶⼦节点是有序的,形成链表,⽅便区间查询和排序
List和Set和Map的区别
List:
一个有序容器(元素存入集合的顺序和取出的顺序一致),元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
Set:
一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
Map:
是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap。
集合与数组区别
1、数组声明了它容纳的元素的类型,而集合不声明。
2、数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
3、数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
4、数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
说一下Java中的集合体系
Collection接口
List: Collection的子接口, 元素可以重复, 而且是有序的
ArrayList:底层数据结构是数组,查询性能高,增删性能低
LinkedList:底层数据结构是双向链表,查询性能低,增删性能高
Vector:底层数据结构是数组,查询性能高,增删性能低
Set:Collection的子接口, 元素不可重复, 而且无序
HashSet:无序不重复的,使用HashMap的key存储元素,判断重复依据是hashCode()和equals()
linkedHashSet: 有序不重复,底层是哈希表+链表
TreeSet:有序不重复的,底层使用TreeMap的key存储元素,排序方式分为自然排序,比较器排序
Map接口: Map是存放键值对(Entry: key value) 的集合
HashMap:key的值没有顺序,线程不安全
TreeMap:key的值可以自然排序,线程不安全
HashTable:它的key和value都不允许为null,线程安全
Properties:它的key和value都是String类型的,线程安全
HashMap和HashTable的区别
HashMap:key的值没有顺序,线程不安全, 允许空值空键, 初始值容量16, 扩容2倍
HashTable:它的key和value都不允许为null,线程安全, 初始值容量11, 扩容为2n+1
HashMap性能高于HashTable
ArrayList和LinkedList区别
ArrayList:底层数据结构是数组,查询性能高,增删性能低
LinkedList:底层数据结构是双向链表,查询性能低,增删性能高
ArrayList和Vector区别
Vector是ArrayList的线程安全,
所以ArrayList的性能比Vector高
ArrayList的自动扩容是1.5倍, Vector是2倍
一个User的List集合,如何实现根据年龄排序?
使用Collections.sort()方法,传入user对象和重写Comparator定制比较器
Comparator方法返回使用包装类的compare()方法的结果即可
// 使用Collections.sort()方法和自定义的Comparator来对集合进行排序
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return Integer.compare(u1.getAge(), u2.getAge());
}
});
哪些集合类是线程安全的
Vector
HashTable
ConcurrentHashMap
HashMap底层用到了那些数据结构?
JDK1.7及其之前:数组,链表 ;
JDK1.8开始:数组,链表,红黑树;
什么是Hash冲突
哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:
拉链法,把哈希碰撞的元素指向一个链表
开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突
再哈希法(再散列法),就是换一种哈希算法重来一次
建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表
HashMap为什么要用到链表结构
当我们向HashMap中添加元素时,会先根据key进行哈希运算,把hash值模与数组长度得到一个下标,然后将该元素添加进去。
但是如果产生了哈希碰撞,也就是不同的key计算出了相同的hash值,这就出问题了,因此它采用了拉链法来解决这个问题,将产生hash碰撞的元素,挂载到链表中
HashMap为什么要用到红黑树
当HashMap中同一个索引位置出现哈希碰撞的元素多了,链表会变得越来越长,查询效率会变得越来越慢。
因此在JDK1.8之后,当链表长度超过8个,会将链表转为红黑树来提高查询
HashMap链表和红黑树在什么情况下转换的?
当链表的长度大于等于8,同时数组的长度大于64,链表会自动转化为红黑树
扩容后当树中的节点数小于等于6,红黑树会自动转化为链表
HashMap在什么情况下扩容?HashMap如何扩容的?
HashMap的数组初始容量是16,负载因子是0.75,大于0.75会成倍扩容
成倍扩容:需要保证数组的长度是2的整数次幂
数组的长度必须是2的整数次幂:由于计算机的运算效率,加减法>乘法>除法>取模,取模的效率是最低的。将取模运算转化成了与运算,即数组长度减1的值和hash值的与运算,以此来优化性能。
HashMap是如何Put一个元素的
首先,将key进行hash运算,将这个hash值与上当前数组长度减1的值,计算出索引。
此时判断该索引位置是否已经有元素了,如果没有,就直接放到这个位置。
如果这个位置已经有元素了,也就是产生了哈希碰撞,那么判断旧元素的key和新元素的key的hash值是否相同,并且将他们进行equals比较,如果相同证明是同一个key,就覆盖旧数据,并将旧数据返回。
如果不相同的话,再判断当前桶是链表还是红黑树,如果是红黑树,就按红黑树的方式,写入该数据。
如果是链表,就依次遍历并比较当前节点的key和新元素的key是否相同,如果相同就覆盖,如果不同就接着往下找,直到找到空节点并把数据封装成新节点挂到链表尾部。
然后需要判断,当前链表的长度是否大于转化红黑树的阈值,如果大于就转化红黑树,最后判断数组长度是否需要扩容。
HashMap是如何Get一个元素的
首先将key进行哈希运算,计算出数组中的索引位置,判断该索引位置是否有元素,如果没有,就返回null,如果有值,判断该数据的key是否为查询的key,如果是就返回当前值的value
如果第一个元素的key不匹配,判断是红黑树还是链表,如果是红黑树,就就按照红黑树的查询方式查找元素并返回,如果是链表,就遍历并匹配key,让后返回value值
你知道HahsMap死循环问题吗
HashMap在扩容数组的时候,会将旧数据迁徙到新数组中,这个操作会将原来链表中的数据颠倒,比如a->b->null,转换成b->a->null
这个过程单线程是没有问题的,但是在多线程环境,就可能会出现a->b->a->b....,这就是死循环
在JDK1.8后,做了改进保证了转换后链表顺序一致,死循环问题得到了解决。但还是会出现高并发时数据丢失的问题,因此在多线程情况下还是建议使用ConcurrentHashMap来保证线程安全问题
说一下你对ConcurrentHashMap的理解
ConcurrentHashMap,它是HashMap的线程安全,支持高并发的版本
在jdk1.7中,它是通过分段锁的方式来实现线程安全的。意思是将哈希表分成许多片段Segment,而Segment本质是一个可重入的互斥锁,所以叫做分段锁。
在jdk1.8中,它是采用了CAS操作和synchronized来实现的,而且每个Node节点的value和next都用了volatile关键字修饰,保证了可见性
结语
🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~