一、说说&和&&的区别?
作为运算符:&将二进制的每一位进行与运算
作为逻辑运算符:两者都是与,&&如果左边为假则终止右边运算,即短路运算。&则需要把两边的比较执行完。
二、int和Integer的区别
int是Java的基本数据类型,而Integer是int的包装类
int直接存储整数值,而Integer是一个对象,包含了一些额外的方法和功能
int的默认值是0,而Integer的默认值是null
扩展:那么我们在判断这两个类型是否相等的时候有什么要注意的呢?
(1)int和int类型的比较可以直接用==判断
(2)int和Integer类型的比较时也可以用==、Integer会自动拆箱为int
(3)Integer和Integer类型的比较中。如果数值范围在[-128,127]之间可以用==。其他范围只能用equals方法、原因是:JVM会
维护这个范围内的缓存,比如第一个Integer是127,会存放在缓存中;在创建第二个Integer时会直接返回缓存的127,所以两者是相等的。
三、接口和抽象类的区别
1.语法层面
(1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract(隐式声明)方法(JDK8默认方法);
(2)抽象类中成员变量可以是各种类型的,而接口中的成员变量只能是public static final(隐式声明)类型的(必须在声明时赋值);
(3)抽象类可以有静态代码块和静态方法,接口中不能含有静态代码块以及静态方法
(4)一个类只能继承一个抽象类,而一个类却可以实现多个接口
2. 设计层面
(1)抽象类:是对整个类进行抽象,包括属性,行为(方法),那么一定是抽象类的种类(拥有同一种属性或行为的类)
(2)接口:是对行为的抽象
(3)抽象类是一种模板设计,接口是一种规范。
3.怎么选择
(1)如果拥有一些方法,并想让他们中的一些有默认的具体实现,请选择抽象类
(2)如果想实现多重继承,那么请使用接口,由于java不支持多继承,子类不能继承多个类,但一个类可以实现多个接口,因此可以使用接口来解决
(3)如果基本功能在不断变化,那么就使用抽象类,如果使用接口,那么每次变更都需要相应的去改变实现该接口的所有类
4. JDK8中为什么会在接口中提供默认方法?
目的:用来减少抽象类和接口的差异,可以在接口中提供默认的实现方法并实现该接口的类不用强制去实现这个方法。JDK8中接口的静态方法只能通过接口名直接去调用,接口中的默认方法因为不是abstract的,所以可重写,也可以不重写。
四、谈谈你对树的理解(数据结构)?
1.树的由来
数组:检索效率高
链表:添加删除效率高
树:综合检索和操作的效率
2.树的分类
根据不同的分类方式,树可以分为不同的类型
(1)根据树分支的数量限制: 可以分为二叉树和多叉树。二叉树最多只有两个子节点,而多叉树一个节点可以有多于两个的子节点
(2)根据树节点的有序性:可以分为查找树和无序树。查找树的基本特征为任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。无序树则没有特定的键值大小关系。
(3)根据具体用途和特征:例如红黑树、AVL树、平衡二叉树、平衡二叉搜索树。红黑树是一种自平衡二叉查找树,AVL树也是一种自平衡二叉查找树,它要求任何节点的两个子树的高度差最大为1。平衡二叉树和平衡二叉搜索树则是为了平衡树的左右子树的高度差。
(4)根据树的完整性和是否包含空值:可以分为完全二叉树、满二叉树、完全二叉搜索树、满二叉搜索树等。完全二叉树和满二叉树是包含所有节点的二叉树,而完全二叉搜索树和满二叉搜索树则是所有节点都按照一定顺序排列得二叉搜索树。
3.常见树的特点
(1)普通二叉查找树:倾斜的问题
(2)AVL自平衡:左旋右旋开销问题
(3)红黑树:黑节点平衡、深度问题、二叉。作为内存中数据存储
(4)B树和B+树:多路查找、深度比较少、一般用于大规模数据存储、索引
4.TreeMap和HashMap的区别
TreeMap: 本质就是红黑树的实现
HashMap:以jdk8为例。就是通过 数组+链表+红黑树+算法实现的
通过上面的实现也可以看到HashMap综合的读写效率要比TreeMap高了
五、HashMap面试汇总
1.jdk8为什么引入了红黑树
因为链表长度增加后检索的效率急剧降低,复杂度是O(n)
2.解决hash冲突,为什么不直接用红黑树?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。单元素小于8个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于8个的时候,红黑树搜索时间复杂度是O(logn),而链表是O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。
3.为什么链表改为红黑树的阈值是8?
首先和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡。在负载因子0.75(HashMap默认)的情况下,单个hash槽内元素个数为8的概率小于百万分之一,将7作为一个分水岭,等于7时不做转换,大于等于8才转红黑树,小于等于6才转链表。链表中元素个数为8时概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的。
4.默认加载因子为什么是0.75
这个是从时间和空间的角度综合得出的
(1)如果是1.0 当数组的值全部填充了才会发生扩容,此时Hash冲突时避免不了的。链表的操作或者红黑树的操作会牺牲时间来保证空间的利用率
(2)如果是0.5 当数组中一半的数据利用了之后就会开始扩容。这时填充的数据少。hash冲突也会减少,底层链表和红黑树的高度也会降低。查询效率增加。但是这时还有太多的空间没有利用。空间资源浪费了。
(3)所以0.75是综合考虑得出的
5.为什么要右移16位?
static final int hash (Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
其实是为了减少碰撞,进一步降低hash冲突的几率。int类型的数值是4个字节的,右移16位异或可以同时保留高16位于低16位的特征。
当数组的长度很短时,只有低位数的hashcode值能参与运算。而让高16位参与运算可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率。并且使得高16位和低16位的信息都被保留了。
在HashMap的put方法里面,是通过key的hash值与数组的长度取模计算得到数组的位置。
而在绝大部分的情况下,n的值一般都会小于2^16次方,也就是65536。
所以也就意味着i的值,始终是使用hash值的低16位与(n-1)进行取模运算,这个是由与运算符&的特性决定的。
这样就会造成key的散列度不高,导致大量的key集中存储在固定的几个数组位置,很显然会影响到数据查找性能。
6. 为什么Hash值要与length-1相与
把hash值对数组长度取模运算,模运算的消耗很大,没有位运算快。
当length总是2的n次方时,h&(length-1)运算等价于length取模,也就是h%length,但是&比%具有更高的效率。
7.介绍下put方法的流程
(1)首先根据key的值计算hash值,找到该元素在数组中存储的下标;
(2)如果数组是空的,则调用resize进行初始化;
(3)如果没有哈希冲突直接放在对应的数组下标里;
(4)如果冲突了,且key已经存在,就覆盖掉value;
(5)如果冲突后,发现该节点是红黑树,就将这个节点挂在树上
(6)如果冲突后是链表,判断该链表是否大于8,如果大于8并且数组容量小于64,就进行扩容;如果链表节点大于8并且数组的容量大于64,则将这个结构转换为红黑树;否则,链表插入键值对,若key存在,就覆盖掉value。
标签:面试题,java,--,接口,二叉,链表,int,hash,抽象类 From: https://www.cnblogs.com/northli/p/18262868