Java
重载和重写的区别
这两个都是多态的一种表现形式。
重载是在编译器通过方法中形参的静态类型确定调用方法版本的过程,是多态在编译期的表现形式。判定只有两个条件:1. 方法名一致 2. 形参列表不同
重写是在方法运行时,通过调用者的实际类型来确定调用的方法版本。即子父类中的重写方法在对应的class文件常量池的位置相同,一旦子类没有重写,那么子类的实例就会沿着这个位置往上找,直到找到父类的同名方法。
重写只发生在可见的实例方法中,静态方法不存在重写,形式上的重写只能说是隐藏。私有方法也不存在重写,父类中private
的方法,子类中就算定义了,也是一个新的方法。静态方法和实例方法不存在相互重写。
重写满足一个规则:两同两小一大。两同指方法名和形参列表一致,两小指重写方法的返回值(引用类型)和抛出异常,要和被重写方法的返回值(引用类型)和抛出异常相同或者是其子类。如果返回值是基本数据类型,那么重写方法和被重写方法必须相同,且不存在自动拆装箱的问题。一大是指重写方法的访问修饰符大于等于被重写方法的访问修饰符。
final,finally和finalize的区别
finally 是一个关键字,它经常和 try 块一起使用,用于异常处理。使用 try…finally 的代码块中,finally 部分的代码一定会被执行,所以我们经常在 finallv 方法中用于资源的关闭操作。
JDK1.7 中,推荐使用 try-with-resources 优雅的关闭资源,它直接使用 try0 进行资源的关闭即可,就不用写 finally 关键字了。finalize 是 Object 对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java9 中,将 finalize 标记为了 deprecated,如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。
内部类有哪些分类
内部类有四种:成员内部类,局部内部类,匿名内部类,静态内部类。
静态内部类是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,不可访问外部类的非静态变量。
成员内部类是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法。包括静态和非静态,私有和公有。
局部内部类是定义在方法中的内部类。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
匿名内部类是没有名字的内部类,必须继承一个抽象类或者实现一个接口,不能定义任何静态成员和静态方法。当所在的方法的形参需要被匿名内部类使用时,必须声明为final。匿名内部类不能是抽象的,必须实现继承的类或者实现的接口的所有抽象方法。
反射的基本原理和反射创建类实例的三种方式
反射机制就是使 Java 程序在运行时具有自省(introsp ect) 的能力,通过反射我们可以直接操作
类 和对象,比如获取某个类的定义,获取类的属性和方法 构造方法等。
创建类实例的三种方式是
⚫ 对象实例.getClass();
⚫ 通过 ClassforName()创建
⚫ 对象实例.newInstance)方法创建
ArrayList和Vector的区别
ArrayList是List的主要实现类,底层用Object[]
存储,线程不安全。Vector是List的古老实现类,底层使用Object[]
存储,线程安全。
ArrayList和LinkedList区别
ArrayList
和LinkedList
都不保证线程安全ArrayList
底层使用Object
数组,LinkedList
底层使用双向链表,jdk1.6之前为循环链表,jdk1.7之后取消了循环。ArrayList
支持快速随机访问,LinkedList
不支持ArrayList
的空间浪费主要体现在list的列表会预留一定容量的空间,而LinkedList
的空间花费主要体现在它的每一个元素都要消耗比ArrayList
更多的空间。
Comparable和Comparator的区别
Comparable
接口是java.lang
包的接口,可实现compareTo(Object obj)
用于排序。Comparator
接口是java.util
包的接口,可实现compare(Object obj1, Object obj2)
用来排序。
Collection和Collections的区别
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。
Collections 则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
无序性和不可重复性的含义
无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定。
不可重复性是指添加的元素按照equals()
判断时,返回false
,需要同时重写equals()
方法和hashcode()
方法。
比较HashSet,LinkedHashSet和TreeSet三者的异同
HashSet
是Set接口的主要实现类,底层是HashMap
,线程不安全,可以存储null值。LinkedHashSet
是HashSet
的子类,能按照添加的顺序遍历。TreeSet
底层使用红黑树,元素是有序的,排序方式有自然排序和定制排序。
Queue与Deque的区别
Queue
是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循先进先出规则,它扩展了Collection
的接口。Deque
是双端队列,在队列两端均可以插入或删除元素,在Queue
基础上增加了在队首和队尾进行插入和删除的方法。
请谈一下对PriorityQueue的认识
PriorityQueue
是在jdk1.5中被引入,与Queue
的区别是PriorityQueue
总是优先级最高的元素先出队。它利用了二叉堆的数据结构实现,底层使用可变长的数组来存储数据。通过堆元素的上浮和下沉,实现了在O(logn)的时间复杂度内插入元素和删除堆顶元素。它是非线程安全的,且不支持存储null和non-Comparable的对象。它默认是小顶堆,但可以接收一个Comparator作为构造参数,从而自定义元素优先级的先后。
HashMap和Hashtable的区别
HashMap
是非线程安全的,Hashtable
是线程安全的。HashMap
比Hashtable
效率高HashMap
可以存储null的key和value,但null作为键只能有一个,null作为值可以有多个。Hashtable
不允许有null键和null值,否则抛出NullPointerException
- 创建时如果不指定容量的初始值,
Hashtable
初始大小为11,每次扩容变为2n+1,HashMap
初始大小为16,每次扩容变为2n。如果指定容量初始值,Hashtable
会直接使用初始值而HashMap
会扩充到2的幂次方
HashSet如何检查重复
比较对象的hashcode值和调用equals()
方法,如果两者均相同,则视为重复元素。在openjdk8中,hashSet的add方法只是简单的调用了HashMap的put方法,是否存在相同元素会在add方法的返回值处体现。
HashMap的长度为什么是2的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做
对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。
fail-fast和fail-safe
fail-fast 是 Java 中的一种 快速失败 机制,java.u til 包下所有的集合都是快速失败的,快速失败会抛出 ConcurrentModificationException 异常,fail-fast 你可以把它理解为一种快速检测机制它只能用来检测错误,不会对错误进行恢复,fail-fast 不一定只在多线程环境下存在,ArrayList 也会抛出这个异常,主要原因是由于 modCount 不等于 expectedModCount。
fail-safe 是 Java 中的一种安全失败机制,它表示的是在遍历时不是直接在原集合上进行访问,而是先复制原有集合内容,在拷贝的集合上进行遍历。 由于迭代时是对原集合的拷贝进行遍历,所以在 遍历过程中对原集合所作的修改并不能被迭代器检测到 所以不会触发 ConcurrentModificationException。java.util.conc urrent 包下的容器都是安全失败的,可以在多线程条件下使用,并发修改。
HashMap和ConcurrentHashMap的区别
ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用 lock 锁进行保护,相对于 HashTable 的 synchronized 锁的粒度更精细了一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的。(JDK1.8 之后 ConcurrentHashMap 启用了一种全新的方式实现,利用 CAS 算法。)
HashMap 的键值对允许有 null,但是 ConCurrentHashMap 都不允许。
ConcurrentHashMap和Hashtable的区别
从底层数据结构上来看:JDK1.7的ConcurrentHashMap底层采用分段的数组+链表实现,JDK1.8采用的数据结构和HashMap1.8的结构一样,数组+链表/红黑树。Hashtable是采用数组+链表的形式,数组是主体,链表是为了解决哈希冲突而存在的。
从实现线程安全的方式上看:在JDK1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。Hashtable使用synchronized来保证线程安全,当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如果使用put添加元素,另一个线程不能使用put添加元素,也不能使用get,竞争会越来越激烈,效率越来越低。
ConcurrentHashMap线程安全的具体实现方式
JDK1.7:
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。
JDK1.8:
ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为O(log(N)))synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会
产生并发,效率又提升 N 倍。
TreeMap和TreeSet在排序时如何比较元素
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进行排序。
Collection工具类的sort方法如何比较元素
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较。
第二种要求传入Comparator 接口的子类型,需重写compare方法实现元素的比较。
MySQL
索引相关
InnoDB与MyISAM索引的区别
MyISAM索引文件和数据文件是分离的,使用B+树实现,主键索引和辅助索引实现是一致的。索引文件存储记录所在页的指针,指向其物理位置。InnoDB的辅助索引data域存储相应记录主键的值而不是地址,其数据文件本身就是主索引文件。
设计模式
单例模式
class Chinese {
private static Chinese objref = new Chinese();
private Chinese() {}
public static Chinese getInstance() {
return objref;
}
}
public class TestChinese {
public static void main(String[] args) {
Chinese obj1 = Chinese.getInstance();
Chinese obj2 = Chinese.getInstance();
System.out.println(obj1 == obj2);
}
}
饿汉式单例模式实现,类一旦加载,就把单例初始化完成,保证getInstance()
时单例已经存在。懒汉式是调用getInstance()
时,才初始化这个单例。
错题知识点合集
-
要将文件中一个字符写入另一个文件首先要读入(
FileInputStream
) 到内存中去,再读出(FileOutputStream
) -
执行顺序: 父类静态代码块,静态变量->子类静态代码块,静态变量->父类局部代码块,成员变量->子类局部代码块,成员变量->子类构造函数 (按声明顺序执行)
-
session用来表示用户会话,session对象在服务端维护,一般tomcat设定session生命周期为30分钟,超时将失效,也可以主动设置无效。
-
接口中方法默认是
abstract public
,在接口中只写函数声明是符合语法规则的,但是变量默认是public final static
修饰的,表示静态常量,必须在声明时初始化。 -
Thread中默认有实现run()方法,没有重写就是调用父类的Thread的run方法。
-
java系统提供3种类加载器: 启动类加载器(Bootstrap ClassLoader),扩展类加载器,应用程序类加载器
-
SpringBoot三种配置文件加载优先级: properties > yml > yaml
-
SpringBoot的maven打包插件会将编写的class文件放在classes目录下,程序所依赖的jar包放在lib目录下,且一并打包启动jar包用的工具。在jar包描述文件
MANIFEST.MF
中指定jar的启动 类加载器类和启动类。 -
springboot配置文件层级:
-
jedis连接redis服务器是直连模式,当多线程模式下使用jedis会存在线程安全问题,解决方案可以通过配置连接池使每个连接专用,这样整体性能就大受影响。
-
lettuce是基于netty框架进行与Redis服务器连接,底层设计中采用
StatefulRedisConnection
。StatefulRedisConnection
自身是线程安全的,可以保障并发访问安全问题,所以一个连接可以被多线程复用。 -
Arrays.asList 转换完成后的 List 不能再进行结构化的的修改,即不能再进行任何 List 元素的增加或者减少的操作
-
javac.exe是编译功能javaCompiler
java.exe是执行class,如果没有编译的话是不能执行的,同理,javac.exe编译完以后如果没有java.exe执行的话也是没有运行的
参考资料
- 尚硅谷MySQL数据库高频面试题
- 黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(spring boot2完整版)