共计 65474 个字符,预计需要花费 164 分钟才能阅读完成。
前言
这篇文章是想记录本人看到的面试题, 而后做个总结. 不仅仅帮忙到我, 也心愿能够帮忙到大家. 有疑难能够分割我.
Java 根底
1. 八种根本数据类型的大小,以及他们的封装类
根本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte)0 | Byte |
short | 2 | (short)2 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | – | false | Boolean |
char | 2 | u0000(null) | Character |
void | – | – | Void |
2. 援用数据类型
援用数据类型是由类的编辑器定义的,他们是用于拜访对象的。这些变量被定义为不可更改的特定类型。
- 类对象和数组变量就是这种援用数据类型。
- 任何援用数据类型的默认值都为空。
- 一个援用数据类型能够被用于任何申明类型和兼容类型的对象。
3.Switch 是否应用 String 做参数
jdk7 之前
switch 只能反对 byte、short、char、int 这几个根本数据类型和其对应的封装类型。
switch 前面的括号外面只能放 int 类型的值,但因为 byte,short,char 类型,它们会? 主动? 转换为 int 类型(精精度小的向大的转化),所以它们也反对。
jdk1.7 后
整形,枚举类型,字符串都能够。
public class SwitchStringText {
static String string = "123";
public static void main(String[] args) {switch (string) {
case "123":
System.out.println("123");
break;
case "abc":
System.out.println("abc");
break;
default:
System.out.println("default");
break;
}
}
}
4.equals 与 == 的区别
应用 == 比拟原生类型如:boolean、int、char 等等,应用 equals()比拟对象。
1、== 是判断两个变量或实例是不是指向同一个内存空间。equals 是判断两个变量或实例所指向的内存空间的值是不是雷同。
2、== 是指对内存地址进行比拟。equals()是对字符串的内容进行比拟。
3、== 指援用是否雷同。equals()指的是值是否雷同。
public static void main(String[] args) {String a = new String("ab"); // a 为一个援用
String b = new String("ab"); // b 为另一个援用, 对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb); // true
System.out.println(a == b); // false,非同一对象
System.out.println(a.equals(b)); // true
System.out.println(42 == 42.0); // true
}
public static void main(String[] args) {Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1.equals(obj2));//false
System.out.println(obj1==obj2);//false
obj1=obj2;
System.out.println(obj1==obj2);//true
System.out.println(obj2==obj1);//true
}
5. 主动装箱,常量池
主动装箱 在 jdk1.5 之前,如果你想要定义一个 value 为 100 的 Integer 对象,则须要如下定义:
Integer i = new Integer(100);
int intNum1 = 100; // 一般变量
Integer intNum2 = intNum1; // 主动装箱
int intNum3 = intNum2; // 主动拆箱
Integer intNum4 = 100; // 主动装箱
下面的代码中,intNum2 为一个 Integer 类型的实例,intNum1 为 Java 中的根底数据类型,将 intNum1 赋值给 intNum2 便是主动装箱;而将 intNum2 赋值给 intNum3 则是主动拆箱。
八种根本数据类型:boolean byte char shrot int long float double , 所生成的变量相当于常量。
根本类型包装类:Boolean Byte Character Short Integer Long Float Double。
主动拆箱和主动装箱定义:
主动装箱是将一个 java 定义的根本数据类型赋值给相应封装类的变量。拆箱与装箱是相同的操作,主动拆箱则是将一个封装类的变量赋值给相应根本数据类型的变量。
6.Object 有哪些专用办法
Object 是所有类的父类,任何类都默认继承 Object 类
clone
爱护办法,实现对象的浅复制,只有实现了 Cloneable 接口才能够调用该办法,否则抛出 CloneNotSupportedException 异样。
equals
在 Object 中与 == 是一样的,子类个别须要重写该办法。
hashCode
该办法用于哈希查找,重写了 equals 办法个别都要重写 hashCode 办法,这个办法在一些具备哈希性能的 Collection 中用到。
getClass
final 办法,取得运行时类型
wait
使以后线程期待该对象的锁,以后线程必须是该对象的拥有者,也就是具备该对象的锁。wait() 办法始终期待,直到取得锁或者被中断。wait(long timeout) 设定一个超时距离,如果在规定工夫内没有取得锁就返回。
调用该办法后以后线程进入睡眠状态,直到以下事件产生
1、其余线程调用了该对象的 notify 办法。2、其余线程调用了该对象的 notifyAll 办法。3、其余线程调用了 interrupt 中断该线程。4、工夫距离到了。此时该线程就能够被调度了,如果是被中断的话就抛出一个 InterruptedException 异样。
notify
唤醒在该对象上期待的某个线程。
notifyAll
唤醒在该对象上期待的所有线程。
toString
转换成字符串,个别子类都有重写,否则打印句柄。
7. 浅拷贝和深拷贝
浅拷贝
被复制对象的所有变量都含有与原来的对象雷同的值,而所有的对其余对象的援用依然指向原来的对象。
换言之,浅拷贝仅仅复制所思考的对象,而不复制它所援用的对象。
深拷贝
被复制对象的所有变量都含有与原来的对象雷同的值,除去那些援用其余对象的变量。
那些援用其余对象的变量将指向被复制过的新对象,而不再是原有的那些被援用的对象。
换言之,深拷贝把要 复制的对象所援用的对象 都要复制一遍。
- 序列化
- 外部创立新对象结构就能够了
- 采纳 cloneabel 接口进行深拷贝
实现 Cloneable 接口(然而要将所有对象中的援用对象都实现 Cloneable 接口,并且在对象的 clone 办法中调用,就是层层克隆的作用, 保障所有数据都被克隆)
Java 中对象的克隆:
- 为了获取对象的一份拷贝,能够利用 Object 类的 clone()办法。
- 在派生类中笼罩基类的 clone()办法,并申明为 public。(Object 类中的 clone()办法是 Protected 的)。在子类重写的时候,能够扩充拜访修饰符的范畴。
- 在派生类的 clone()办法中,调用 super.clone()。因为在运行时刻,Object 类中的 clone()辨认出你要复制的是哪一个对象,而后为此对象调配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
- 在派生类中实现 Cloneable 接口。
8.Java 中的四种援用,强弱软虚,用到的场景
从 JDK1.2 版本开始,把对象的援用分为四种级别,从而使程序能更加灵便的管制对象的生命周期。这四种级别由高到低顺次为:强援用、软援用、弱援用和虚援用。
1. 强援用
最广泛的一种援用形式,如 String s = “abc”,变量 s 就是字符串“abc”的强援用,只有强援用存在,则垃圾回收器就不会回收这个对象。
2. 软援用(SoftReference)
用于形容还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。个别用于实现内存敏感的高速缓存,软援用能够和援用队列 ReferenceQueue 联结应用,如果软援用的对象被垃圾回收,JVM 就会把这个软援用退出到与之关联的援用队列中。
3. 弱援用(WeakReference)
弱援用和软援用大致相同,弱援用与软援用的区别在于:只具备弱援用的对象领有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具备弱援用的对象,不论以后内存空间足够与否,都会回收它的内存。
4. 虚援用(PhantomReference)
就是形同虚设,与其余几种援用都不同,虚援用并不会决定对象的生命周期。如果一个对象仅持有虚援用,那么它就和没有任何援用一样,在任何时候都可能被垃圾回收器回收。虚援用次要用来跟踪对象被垃圾回收器回收的流动。
虚援用与软援用和弱援用的一个区别在于:
虚援用必须和援用队列(ReferenceQueue)联结应用。当垃圾回收器筹备回收一个对象时,如果发现它还有虚引,就会在回收对象的内存之前,把这个虚援用退出到与之关联的援用队列中。
9.HashCode 的作用
1、HashCode 的个性
(1)HashCode 的存在次要是用于 查找的快捷性 ,如 Hashtable,HashMap 等,HashCode 常常用于 确定对象的存储地址。
(2)如果两个对象雷同,equals 办法肯定返回 true,并且这两个对象的HashCode 肯定雷同。
(3)两个对象的 HashCode 雷同,并不一定示意两个对象就雷同 ,即 equals() 不肯定为 true,只可能阐明这两个对象在一个散列存储构造中。
(4)如果对象的 equals 办法被重写,那么对象 的 HashCode 也尽量重写。
2、HashCode 作用
Java 中的汇合有两类,一类是 List,再有一类 是 Set。前者汇合内的元素是有序的,元素能够反复;后者元素无序,但元素 不可反复。
equals 办法可用于保障元素不反复 ,但如果每减少一个元素就查看一次,若汇合中当初曾经有 1000 个元素,那么第 1001 个元素退出汇合时,就要调用 1000 次 equals 办法。这显然会大大降低效率。于是,Java 采纳了 哈希表的原理。
哈希算法也称为散列算法,是将数据依特定算法间接指定到一个地址上。
这样一来,当汇合要 增加新的元素时 ,先调用这个 元素的 HashCode 办法 ,就一下子能 定位到它应该搁置的物理地位上。
(1)如果这个地位上没有元素,它就能够间接存储在这个地位上,不必再进行任何比拟了。
(2)如果这个地位上曾经有元素了,就调用它的 equals 办法与新元素进行比拟,雷同的话就不存了。
(3)不雷同的话,也就是产生了 Hash key 雷同导致 抵触的状况,那么就在这个 Hash key 的中央产生一个链表,将所有产生雷同 HashCode 的对象放到这个单链表下来,串在一起(很少呈现)。
这样一来理论调用 equals 办法的次数就大大降低了,简直只须要一两次。
从 Object 角度看,JVM 每 new 一个 Object,它都会将这个 Object 丢到一个 Hash 表中去,这样的话,下次做 Object 的 比拟或者取 这个对象的时候(读取过程),它会依据对象的 HashCode 再从 Hash 表中取这个对象。这样做的目标是 进步取对象的效率。若 HashCode 雷同再去调用 equals。
3、HashCode 实际(如何用来查找)
HashCode 是用于查找应用的,而 equals 是用于比拟两个对象是否相等的。
(1)例如内存中有这样的地位
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫 ID,我要把这个类寄存在以上 8 个地位之一,如果不必 HashCode 而任意寄存,那么当查找时就须要到这八个地位里挨个去找,或者用二分法一类的算法。
但以上问题如果用 HashCode 就会使效率进步很多 定义咱们的 HashCode 为 ID%8,比方咱们的 ID 为 9,9 除 8 的余数为 1,那么咱们就把该类存在 1 这个地位,如果 ID 是 13,求得的余数是 5,那么咱们就把该类放在 5 这个地位。依此类推。
(2)然而如果两个类有雷同的 HashCode,例如 9 除以 8 和 17 除以 8 的余数都是 1,也就是说,咱们先通过 HashCode 来判断两个类是否寄存某个桶里,但这个桶里可能有很多类,那么咱们就须要再通过 equals 在这个桶里找到咱们要的类。
请看上面这个例子
public class HashTest {
private int i;
public int getI() {return i;}
public void setI(int i) {this.i = i;}
public int hashCode() {return i % 10;}
public final static void main(String[] args) {HashTest a = new HashTest();
HashTest b = new HashTest();
a.setI(1);
b.setI(1);
Set<HashTest> set = new HashSet<HashTest>();
set.add(a);
set.add(b);
System.out.println(a.hashCode() == b.hashCode());
System.out.println(a.equals(b));
System.out.println(set);
}
}
输入后果为:
true
False
[HashTest@1, HashTest@1]
以上这个示例,咱们只是重写了 HashCode 办法,从下面的后果能够看出,尽管两个对象的 HashCode 相等 ,然而实际上 两个对象并不是相等 , 因为咱们没有重写 equals 办法,那么就会调用 Object 默认的 equals 办法,显示这是两个不同的对象。
这里咱们将生成的对象放到了 HashSet 中,而 HashSet 中只可能寄存惟一的对象,也就是雷同的(实用于 equals 办法)的对象只会寄存一个,然而这里实际上是两个对象 ab 都被放到了 HashSet 中,这样 HashSet 就失去了他自身的意义了。
上面咱们持续重写 equals 办法:
public class HashTest {
private int i;
public int getI() {return i;}
public void setI(int i) {this.i = i;}
public boolean equals(Object object) {if (object == null) {return false;}
if (object == this) {return true;}
if (!(object instanceof HashTest)) {return false;}
HashTest other = (HashTest) object;
if (other.getI() == this.getI()) {return true;}
return false;
}
public int hashCode() {return i % 10;}
public final static void main(String[] args) {HashTest a = new HashTest();
HashTest b = new HashTest();
a.setI(1);
b.setI(1);
Set<HashTest> set = new HashSet<HashTest>();
set.add(a);
set.add(b);
System.out.println(a.hashCode() == b.hashCode());
System.out.println(a.equals(b));
System.out.println(set);
}
}
输入后果如下所示。
从后果咱们能够看出,当初两个对象就齐全相等了,HashSet 中也只寄存了一份对象。
留神:
hashCode()只是简略示例写的,真正的生产换将不是这样的
true
true
[HashTest@1]
10.HashMap 中的 HashCode 作用
hashCode 的存在次要是用于查找的快捷性 ,如 Hashtable,HashMap 等,hashCode 是用来在散列存储构造中 确定对象的存储地址 的。
如果两个对象雷同,就是实用于 equals(java.lang.Object) 办法,那么这两个对象的 hashCode 肯定要雷同。
如果对象的 equals 办法被重写,那么对象 的 hashCode 也尽量重写,并且产生 hashCode 应用的对象,肯定要和 equals 办法中应用的统一,否则就会违反下面提到的第 2 点。
两个对象的 hashCode 雷同,并不一定示意两个对象就雷同,也就是不肯定实用于 equals(java.lang.Object) 办法,只可能阐明这两个对象在散列存储构造中,如 Hashtable,他们“寄存在同一个篮子里”。
什么时候须要重写?
个别的中央不须要重载 hashCode,只有当类须要放在 HashTable、HashMap、HashSet 等等 hash 构造的汇合时才会重载 hashCode,那么为什么要重载 hashCode 呢?
要比拟两个类的内容属性值,是否雷同时候,依据 hashCode 重写规定,重写类的 指定字段的 hashCode(),equals()办法。
例如:
public class EmpWorkCondition{
/**
* 员工 ID
*/
private Integer empId;
/**
* 员工服务总复数
*/
private Integer orderSum;
@Override
public boolean equals(Object o) {if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
EmpWorkCondition that = (EmpWorkCondition) o;
return Objects.equals(empId, that.empId);
}
@Override
public int hashCode() {return Objects.hash(empId);
}
// 省略 getter setter
}
public static void main(String[] args) {List<EmpWorkCondition> list1 = new ArrayList<EmpWorkCondition>();
EmpWorkCondition emp1 = new EmpWorkCondition();
emp1.setEmpId(100);
emp1.setOrderSum(90000);
list1.add(emp1);
List<EmpWorkCondition> list2 = new ArrayList<EmpWorkCondition>();
EmpWorkCondition emp2 = new EmpWorkCondition();
emp2.setEmpId(100);
list2.add(emp2);
System.out.println(list1.contains(emp2));
}
输入后果为:
true
下面的办法,做的事件就是,比拟两个汇合中的,实体类对象属性值,是否统一
OrderSum 不在比拟范畴内,因为没有重写它的,equals()和 hashCode()办法
为什么要重载 equals 办法?
因为 Object 的 equals 办法默认是两个对象的援用的比拟,意思就是指向同一内存, 地址则相等,否则不相等;如果你当初须要利用对象外面的值来判断是否相等,则重载 equals 办法。
11. 为什么要重载 HashCode 办法?
个别的中央不须要重载 hashCode,只有当类须要放在 HashTable、HashMap、HashSet 等等 hash 构造的汇合时才会重载 hashCode,那么为什么要重载 hashCode 呢?
如果你重写了 equals,比如说是基于对象的内容实现的,而保留 hashCode 的实现不变,那么很可能某两个对象明明是“相等”,而 hashCode 却不一样。
这样,当你用其中的一个作为键保留到 hashMap、hasoTable 或 hashSet 中,再以“相等的”找另一个作为键值去查找他们的时候,则基本找不到。
为什么 equals()相等,hashCode 就肯定要相等,而 hashCode 相等,却不要求 equals 相等?
1、因为是依照 hashCode 来拜访小内存块,所以 hashCode 必须相等。2、HashMap 获取一个对象是比拟 key 的 hashCode 相等和 equals 为 true。
之所以 hashCode 相等,却能够 equals 不等,就比方 ObjectA 和 ObjectB 他们都有属性 name,那么 hashCode 都以 name 计算,所以 hashCode 一样,然而两个对象属于不同类型,所以 equals 为 false。
为什么须要 hashCode?
1、通过 hashCode 能够很快的查到小内存块。2、通过 hashCode 比拟比 equals 办法快,当 get 时先比拟 hashCode,如果 hashCode 不同,间接返回 false。
12.ArrayList、LinkedList、Vector 的区别
List 的三个子类的特点
ArrayList:
- 底层数据结构是数组,查问快,增删慢。
- 线程不平安,效率高。
Vector:
- 底层数据结构是数组,查问快,增删慢。
- 线程平安,效率低。
- Vector 绝对 ArrayList 查问慢(线程平安的)。
- Vector 绝对 LinkedList 增删慢(数组构造)。
LinkedList
- 底层数据结构是链表,查问慢,增删快。
- 线程不平安,效率高。
Vector 和 ArrayList 的区别
- Vector 是线程平安的, 效率低。
- ArrayList 是线程不平安的, 效率高。
- 共同点: 底层数据结构都是数组实现的, 查问快, 增删慢。
ArrayList 和 LinkedList 的区别
- ArrayList 底层是数组后果, 查问和批改快。
- LinkedList 底层是链表构造的, 增和删比拟快, 查问和批改比较慢。
- 共同点: 都是线程不平安的
List 有三个子类应用
- 查问多用 ArrayList。
- 增删多用 LinkedList。
- 如果都多 ArrayList。
13.String、StringBuffer 与 StringBuilder 的区别
String
:实用于大量的字符串操作的状况。
StringBuilder
实用于单线程下在字符缓冲区进行大量操作的状况。
StringBuffer
实用多线程下在字符缓冲区进行大量操作的状况。
StringBuilder是线程不平安的,而 StringBuffer 是线程平安的。
这三个类之间的区别次要是在两个方面,即运行速度和线程平安 这两方面。首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。
String 最慢的起因
String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创立之后该对象是不可更改的,但后两者的对象是变量,是能够更改的。
再来说线程平安
在线程平安上,StringBuilder 是线程不平安的,而 StringBuffer 是线程平安的。
如果一个 StringBuffer 对象在字符串缓冲区被多个线程应用时,StringBuffer 中很多办法能够带有 synchronized 关键字,所以能够保障线程是平安的,但 StringBuilder 的办法则没有该关键字,所以不能保障线程平安,有可能会呈现一些谬误的操作。所以如果要进行的操作是多线程的,那么就要应用 StringBuffer,然而在单线程的状况下,还是倡议应用速度比拟快的 StringBuilder。
14.Map、Set、List、Queue、Stack 的特点与用法
Map
- Map 是键值对,键 Key 是惟一不能反复的,一个键对应一个值,值能够反复。
- TreeMap 能够保障程序。
- HashMap 不保障程序,即为无序的。
- Map 中能够将 Key 和 Value 独自抽取进去,其中 KeySet()办法能够将所有的 keys 抽取正一个 Set。而 Values()办法能够将 map 中所有的 values 抽取成一个汇合。
Set
- 不蕴含反复元素的汇合,set 中最多蕴含一个 null 元素。
- 只能用 Iterator 实现单项遍历,Set 中没有同步办法。
List
- 有序的可反复汇合。
- 能够在任意地位减少删除元素。
- 用 Iterator 实现单向遍历,也可用 ListIterator 实现双向遍历。
Queue
- Queue 听从先进先出准则。
- 应用时尽量避免 add()和 remove()办法, 而是应用 offer()来增加元素,应用 poll()来移除元素,它的长处是能够通过返回值来判断是否胜利。
- LinkedList 实现了 Queue 接口。
- Queue 通常不容许插入 null 元素。
Stack
- Stack 听从后进先出准则。
- Stack 继承自 Vector。
- 它通过五个操作对类 Vector 进行扩大,容许将向量视为堆栈,它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek()办法、测试堆栈是否为空的 empty 办法等。
用法
- 如果波及堆栈,队列等操作,倡议应用 List。
- 对于疾速插入和删除元素的,倡议应用 LinkedList。
- 如果须要疾速随机拜访元素的,倡议应用 ArrayList。
更为精炼的总结
Collection 是对象汇合,Collection 有两个子接口 List 和 Set
List 能够通过下标 (1,2..) 来获得值,值能够反复。Set 只能通过游标来取值,并且值是不能反复的。
ArrayList,Vector,LinkedList 是 List 的实现类
- ArrayList 是线程不平安的,Vector 是线程平安的,这两个类底层都是由数组实现的。
- LinkedList 是线程不平安的,底层是由链表实现的。
Map 是键值对汇合
- HashTable 和 HashMap 是 Map 的实现类。
- HashTable 是线程平安的,不能存储 null 值。
- HashMap 不是线程平安的,能够存储 null 值。
Stack 类:继承自 Vector,实现一个后进先出的栈。提供了几个根本办法,push、pop、peak、empty、search 等。
Queue 接口:提供了几个根本办法,offer、poll、peek 等。已知实现类有 LinkedList、PriorityQueue 等。
15.HashMap 和 HashTable 的区别
Hashtable 是基于古老的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口的一个实现,它们都是汇合中将数据无序寄存的。
1、hashMap 去掉了 HashTable 的 contains 办法,然而加上了 containsValue()和 containsKey()办法
HashTable Synchronize 同步的,线程平安,HashTable 不容许空键值为空,效率低。
HashMap 非 Synchronize 线程同步的,线程不平安,HashMap 容许空键值为空,效率高。
Hashtable 是基于古老的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口的一个实现,它们都是汇合中将数据无序寄存的。
Hashtable 的办法是同步的,HashMap 未经同步,所以在多线程场合要手动同步 HashMap 这个区别就像 Vector 和 ArrayList 一样。
查看 Hashtable 的源代码就能够发现,除构造函数外,Hashtable 的所有 public 办法申明中都有 synchronized 关键字,而 HashMap 的源代码中则连 synchronized 的影子都没有,当然,正文除外。
2、Hashtable 不容许 null 值(key 和 value 都不能够),HashMap 容许 null 值(key 和 value 都能够)。
3、两者的遍历形式大同小异,Hashtable 仅仅比 HashMap 多一个 elements 办法。
Hashtable table = new Hashtable();
table.put("key", "value");
Enumeration em = table.elements();
while (em.hasMoreElements()) {String obj = (String) em.nextElement();
System.out.println(obj);
}
4、HashTable 应用 Enumeration,HashMap 应用 Iterator
从外部机制实现上的区别如下:
- 哈希值的应用不同,Hashtable 间接应用对象的 hashCode
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而 HashMap 从新计算 hash 值,而且用与代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length);
static int hash(Object x) {int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length) {return h & (length-1);
}
- Hashtable 中 hash 数组默认大小是 11,减少的形式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且肯定是 2 的指数。
16.JDK7 和 JDK8 中 HashMap 的实现
JDK7 中的 HashMap
HashMap 底层保护一个数组,数组中的每一项都是一个 Entry。
transient Entry<K,V>[] table;
咱们向 HashMap 中所搁置的对象实际上是存储在该数组当中。而 Map 中的 key,value 则以 Entry 的模式寄存在数组中。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
总结一下 map.put 后的过程:
当向 HashMap 中 put 一对键值时 ,它会 依据 key 的 hashCode 值计算出一个地位,该地位就是此对象筹备往数组中寄存的地位。
如果该地位没有对象存在,就将此对象间接放进数组当中;如果该地位曾经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是否是否值雷同,map 不容许 <key,value> 键值对反复),如果此链上有对象的话,再去应用 equals 办法进行比拟,如果对此链上的每个对象的 equals 办法比拟都为 false,则将该对象放到数组当中,而后将数组中该地位以前存在的那个对象链接到此对象的前面。
JDK8 中的 HashMap
JDK8 中采纳的是位桶 + 链表 / 红黑树(无关红黑树请查看红黑树)的形式,也是非线程平安的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
JDK8 中,当同一个 hash 值的节点数不小于 8 时,将不再以单链表的模式存储了,会被调整成一颗红黑树(上图中 null 节点没画)。这就是 JDK7 与 JDK8 中 HashMap 实现的最大区别。
接下来,咱们来看下 JDK8 中 HashMap 的源码实现。
JDK 中 Entry 的名字变成了 Node,起因是和红黑树的实现 TreeNode 相关联。
transient Node<K,V>[] table;
当抵触节点数不小于 8 - 1 时,转换成红黑树。
]static final int TREEIFY_THRESHOLD = 8;
17.HashMap 和 ConcurrentHashMap 的区别,Hashmap 底层源码
为了线程平安 从 ConcurrentHashMap 代码中能够看出,它引入了一个“分段锁 ”的概念,具体能够了解为把一个大的 Map 拆分成 N 个小的 HashTable,依据 key.hashCode() 来决定把 key 放到哪个 HashTable 中。
HashMap 实质是数组加链表。依据 key 获得 hash 值,而后计算出数组下标,如果多个 key 对应到同一个下标,就用链表串起来,新插入的在后面。
ConcurrentHashMap:在 hashMap 的根底上,ConcurrentHashMap 将数据分为多个 segment,默认 16 个(concurrency level),而后每次操作 对一个 segment 加锁,防止多线程锁的几率,进步并发效率。
总结
JDK6,7 中的 ConcurrentHashMap 次要应用 Segment 来实现减小锁粒度,把 HashMap 宰割成若干个 Segment,在 put 的时候须要锁住 Segment,get 时候不加锁,应用 volatile 来保障可见性,当要统计全局时(比方 size),首先会尝试屡次计算 modcount 来确定,这几次尝试中,是否有其余线程进行了批改操作,如果没有,则间接返回 size。如果有,则须要顺次锁住所有的 Segment 来计算。
jdk7 中 ConcurrentHashMap 中,当长度过长碰撞会很频繁,链表的增改删查操作都会耗费很长的工夫,影响性能。
jdk8 中齐全重写了 concurrentHashMap, 代码量从原来的 1000 多行变成了 6000 多 行,实现上也和原来的分段式存储有很大的区别。
JDK8 中采纳的是位桶 + 链表 / 红黑树(无关红黑树请查看红黑树)的形式,也是非线程平安的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
JDK8 中,当同一个 hash 值的节点数不小于 8 时,将不再以单链表的模式存储了,会被调整成一颗红黑树。这就是 JDK7 与 JDK8 中 HashMap 实现的最大区别。
次要设计上的变动有以下几点
1.jdk8 不采纳 segment 而采纳 node,锁住 node 来实现减小锁粒度。2. 设计了 MOVED 状态 当 resize 的中过程中 线程 2 还在 put 数据,线程 2 会帮忙 resize。3. 应用 3 个 CAS 操作来确保 node 的一些操作的原子性,这种形式代替了锁。4.sizeCtl 的不同值来代表不同含意,起到了管制的作用。
至于为什么 JDK8 中应用 synchronized 而不是 ReentrantLock,是因为 JDK8 中对 synchronized 有了足够的优化吧。
18.ConcurrentHashMap 能齐全代替 HashTable 吗?
HashTable 尽管性能上不如 ConcurrentHashMap,但并不能齐全被取代,两者的迭代器的一致性不同的,hashTable 的迭代器是强一致性的,而 ConcurrentHashMap 是弱统一的。
ConcurrentHashMap 的 get,clear,iterator 都是弱一致性的。Doug Lea 也将这个判断留给用户本人决定是否应用 ConcurrentHashMap。
ConcurrentHashMap 与 HashTable 都能够用于多线程的环境,然而当 HashTable 的大小减少到肯定的时候,性能会急剧下降,因为迭代时须要被锁定很长的工夫。因为 ConcurrentHashMap 引入了宰割(segmentation),不管它变得如许大,仅仅须要锁定 map 的某个局部,而其它的线程不须要等到迭代实现能力拜访 map。简而言之,在迭代的过程中,ConcurrentHashMap 仅仅锁定 map 的某个局部,而 HashTable 则会锁定整个 map。
那么什么是强一致性和弱一致性呢?
get 办法是弱统一的,是什么含意?可能你冀望往 ConcurrentHashMap 底层数据结构中退出一个元素后,立马能对 get 可见,但 ConcurrentHashMap 并不能如你所愿。换句话说,put 操作将一个元素退出到底层数据结构后,get 可能在某段时间内还看不到这个元素,若不思考内存模型,单从代码逻辑上来看,却是应该能够看失去的。
上面将联合代码和 java 内存模型相干内容来剖析下 put/get 办法。put 办法咱们只需关注 Segment#put,get 办法只需关注 Segment#get,在持续之前,先要阐明一下 Segment 里有两个 volatile 变量:count 和 table;HashEntry 里有一个 volatile 变量:value。
总结
ConcurrentHashMap 的弱一致性次要是为了晋升效率,是一致性与效率之间的一种衡量。要成为强一致性,就失去处应用锁,甚至是全局锁,这就与 HashTable 和同步的 HashMap 一样了。
19. 为什么 HashMap 是线程不平安的?
HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率靠近 100%。因为多线程会导致 HashMap 的 Node 链表造成环形数据结构,一旦造成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。
20. 如何线程平安的应用 HashMap?
理解了 HashMap 为什么线程不平安,那当初看看如何线程平安的应用 HashMap。这个无非就是以下三种形式:
Hashtable
ConcurrentHashMap
SynchronizedMap
Hashtable
例子
//Hashtable
Map<String, String> hashtable = new Hashtable<>();
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
HashTable 源码中是应用 synchronized 来保障线程平安的,比方上面的 get 办法和 put 办法:
public synchronized V get(Object key) {// 省略实现}
public synchronized V put(K key, V value) {// 省略实现}
所以当一个线程拜访 HashTable 的同步办法时,其余线程如果也要拜访同步办法,会被阻塞住。举个例子,当一个线程应用 put 办法时,另一个线程岂但不能够应用 put 办法,连 get 办法都不能够,好王道啊!!!so~~,效率很低,当初根本不会抉择它了。
ConcurrentHashMap
ConcurrentHashMap 于 Java 7 的,和 8 有区别, 在 8 中 CHM 摒弃了 Segment(锁段)的概念,而是启用了一种全新的形式实现, 利用 CAS 算法。
SynchronizedMap
synchronizedMap() 办法后会返回一个 SynchronizedMap 类的对象,而在 SynchronizedMap 类中应用了 synchronized 同步关键字来保障对 Map 的操作是线程平安的。
性能比照
这是要靠数据谈话的时代,所以不能只靠嘴说 ConcurrentHashMap 快,它就快了。写个测试用例,理论的比拟一下这三种形式的效率(源码起源),上面的代码别离通过三种形式创立 Map 对象,应用 ExecutorService 来并发运行 5 个线程,每个线程增加 / 获取 500K 个元素。
Test started for: class java.util.Hashtable
2500K entried added/retrieved in 2018 ms
2500K entried added/retrieved in 1746 ms
2500K entried added/retrieved in 1806 ms
2500K entried added/retrieved in 1801 ms
2500K entried added/retrieved in 1804 ms
For class java.util.Hashtable the average time is 1835 ms
Test started for: class java.util.Collections$SynchronizedMap
2500K entried added/retrieved in 3041 ms
2500K entried added/retrieved in 1690 ms
2500K entried added/retrieved in 1740 ms
2500K entried added/retrieved in 1649 ms
2500K entried added/retrieved in 1696 ms
For class java.util.Collections$SynchronizedMap the average time is 1963 ms
Test started for: class java.util.concurrent.ConcurrentHashMap
2500K entried added/retrieved in 738 ms
2500K entried added/retrieved in 696 ms
2500K entried added/retrieved in 548 ms
2500K entried added/retrieved in 1447 ms
2500K entried added/retrieved in 531 ms
For class java.util.concurrent.ConcurrentHashMap the average time is 792 ms
ConcurrentHashMap 性能是显著优于 Hashtable 和 SynchronizedMap 的,CHM 破费的工夫比前两个的一半还少。
21.TreeMap、HashMap、LindedHashMap 的区别
LinkedHashMap能够保障 HashMap 汇合有序,存入的程序和取出的程序统一。
TreeMap实现 SortMap 接口,可能把它保留的记录依据键排序, 默认是按键值的升序排序,也能够指定排序的比拟器,当用 Iterator 遍历 TreeMap 时,失去的记录是排过序的。
HashMap不保障程序,即为无序的,具备很快的访问速度。HashMap最多只容许一条记录的键为 Null; 容许多条记录的值为 Null。HashMap不反对线程的同步。
咱们在开发的过程中 应用 HashMap 比拟多,在 Map 中插入、删除和定位元素,HashMap 是最好的抉择。
但如果您要 按天然程序或自定义程序遍历键,那么TreeMap 会更好。
如果 须要输入的程序和输出的雷同 , 那么 用 LinkedHashMap 能够实现, 它还能够按读取程序来排列。
22.Collection 包构造,与 Collections 的区别
Collection 是汇合类的下级接口,子接口次要有 Set、List、Map。
Collecions 是针对汇合类的一个帮忙类,提供了操作汇合的工具办法,一系列静态方法实现对各 种汇合的搜寻、排序线性、线程平安化 等操作。
例如
Map<String, Object> map4 = Collections.synchronizedMap(new HashMap<String, Object>()); // 线程平安的 HashMap
Collections.sort(List<T> list, Comparator<? super T> c); // 排序 List
Collection 是单列汇合
List
元素是有序的、可反复。有序的 collection,能够对列表中每个元素的插入地位进行准确地管制。能够依据元素的整数索引(在列表中的地位)拜访元素,并搜寻列表中的元素。可寄存反复元素,元素存取是有序的。
List 接口中罕用类
Vector
线程平安,但速度慢,已被 ArrayList 代替。底层数据结构是数组构造。
ArrayList
线程不平安,查问速度快。底层数据结构是数组构造。
LinkedList
线程不平安。增删速度快。底层数据结构是列表构造。
Set
Set 接口中罕用的类
Set
元素无序的、不可反复。取出元素的办法只有迭代器。不能够寄存反复元素,元素存取是无序的。
HashSet
线程不平安,存取速度快。它是如何保障元素唯一性的呢?依赖的是元素的 hashCode 办法和 euqals 办法。
TreeSet
线程不平安,能够对 Set 汇合中的元素进行排序。它的排序是如何进行的呢?通过 compareTo 或者 compare 办法中的来保障元素的唯一性。元素是以二叉树的模式寄存的。
Map
map 是一个双列汇合
Hashtable
线程平安,速度快。底层是哈希表数据结构。是同步的。不容许 null 作为键,null 作为值。
Properties
用于配置文件的定义和操作,应用频率十分高,同时键和值都是字符串。是汇合中能够和 IO 技术相结合的对象。
HashMap
线程不平安,速度慢。底层也是哈希表数据结构。是不同步的。容许 null 作为键,null 作为值,代替了 Hashtable。
LinkedHashMap
能够保障 HashMap 汇合有序。存入的程序和取出的程序统一。
TreeMap
能够用来对 Map 汇合中的键进行排序
23.try catch finally,try 里有 return,finally 还执行吗?
必定会执行。finally{}块的代码。只有在 try{}块中蕴含遇到System.exit(0)。之类的导致 Java 虚拟机间接退出的语句才会不执行。
当程序执行 try{}遇到 return 时,程序会先执行 return 语句,但并不会立刻返回——也就是把 return 语句要做的所有事件都筹备好,也就是在将要返回、但并未返回的时候,程序把执行流程转去执行 finally 块,当 finally 块执行实现后就间接返回方才 return 语句曾经筹备好的后果。
24.Excption 与 Error 包构造。OOM 你遇到过哪些状况,SOF 你遇到过哪些状况
Throwable 是 Java 语言中所有谬误或异样的超类。Throwable 蕴含两个子类: Error 和 Exception。它们通常用于批示产生了异常情况。Throwable 蕴含了其线程创立时线程执行堆栈的快照,它提供了 printStackTrace()等接口用于获取堆栈跟踪数据等信息。
Java 将可抛出 (Throwable) 的构造分为三种类型:
被查看的异样(Checked Exception)。运行时异样(RuntimeException)。谬误(Error)。
运行时异样 RuntimeException
定义 : RuntimeException 及其子类都被称为运行时异样。特点 : Java 编译器不会查看它 也就是说,当程序中可能呈现这类异样时,假使既 ” 没有通过 throws 申明抛出它 ”,也 ” 没有用 try-catch 语句捕捉它 ”,还是会编译通过。
例如,除数为零时产生的 ArithmeticException 异样,数组越界时产生的 IndexOutOfBoundsException 异样,fail-fail 机制产生的 ConcurrentModificationException 异样等,都属于运行时异样。
堆内存溢出 OutOfMemoryError(OOM)
除了程序计数器外,虚拟机内存的其余几个运行时区域都有产生 OutOfMemoryError(OOM)异样的可能。
Java Heap 溢出。个别的异样信息:java.lang.OutOfMemoryError:Java heap spacess。java 堆用于存储对象实例,咱们只有一直的创建对象,并且保障 GC Roots 到对象之间有可达门路来防止垃圾回收机制革除这些对象,就会在对象数量达到最大堆容量限度后产生内存溢出异样。
堆栈溢出 StackOverflow(SOF)
StackOverflowError 的定义:当应用程序递归太深而产生堆栈溢出时,抛出该谬误。因为栈个别默认为 1 -2m,一旦呈现死循环或者是大量的递归调用,在一直的压栈过程中,造成栈容量超过 1m 而导致溢出。
栈溢出的起因:
递归调用。大量循环或死循环。全局变量是否过多。数组、List、map 数据过大。
25.Java(OOP)面向对象的三个特色与含意
封装(高内聚低耦合 –> 解耦)
封装是指将某事物的属性和行为包装到对象中,这个对象只对外颁布须要公开的属性和行为,而这个颁布也是能够有选择性的颁布给其它对象。在 java 中能应用 private、protected、public 三种修饰符或不必(即默认 defalut)对外部对象拜访该对象的属性和行为进行限度。
java 的继承(重用父类的代码)
继承是子对象能够继承父对象的属性和行为,亦即父对象领有的属性和行为,其子对象也就领有了这些属性和行为。
java 中的多态(父类援用指向子类对象)
多态是指父对象中的同一个行为能在其多个子对象中有不同的体现。
有两种多态的机制:编译时多态、运行时多态。
1、办法的重载:重载是指同一类中有多个同名的办法,但这些办法有着不同的参数。,因而在编译时就能够确定到底调用哪个办法,它是一种编译时多态。2、办法的重写:子类能够笼罩父类的办法,因而同样的办法会在父类中与子类中有着不同的表现形式。
26.Override 和 Overload 的含意去区别
重载 Overload 办法名雷同,参数列表不同(个数、程序、类型不同) 与返回类型无关。重写 Override 笼罩。将父类的办法笼罩。重写办法重写:办法名雷同,拜访修饰符只能大于被重写的办法拜访修饰符,办法签名个数,程序个数类型雷同。
Override(重写)
- 办法名、参数、返回值雷同。
- 子类办法不能放大父类办法的拜访权限。
- 子类办法不能抛出比父类办法更多的异样(但子类办法能够不抛出异样)。
- 存在于父类和子类之间。
- 办法被定义为 final 不能被重写。
Overload(重载)
- 参数类型、个数、程序至多有一个不雷同。
- 不能重载只有返回值不同的办法名。
- 存在于父类和子类、同类中。
而重载的规定
1、必须具备不同的参数列表。2、能够有不同的返回类型,只有参数列表不同就能够了。3、能够有不同的拜访修饰符。4、能够抛出不同的异样。
重写办法的规定
1、参数列表必须齐全与被重写的办法雷同,否则不能称其为重写而是重载。2、返回的类型必须始终与被重写的办法的返回类型雷同,否则不能称其为重写而是重载。3、拜访修饰符的限度肯定要大于被重写办法的拜访修饰符(public>protected>default>private)。4、重写办法肯定不能抛出新的查看异样或者比被重写办法申明更加宽泛的查看型异样。
例如:父类的一个办法申明了一个查看异样 IOException,在重写这个办法是就不能抛出 Exception, 只能抛出 IOException 的子类异样,能够抛出非查看异样。
27.Interface 与 abstract 类的区别
Interface 只能有成员常量,只能是办法的申明。Abstract class能够有成员变量,能够申明一般办法和形象办法。
interface 是接口,所有的办法都是形象办法,成员变量是默认的 public static final 类型。接口不能实例化本人。
abstract class 是抽象类,至多蕴含一个形象办法的累叫抽象类,抽象类不能被本身实例化,并用 abstract 关键字来润饰。
28.Static class 与 non static class 的区别
static class(外部动态类)
1、用 static 润饰的外部类,此时这个外部类变为动态外部类;对测试有用。2、外部动态类不须要有指向外部类的援用。3、动态外部类只能拜访外部类的动态成员,不能拜访外部类的非动态成员。
non static class(非动态外部类)
1、非动态外部类须要持有对外部类的援用。2、非动态外部类可能拜访外部类的动态和非动态成员。3、一个非动态外部类不能脱离外部类实体被创立。4、一个非动态外部类能够拜访外部类的数据和办法。
29.foreach 与失常 for 循环效率比照
用 for 循环 arrayList 10 万次破费工夫:5 毫秒。用 foreach 循环 arrayList 10 万次破费工夫:7 毫秒。用 for 循环 linkList 10 万次破费工夫:4481 毫秒。用 foreach 循环 linkList 10 万次破费工夫:5 毫秒。
循环 ArrayList 时,一般 for 循环比 foreach 循环破费的工夫要少一点。循环 LinkList 时,一般 for 循环比 foreach 循环破费的工夫要多很多。
当我将循环次数晋升到一百万次的时候,循环 ArrayList,一般 for 循环还是比 foreach 要快一点;然而一般 for 循环在循环 LinkList 时,程序间接卡死。
ArrayList
ArrayList 是采纳数组的模式保留对象的,这种形式将对象放在间断的内存块中,所以插入和删除时比拟麻烦,查问比拟不便。
LinkList
LinkList 是将对象放在独立的空间中,而且每个空间中还保留下一个空间的索引,也就是数据结构中的链表构造,插入和删除比拟不便,然而查找很麻烦,要从第一个开始遍历。
论断:
须要循环数组构造的数据时,倡议应用一般 for 循环,因为 for 循环采纳下标拜访,对于数组构造的数据来说,采纳下标拜访比拟好。
须要循环链表构造的数据时,肯定不要应用一般 for 循环,这种做法很蹩脚,数据量大的时候有可能会导致系统解体。
30.Java IO 与 NIO
NIO 是为了补救 IO 操作的有余而诞生的,NIO 的一些新个性有:非阻塞 I /O,选择器,缓冲以及管道。管道(Channel),缓冲(Buffer),选择器(Selector)是其次要特色。
概念解释
Channel——管道实际上就像传统 IO 中的流,到任何目的地 (或来自任何中央) 的所有数据都必须通过一个 Channel 对象。一个 Buffer 本质上是一个容器对象。
每一种根本 Java 类型都有一种缓冲区类型:
ByteBuffer——byte
CharBuffer——char
ShortBuffer——short
IntBuffer——int
LongBuffer——long
FloatBuffer——float
DoubleBuffer——double
Selector
——选择器用于监听多个管道的事件,应用传统的阻塞 IO 时咱们能够不便的晓得什么时候能够进行读写,而应用非阻塞通道,咱们须要一些办法来晓得什么时候通道筹备好了,选择器正是为这个须要而诞生的。
NIO 和传统的 IO 有什么区别呢?
IO 是面向流的,NIO 是面向块(缓冲区)的。
IO 面向流的操作一次一个字节地解决数据。一个输出流产生一个字节的数据,一个输入流生产一个字节的数据。,导致了数据的读取和写入效率不佳。
NIO 面向块的操作在一步中产生或者生产一个数据块。按块解决数据比按 (流式的) 字节解决数据要快得多,同时数据读取到一个它稍后解决的缓冲区,须要时可在缓冲区中前后挪动。这就减少了处理过程中的灵活性。艰深来说,NIO 采取了“预读”的形式,当你读取某一部分数据时,他就会猜想你下一步可能会读取的数据而事后缓冲下来。
IO 是阻塞的,NIO 是非阻塞的
对于传统的 IO,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据齐全写入。该线程在此期间不能再干任何事件了。
而对于 NIO,应用一个线程发送读取数据申请,没有失去响应之前,线程是闲暇的,此时线程能够去执行别的工作,而不是像 IO 中那样只能期待响应实现。
NIO 和 IO 实用场景
NIO 是为补救传统 IO 的有余而诞生的,然而尺有所短寸有所长,NIO 也有毛病,因为 NIO 是面向缓冲区的操作,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理之前必须要判断缓冲区的数据是否残缺或者曾经读取结束,如果没有,假如数据只读取了一部分,那么对不残缺的数据处理没有任何意义。所以每次数据处理之前都要检测缓冲区数据。
那么 NIO 和 IO 各实用的场景是什么呢?
如果须要治理同时关上的成千上万个连贯,这些连贯每次只是发送大量的数据,例如聊天服务器,这时候用 NIO 解决数据可能是个很好的抉择。
而如果只有大量的连贯,而这些连贯每次要发送大量的数据,这时候传统的 IO 更适合。应用哪种解决数据,须要在数据的响应等待时间和查看缓冲区数据的工夫上作比拟来衡量抉择。
艰深解释,最初,对于 NIO 和传统 IO
有一个网友讲的活泼的例子:
以前的流总是梗塞的,一个线程只有对它进行操作,其它操作就会被梗塞,也就相当于水管没有阀门,你伸手接水的时候,不论水到了没有,你就都只能耗在接水(流)上。
nio 的Channel
的退出,相当于减少了水龙头(有阀门),尽管一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流进去的水,都能够失去妥善接收,这个要害之处就是减少了一个接水工,也就是Selector
,他负责协调,也就是看哪根水管有水了的话,在以后水管的水接到肯定水平的时候,就切换一下:长期关上以后水龙头,试着关上另一个水龙头(看看有没有水)。
当其他人须要用水的时候,不是间接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer
。也就是,其他人尽管也可能要等,但不会在现场等,而是回家等,能够做其它事去,水接满了,接水工会告诉他们。
这其实也是十分靠近以后社会分工细化的事实,也是统分利用现有资源达到并发成果的一种很经济的伎俩,而不是动不动就来个并行处理,尽管那样是最简略的,但也是最浪费资源的形式。
31.Java 反射的作用与原理
什么是 Java 的反射呢?
Java 反射是能够让咱们在运行时,通过一个类的 Class 对象来获取它获取类的办法、属性、父类、接口等类的外部信息的机制。
这种动静获取信息以及动静调用对象的办法的性能称为 JAVA 的反射。
反射的作用?
反射就是:在任意一个办法里:
1. 如果我晓得一个类的名称 / 或者它的一个实例对象,我就能把这个类的所有办法和变量的信息找进去(办法名,变量名,办法,修饰符,类型,办法参数等等所有信息)
2. 如果我还明确晓得这个类里某个变量的名称,我还能失去这个变量以后的值。
3. 当然,如果我明确晓得这个类里的某个办法名 + 参数个数类型,我还能通过传递参数来运行那个类里的那个办法。
反射机制次要提供了以下性能:
- 在运行时判断任意一个对象所属的类。
- 在运行时结构任意一个类的对象。
- 在运行时判断任意一个类所具备的成员变量和办法。
- 在运行时调用任意一个对象的办法。
- 生成动静代理。
反射的原理?
JAVA 语言编译之后会生成一个.class 文件,反射就是通过字节码文件找到某一个类、类中的办法以及属性等。
反射的实现 API 有哪些?
反射的实现次要借助以下四个类:
Class:类的对象
Constructor:类的构造方法
Field:类中的属性对象
Method:类中的办法对象
32. java 创建对象的几种形式
- 采纳 new
- 通过反射
- 采纳 clone
- 通过序列化机制
前 2 者都须要显式地调用构造方法,造成耦合性最高的恰好是第一种,因而你发现无论什么框架,只有波及到解耦必先缩小 new 的应用。
33. 你对 String 对象的 intern()办法相熟么?
intern()办法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则当初常量池中创立,如果曾经存在则间接返回。
例子:
String s1=”aa”;
String s2=s1.intern();
System.out.print(s1==s2);// 返回 true
34.a=a+ b 与 a += b 有什么区别吗?
+= 操作符会进行隐式主动类型转换,此处 a += b 隐式的将加操作的后果类型强制转换为持有后果的类型,而 a =a+ b 则不会主动进行类型转换。
例子:
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
35.short s1= 1; s1 = s1 + 1; 该段代码是否有错, 有的话怎么改?
有谬误,short 类型在进行运算时会主动晋升为 int 类型,也就是说 s1+1
的运算后果是 int 类型。
36.& 和 && 的区别
& 是
位操作, 而 &&
是逻辑运算符。
&&
逻辑运算符具备短路个性,而 &
不具备短路个性。
public class Test{
static String name;
public static void main(String[] args){if(name!=null&userName.equals("")){System.out.println("ok");
}else{System.out.println("erro");
}
}
}
37.int 和 Integer 谁占用的内存更多?
Integer 对象会占用更多的内存。Integer 是一个对象,须要存储对象的元数据。然而 int 是一个原始类型的数据,所以占用的空间更少。
参考 https://www.cnblogs.com/java1024/p/8622195.html
Java 汇合篇
1. 说说常见的汇合有哪些吧?
Map 接口和 Collection 接口是所有汇合框架的父接口:
- Collection 接口的子接口包含:Set 接口和 List 接口
- Map 接口的实现类次要有:HashMap、TreeMap、HashTable、ConcurrentHashMap 以及 Properties 等
- Set 接口的实现类次要有:HashSet、TreeSet、LinkedHashSet 等
- List 接口的实现类次要有:ArrayList、LinkedList、Stack 以及 Vector 等
2.HashMap 与 HashTable 的区别?
- HashMap 没有思考同步,是线程不平安的;HashTable 应用了 synchronized 关键字,是线程平安的。
- HashMap 容许 K / V 都为 null;后者 K / V 都不容许为 null。
- HashMap 继承自 AbstractMap 类,而 HashTable 继承自 Dictionary 类。
3.HashMap 的 put 办法具体流程?
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
// 1. 如果 table 为空或者长度为 0,即没有元素,那么应用 resize()办法扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 2. 计算插入存储的数组索引 i,此处计算方法同 1.7 中的 indexFor()办法
// 如果数组为空,即不存在 Hash 抵触,则直接插入数组
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 3. 插入时,如果产生 Hash 抵触,则顺次往下判断
else {
HashMap.Node<K,V> e; K k;
// a. 判断 table[i]的元素的 key 是否与须要插入的 key 一样,若雷同则间接用新的 value 笼罩掉旧的 value
// 判断准则 equals() - 所以须要当 key 的对象重写该办法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// b. 持续判断:须要插入的数据结构是红黑树还是链表
// 如果是红黑树,则间接在树中插入 or 更新键值对
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 如果是链表,则在链表中插入 or 更新键值对
else {// i . 遍历 table[i],判断 key 是否已存在:采纳 equals 比照以后遍历结点的 key 与须要插入数据的 key
// 如果存在雷同的,则间接笼罩
// ii. 遍历结束后工作发现上述情况,则间接在链表尾部插入数据
// 插入实现后判断链表长度是否 > 8:若是,则把链表转换成红黑树
for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 对于 i 状况的后续操作:发现 key 已存在,间接用新 value 笼罩旧 value& 返回旧 value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 插入胜利后,判断理论存在的键值对数量 size > 最大容量
// 如果大于则进行扩容
if (++size > threshold)
resize();
// 插入胜利时会调用的办法(默认实现为空)afterNodeInsertion(evict);
return null;
}
图片总结为:
4.HashMap 的扩容操作是怎么实现的?
通过剖析源码咱们晓得了 HashMap 通过 resize()
办法进行扩容或者初始化的操作,上面是对源码进行的一些简略剖析:
/**
* 该函数有 2 中应用状况:1. 初始化哈希表;2. 以后数组容量过小,须要扩容
*/
final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;// 扩容前的数组(以后数组)int oldCap = (oldTab == null) ? 0 : oldTab.length;// 扩容前的数组容量(数组长度)int oldThr = threshold;// 扩容前数组的阈值
int newCap, newThr = 0;
if (oldCap > 0) {
// 针对状况 2:若扩容前的数组容量超过最大值,则不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 针对状况 2:若没有超过最大值,就扩容为原来的 2 倍(左移 1 位)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 针对状况 1:初始化哈希表(采纳指定或者应用默认值的形式)else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算新的 resize 下限
if (newThr == 0) {float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把每一个 bucket 都挪动到新的 bucket 中去
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
5.HashMap 是怎么解决哈希抵触的?
在解决这个问题之前,咱们首先须要晓得 什么是哈希抵触 ,而在理解哈希抵触之前咱们还要晓得 什么是哈希 才行;
什么是哈希?
Hash,个别翻译为“散列”,也有间接音译为“哈希”的,这就是把任意长度的输出通过散列算法,变换成固定长度的输入,该输入就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输出的空间,不同的输出可能会散列成雷同的输入,所以不可能从散列值来惟一的确定输出值。简略的说就是一种将任意长度的消息压缩到某一固定长度的音讯摘要的函数。
所有散列函数都有如下一个根本个性:依据同一散列函数计算出的散列值如果不同,那么输出值必定也不同。然而,依据同一散列函数计算出的散列值如果雷同,输出值不肯定雷同。
什么是哈希抵触?
当两个不同的输出值,依据同一散列函数计算出雷同的散列值的景象,咱们就把它叫做碰撞(哈希碰撞)。
HashMap 的数据结构
在 Java 中,保留数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除艰难;链表的特点是:寻址艰难,但插入和删除容易;所以咱们将数组和链表联合在一起,施展两者各自的劣势,应用一种叫做 链地址法 的形式能够解决哈希抵触:
这样咱们就能够将领有雷同哈希值的对象组织成一个链表放在 hash 值所对应的 bucket 下,但相比于 hashCode 返回的 int 类型,咱们 HashMap 初始的容量大小 DEFAULT_INITIAL_CAPACITY = 1 << 4
(即 2 的四次方 16)要远小于 int 类型的范畴,所以咱们如果只是单纯的用 hashCode 取余来获取对应的 bucket 这将会大大增加哈希碰撞的概率,并且最坏状况下还会将 HashMap 变成一个单链表, 所以咱们还须要对 hashCode 作肯定的优化
hash()函数
下面提到的问题,次要是因为如果应用 hashCode 取余,那么相当于 参加运算的只有 hashCode 的低位 ,高位是没有起到任何作用的,所以咱们的思路就是让 hashCode 取值出的高位也参加运算,进一步升高 hash 碰撞的概率,使得数据分布更均匀,咱们把这样的操作称为 扰动 ,在JDK 1.8 中的 hash()函数如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与本人右移 16 位进行异或运算(高下位异或)}
这比在 JDK 1.7 中,更为简洁,相比在 1.7 中的 4 次位运算,5 次异或运算(9 次扰动),在 1.8 中,只进行了 1 次位运算和 1 次异或运算(2 次扰动);
JDK1.8 新增红黑树
总结
简略总结一下 HashMap 是应用了哪些办法来无效解决哈希抵触的:
1. 应用链地址法(应用散列表)来链接领有雷同 hash 值的数据;
2. 应用 2 次扰动函数(hash 函数)来升高哈希抵触的概率,使得数据分布更均匀;
3. 引入红黑树进一步升高遍历的工夫复杂度,使得遍历更快;
6.HashMap 为什么不间接应用 hashCode()解决后的哈希值间接作为 table 的下标?
hashCode()
办法返回的是 int 整数类型,其范畴为 -(2 ^ 31)~(2 ^ 31 – 1),约有 40 亿个映射空间,而 HashMap 的容量范畴是在 16(初始化默认值)~2 ^ 30,HashMap 通常状况下是取不到最大值的,并且设施上也难以提供这么多的存储空间,从而导致通过 hashCode()
计算出的哈希值可能不在数组大小范畴内,进而无奈匹配存储地位;
面试官:那怎么解决呢?
- HashMap 本人实现了本人的
hash()
办法,通过两次扰动使得它本人的哈希值高下位自行进行异或运算,升高哈希碰撞概率也使得数据分布更均匀。 - 在保障数组长度为 2 的幂次方的时候,应用
hash()
运算之后的值与运算(&)(数组长度 – 1)来获取数组下标的形式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为 2 的幂次方时,h&(length-1)才等价于 h%length,三来解决了“哈希值与数组大小范畴不匹配”的问题。
面试官:为什么数组长度要保障为 2 的幂次方呢?
- 只有当数组长度为 2 的幂次方时,h&(length-1)才等价于 h%length,即实现了 key 的定位,2 的幂次方也能够缩小抵触次数,进步 HashMap 的查问效率。
- 如果 length 为 2 的次幂 则 length-1 转化为二进制必然是 11111……的模式,在于 h 的二进制与操作效率会十分的快,而且空间不节约;如果 length 不是 2 的次幂,比方 length 为 15,则 length – 1 为 14,对应的二进制为 1110,在于 h 与操作,最初一位都为 0,而 0001,0011,0101,1001,1011,0111,1101 这几个地位永远都不能寄存元素了,空间节约相当大,更糟的是这种状况中,数组能够应用的地位比数组长度小了很多,这意味着进一步减少了碰撞的几率,减慢了查问的效率!这样就会造成空间的节约。
面试官:那为什么是两次扰动呢?
这样就是加大哈希值低位的随机性,使得散布更平均,从而进步对应数组存储下标地位的随机性 & 平均性,最终缩小 Hash 抵触,两次就够了,曾经达到了高位低位同时参加运算的目标。
7.HashMap 在 JDK1.7 和 JDK1.8 中有哪些不同?
不同 | JDK 1.7 | JDK 1.8 |
---|---|---|
存储构造 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
初始化形式 | 独自函数:inflateTable() | 间接集成到了扩容函数 resize()中 |
hash 值计算形式 | 扰动解决 = 9 次扰动 = 4 次位运算 + 5 次异或运算 | 扰动解决 = 2 次扰动 = 1 次位运算 + 1 次异或运算 |
存放数据的规定 | 无抵触时,寄存数组;抵触时,寄存链表 | 无抵触时,寄存数组;抵触 & 链表长度 < 8:寄存单链表;抵触 & 链表长度 > 8:树化并寄存红黑树 |
插入数据形式 | 头插法(先讲原地位的数据移到后 1 位,再插入数据到该地位) | 尾插法(直接插入到链表尾部 / 红黑树) |
扩容后存储地位的计算形式 | 全副依照原来办法进行计算(即 hashCode ->> 扰动函数 ->> (h&length-1)) | 依照扩容后的法则计算(即扩容后的地位 = 原地位 or 原地位 + 旧容量) |
8. 为什么 HashMap 中 String、Integer 这样的包装类适宜作为 Key?
String、Integer 等包装类的个性可能保障 Hash 值的不可更改性和计算准确性,可能无效的缩小 Hash 碰撞的几率
- 都是 final 类型,即不可变性,保障 key 的不可更改性,不会存在获取 hash 值不同的状况
- 外部已重写了
equals()
、hashCode()
等办法,恪守了 HashMap 外部的标准(不分明能够去下面看看 putValue 的过程),不容易呈现 Hash 值计算错误的状况;
面试官:如果我想要让本人的 Object 作为 K 应该怎么办呢?
重写 hashCode()
和equals()
办法
- 重写
hashCode()
是因为须要计算存储数据的存储地位,须要留神不要试图从散列码计算中排除掉一个对象的要害局部来进步性能,这样尽管能更快但可能会导致更多的 Hash 碰撞。 - 重写
equals()
办法 ,须要恪守自反性、对称性、传递性、一致性以及对于任何非 null 的援用值 x,x.equals(null) 必须返回 false 的这几个个性,目标是为了保障 key 在哈希表中的唯一性。
9.ConcurrentHashMap 和 HashTable 的区别?
ConcurrentHashMap 联合了 HashMap 和 HashTable 二者的劣势。HashMap 没有思考同步,HashTable 思考了同步的问题。然而 HashTable 在每次同步执行时都要锁住整个构造。ConcurrentHashMap 锁的形式是略微细粒度的。
面试官:ConcurrentHashMap 的具体实现晓得吗?
在 JDK1.7 中,ConcurrentHashMap 采纳 Segment + HashEntry 的形式进行实现,构造如下:
- 该类蕴含两个动态外部类 HashEntry 和 Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;
- Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行批改时,必须首先取得对应的 Segment 锁。
在JDK1.8 中,放弃了 Segment 臃肿的设计,取而代之的是采纳 Node + CAS + Synchronized 来保障并发平安进行实现,构造如下:
插入元素过程(倡议去看看源码):
-
如果相应地位的 Node 还没有初始化,则调用 CAS 插入相应的数据。
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin }
-
如果相应地位的 Node 不为空,且以后该节点不处于挪动状态,则对该节点加 synchronized 锁,如果该节点的 hash 不小于 0,则遍历链表更新节点或插入新节点。
if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value, null); break; } } }
- 如果该节点是 TreeBin 类型的节点,阐明是红黑树结构,则通过 putTreeVal 办法往红黑树中插入节点;如果 binCount 不为 0,阐明 put 操作对数据产生了影响,如果以后链表的个数达到 8 个,则通过 treeifyBin 办法转化为红黑树,如果 oldVal 不为空,阐明是一次更新操作,没有对元素个数产生影响,则间接返回旧值。
- 如果插入的是一个新节点,则执行 addCount()办法尝试更新元素个数 baseCount。
10.Java 汇合的疾速失败机制“fail-fast”?
是 java 汇合的一种谬误检测机制,当多个线程对汇合进行构造上的扭转的操作时,有可能会产生 fail-fast 机制。
例如:假如存在两个线程(线程 1、线程 2),线程 1 通过 Iterator 在遍历汇合 A 中的元素,在某个时候线程 2 批改了汇合 A 的构造(是构造下面的批改,而不是简略的批改汇合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异样,从而产生 fail-fast 机制。
起因:迭代器在遍历时间接拜访汇合中的内容,并且在遍历过程中应用一个 modCount 变量。汇合在被遍历期间如果内容发生变化,就会扭转 modCount 的值。每当迭代器应用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异样,终止遍历。
解决办法:
1. 在遍历过程中,所有波及到扭转 modCount 值得中央全副加上 synchronized。
2. 应用 CopyOnWriteArrayList 来替换 ArrayList
11. ArrayList 和 Vector 的区别?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序汇合,即存储在这两个汇合中的元素地位都是有程序的,相当于一种动静的数组,咱们当前能够按地位索引来取出某个元素,并且其中的数据是容许反复的,这是与 HashSet 之类的汇合的最大不同处,HashSet 之类的汇合不能够按索引号去检索其中的元素,也不容许有反复的元素。
ArrayList 与 Vector 的区别次要包含两个方面:
- 同步性:
Vector 是线程平安的,也就是说它的办法之间是线程同步(加了 synchronized 关键字)的,而 ArrayList 是线程不平安的,它的办法之间是线程不同步的。如果只有一个线程会拜访到汇合,那最好是应用 ArrayList,因为它不思考线程平安的问题,所以效率会高一些;如果有多个线程会拜访到汇合,那最好是应用 Vector,因为不须要咱们本人再去思考和编写线程平安的代码。
- 数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们外面的元素的集体超过了容量时,就须要减少 ArrayList 和 Vector 的存储空间,每次要减少存储空间时,不是只减少一个存储单元,而是减少多个存储单元,每次减少的存储单元的个数在内存空间利用与程序效率之间要去的肯定的均衡。Vector 在数据满时(加载因子 1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。
12.ArrayList 和 LinkedList 的区别?
- LinkedList 实现了 List 和 Deque 接口,个别称为双向链表;ArrayList 实现了 List 接口,动静数组;
- LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;
- LinkedList 比 ArrayList 须要更多的内存;
面试官:Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?
它们的区别是:
- Array 能够蕴含根本类型和对象类型,ArrayList 只能蕴含对象类型。
- Array 大小是固定的,ArrayList 的大小是动态变化的。
- ArrayList 提供了更多的办法和个性,比方:addAll(),removeAll(),iterator() 等等。
对于根本类型数据,汇合应用主动装箱来缩小编码工作量。然而,当解决固定大小的根本数据类型的时候,这种形式绝对比较慢。
13.HashSet 是如何保证数据不可反复的?
HashSet 的底层其实就是 HashMap,只不过咱们HashSet 是实现了 Set 接口并且把数据作为 K 值,而 V 值始终应用一个雷同的虚值来保留,咱们能够看到源码:
public boolean add(E e) {return map.put(e, PRESENT)==null;// 调用 HashMap 的 put 办法,PRESENT 是一个至始至终都雷同的虚值
}
因为 HashMap 的 K 值自身就不容许反复,并且在 HashMap 中如果 K / V 雷同时,会用新的 V 笼罩掉旧的 V,而后返回旧的 V,那么在 HashSet 中执行这一句话始终会返回一个 false,导致插入失败,这样就保障了数据的不可重复性。
14.BlockingQueue 是什么?
Java.util.concurrent.BlockingQueue 是一个队列,在进行检索或移除一个元素的时候,它会期待队列变为非空;当在增加一个元素时,它会期待队列中的可用空间。BlockingQueue 接口是 Java 汇合框架的一部分,次要用于实现生产者 - 消费者模式。咱们不须要放心期待生产者有可用的空间,或消费者有可用的对象,因为它都在 BlockingQueue 的实现类中被解决了。Java 提供了集中 BlockingQueue 的实现,比方 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue 等。
参考 https://www.jianshu.com/p/939b8a672070
Java 线程篇
1. 什么是线程?
线程是操作系统可能进行运算调度的最小单位,它被蕴含在过程之中,是过程中的理论运作单位。程序员能够通过它进行多处理器编程,你能够应用多线程对运算密集型工作提速。比方,如果一个线程实现一个工作要 100 毫秒,那么用十个线程实现该工作只需 10 毫秒。
2. 线程和过程有什么区别?
一个过程是一个独立 (self contained) 的运行环境,它能够被看作一个程序或者一个利用。而线程是在过程中执行的一个工作。线程是过程的子集,一个过程能够有很多线程,每条线程并行执行不同的工作。不同的过程应用不同的内存空间,而所有的线程共享一片雷同的内存空间。别把它和栈内存搞混,每个线程都领有独自的栈内存用来存储本地数据。
3. 如何在 Java 中实现线程?
有两种创立线程的办法:一是实现 Runnable 接口,而后将它传递给 Thread 的构造函数,创立一个 Thread 对象;二是间接继承 Thread 类。
4. 用 Runnable 还是 Thread?
这个问题是上题的后续,大家都晓得咱们能够通过继承 Thread 类或者调用 Runnable 接口来实现线程,问题是,那个办法更好呢?什么状况下应用它?这个问题很容易答复,如果你晓得 Java 不反对类的多重继承,但容许你调用多个接口。所以如果你要继承其余类,当然是调用 Runnable 接口好了。更多详细信息请点击这里。
5.Thread 类中的 start() 和 run() 办法有什么区别?
start()办法被用来启动新创建的线程,使该被创立的线程状态变为可运行状态。当你调用 run()办法的时候,只会是在原来的线程中调用,没有新的线程启动,start()办法才会启动新线程。如果咱们调用了 Thread 的 run()办法,它的行为就会和一般的办法一样,间接运行 run()办法。为了在新的线程中执行咱们的代码,必须应用 Thread.start()办法。
6.Java 中 Runnable 和 Callable 有什么不同?
Runnable 和 Callable 都代表那些要在不同的线程中执行的工作。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 减少的。它们的次要区别是 Callable 的 call() 办法能够返回值和抛出异样,而 Runnable 的 run()办法没有这些性能。Callable 能够返回装载有计算结果的 Future 对象。
7.Java 中 CyclicBarrier 和 CountDownLatch 有什么不同?
CyclicBarrier 和 CountDownLatch 都能够用来让一组线程期待其它线程。
与 CyclicBarrier 不同的是,CountdownLatch 不能从新应用。
8.Java 内存模型是什么?
Java 内存模型规定和指引 Java 程序在不同的内存架构、CPU 和操作系统间有确定性地行为。它在多线程的状况下尤其重要。Java 内存模型对一个线程所做的变动能被其它线程可见提供了保障,它们之间是后行产生关系。这个关系定义了一些规定让程序员在并发编程时思路更清晰。比方,后行产生关系确保了。
- 线程内的代码可能按先后顺序执行,这被称为程序秩序规定。
- 对于同一个锁,一个解锁操作肯定要产生在工夫上后产生的另一个锁定操作之前,也叫做管程锁定规定。
- 前一个对 volatile 的写操作在后一个 volatile 的读操作之前,也叫 volatile 变量规定。
- 一个线程内的任何操作必须在这个线程的 start()调用之后,也叫作线程启动规定。
- 一个线程的所有操作都会在线程终止之前,线程终止规定。
- 一个对象的终结操作必须在这个对象结构实现之后,也叫对象终结规定。
- 可传递性
9. 什么是线程平安?Vector 是一个线程安全类吗?
如果你的代码所在的过程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行后果和单线程运行的后果是一样的,而且其余的变量的值也和预期的是一样的,就是线程平安的。一个线程平安的计数器类的同一个实例对象在被多个线程应用的状况下也不会呈现计算失误。很显然你能够将汇合类分成两组,线程平安和非线程平安的。Vector 是用同步办法来实现线程平安的, 而和它类似的 ArrayList 不是线程平安的。
10.Java 中什么是竞态条件?
在大多数理论的多线程利用中,两个或两个以上的线程须要共享对同一数据的存取。如果 i 线程存取雷同的对象,并且每一个线程都调用了一个批改该对象状态的办法,将会产生什么呢?能够设想,线程彼此踩了对方的脚。依据线程拜访数据的秩序,可能会产生讹误的对象。这样的状况通常称为竞争条件。
11.Java 中如何进行一个线程?
Java 提供了很丰盛的 API 但没有为进行线程提供 API。JDK 1.0 原本有一些像 stop(), suspend() 和 resume()的管制办法,然而因为潜在的死锁威逼。因而在后续的 JDK 版本中他们被弃用了,之后 Java API 的设计者就没有提供一个兼容且线程平安的办法来进行一个线程。当 run() 或者 call() 办法执行完的时候线程会主动完结,如果要手动完结一个线程,能够用 volatile 布尔变量来退出 run()办法的循环或者是勾销工作来中断线程。
12. 一个线程运行时产生异样会怎么?
如果异样没有被捕捉该线程将会进行执行。Thread.UncaughtExceptionHandler 是用于解决未捕捉异样造成线程忽然中断状况的一个内嵌接口。当一个未捕捉异样将造成线程中断的时候 JVM 会应用 Thread.getUncaughtExceptionHandler()来查问线程的 UncaughtExceptionHandler 并将线程和异样作为参数传递给 handler 的 uncaughtException()办法进行解决。
13.Java 中 notify 和 notifyAll 有什么区别?
这又是一个刁钻的问题,因为多线程能够期待单监控锁,Java API 的设计人员提供了一些办法当期待条件扭转的时候告诉它们,然而这些办法没有齐全实现。notify()办法不能唤醒某个具体的线程,所以只有一个线程在期待的时候它才有用武之地。而 notifyAll()唤醒所有线程并容许他们抢夺锁确保了至多有一个线程能持续运行。
14. 为什么 wait, notify 和 notifyAll 这些办法不在 thread 类外面?
一个很显著的起因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程取得。如果线程须要期待某些锁那么调用对象中的 wait()办法就有意义了。如果 wait()办法定义在 Thread 类中,线程正在期待的是哪个锁就不显著了。简略的说,因为 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。
15. 什么是 ThreadLocal 变量?
ThreadLocal 是 Java 里一种非凡的变量。每个线程都有一个 ThreadLocal 就是每个线程都领有了本人独立的一个变量,竞争条件被彻底消除了。如果为每个线程提供一个本人独有的变量拷贝,将大大提高效率。首先,通过复用缩小了代价昂扬的对象的创立个数。其次,你在没有应用高代价的同步或者不变性的状况下取得了线程平安。
16. 什么是 FutureTask?
在 Java 并发程序中 FutureTask 示意一个能够勾销的异步运算。它有启动和勾销运算、查问运算是否实现和取回运算后果等办法。只有当运算实现的时候后果能力取回,如果运算尚未实现 get 办法将会阻塞。一个 FutureTask 对象能够对调用了 Callable 和 Runnable 的对象进行包装,因为 FutureTask 也是调用了 Runnable 接口所以它能够提交给 Executor 来执行。
17.Java 中 interrupted 和 isInterruptedd 办法的区别?
interrupted() 和 isInterrupted()的次要区别是前者会将中断状态革除而后者不会。Java 多线程的中断机制是用外部标识来实现的,调用 Thread.interrupt()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted()来查看中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查问其它线程的中断状态且不会扭转中断状态标识。简略的说就是任何抛出 InterruptedException 异样的办法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来扭转。
18. 为什么 wait 和 notify 办法要在同步块中调用?
当一个线程须要调用对象的 wait()办法的时候,这个线程必须领有该对象的锁,接着它就会开释这个对象锁并进入期待状态直到其余线程调用这个对象上的 notify()办法。同样的,当一个线程须要调用对象的 notify()办法时,它会开释这个对象的锁,以便其余在期待的线程就能够失去这个对象锁。因为所有的这些办法都须要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步办法或者同步块中被调用。如果你不这么做,代码会抛出 IllegalMonitorStateException 异样。
19.Java 中的同步汇合与并发汇合有什么区别?
同步汇合与并发汇合都为多线程和并发提供了适合的线程平安的汇合,不过并发汇合的可扩展性更高。在 Java1.5 之前程序员们只有同步汇合来用且在多线程并发的时候会导致争用,妨碍了零碎的扩展性。Java5 介绍了并发汇合像 ConcurrentHashMap,不仅提供线程平安还用锁拆散和外部分区等古代技术进步了可扩展性。
20.Java 中堆和栈有什么不同?
为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程严密相干的内存区域。每个线程都有本人的栈内存,用于存储本地变量,办法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片专用内存区域。对象都在堆里创立,为了晋升效率线程会从堆中弄一个缓存到本人的栈,如果多个线程应用该变量就可能引发问题,这时 volatile 变量就能够发挥作用了,它要求线程从主存中读取变量的值。
21. 如何写代码来解决生产者消费者问题?
在事实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产工作供其它线程进行生产,你必须晓得怎么进行线程间通信来解决这个问题。比拟低级的方法是用 wait 和 notify 来解决这个问题,比拟赞的方法是用 Semaphore 或者 BlockingQueue 来实现生产者消费者模型。
22. 如何防止死锁?
Java 多线程中的死锁
死锁是指两个或两个以上的过程在执行过程中,因抢夺资源而造成的一种相互期待的景象,若无外力作用,它们都将无奈推动上来。这是一个重大的问题,因为死锁会让你的程序挂起无奈实现工作,死锁的产生必须满足以下四个条件:
- 互斥条件:一个资源每次只能被一个过程应用。
- 申请与放弃条件:一个过程因申请资源而阻塞时,对已取得的资源放弃不放。
- 不剥夺条件:过程已取得的资源,在末应用完之前,不能强行剥夺。
- 循环期待条件:若干过程之间造成一种头尾相接的循环期待资源关系。
防止死锁最简略的办法就是阻止循环期待条件,将零碎中所有的资源设置标记位、排序,规定所有的过程申请资源必须以肯定的程序(升序或降序)做操作来防止死锁。
23.Java 中活锁和死锁有什么区别?
这是上题的扩大,活锁和死锁相似,不同之处在于处于活锁的线程或过程的状态是一直扭转的,活锁能够认为是一种非凡的饥饿。一个事实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,然而因为避让的方向都一样导致最初谁都不能通过走廊。简略的说就是,活锁和死锁的次要区别是前者过程的状态能够扭转然而却不能继续执行。
24. 怎么检测一个线程是否领有锁?
在 java.lang.Thread 中有一个办法叫 holdsLock(),它返回 true 如果当且仅当以后线程领有某个具体对象的锁。
25.JVM 中哪个参数是用来控制线程的栈堆栈小的
这个问题很简略,-Xss 参数用来控制线程的堆栈大小。你能够查看 JVM 配置列表来理解这个参数的更多信息。
26.Java 中 synchronized 和 ReentrantLock 有什么不同?
Java 在过来很长一段时间只能通过 synchronized 关键字来实现互斥,它有一些毛病。比方你不能扩大锁之外的办法或者块边界,尝试获取锁时不能中途勾销等。Java 5 通过 Lock 接口提供了更简单的管制来解决这些问题。ReentrantLock 类实现了 Lock,它领有与 synchronized 雷同的并发性和内存语义且它还具备可扩展性。
27.Thread 类中的 yield 办法有什么作用?
yield 办法能够暂停以后正在执行的线程对象,让其它有雷同优先级的线程执行。它是一个静态方法而且只保障以后线程放弃 CPU 占用而不能保障使其它线程肯定能占用 CPU,执行 yield()的线程有可能在进入到暂停状态后马上又被执行。
28.Java 中 Semaphore 是什么?
Java 中的 Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量保护了一个许可汇合。如有必要,在许可可用前会阻塞每一个 acquire(),而后再获取该许可。每个 release()增加一个许可,从而可能开释一个正在阻塞的获取者。然而,不应用理论的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的口头。信号量经常用于多线程的代码中,比方数据库连接池。
29. 如果你提交工作时,线程池队列已满。会时发会生什么?
这个问题问得很狡猾,许多程序员会认为该工作会阻塞直到线程池队列有空位。事实上如果一个工作不能被调度执行那么 ThreadPoolExecutor’s submit()办法将会抛出一个 RejectedExecutionException 异样。
30.Java 线程池中 submit() 和 execute()办法有什么区别?
两个办法都能够向线程池提交工作,execute()办法的返回类型是 void,它定义在 Executor 接口中, 而 submit()办法能够返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩大了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些办法。
31. 你对线程优先级的了解是什么?
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具备优先权,但这依赖于线程调度的实现,这个实现是和操作系统相干的(OS dependent)。咱们能够定义线程的优先级,然而这并不能保障高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1 -10),1 代表最低优先级,10 代表最高优先级。
32.Java 中的 ReadWriteLock 是什么?
一般而言,读写锁是用来晋升并发程序性能的锁拆散技术的成绩。Java 中的 ReadWriteLock 是 Java 5 中新增的一个接口,一个 ReadWriteLock 保护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的状况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你能够应用 JDK 中的 ReentrantReadWriteLock 来实现这个规定,它最多反对 65535 个写锁和 65535 个读锁。
33. 单例模式的双检锁是什么?
这个问题在 Java 面试中常常被问到,然而面试官对答复此问题的满意度仅为 50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5 是如何对它修改的。它其实是一个用来创立线程平安的单例的老办法,当单例实例第一次被创立时它试图用单个锁进行性能优化,然而因为太过于简单在 JDK1.4 中它是失败的。
34. 如何在 Java 中创立线程平安的 Singleton?
这是下面那个问题的后续,如果你不喜爱双检锁而面试官问了创立 Singleton 类的代替办法,你能够利用 JVM 的类加载和动态变量初始化特色来创立 Singleton 实例,或者是利用枚举类型来创立 Singleton。
35. 线程之间是如何通信的?
当线程间是能够共享资源时,线程间通信是协调它们的重要的伎俩。Object 类中 wait()notify()notifyAll()办法能够用于线程间通信对于资源的锁的状态。
36. 如何创立守护线程?
应用 Thread 类的 setDaemon(true)办法能够将线程设置为守护线程,须要留神的是,须要在调用 start()办法前调用这个办法,否则会抛出 IllegalThreadStateException 异样。
37. 同步办法和同步块,哪个是更好的抉择?
同步块是更好的抉择,因为它不会锁住整个对象(当然你也能够让它锁住整个对象)。同步办法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们进行执行并须要期待取得这个对象上的锁。
38. 锁降级过程是什么?
锁降级程序
- 无锁
- 偏差锁
- 轻量级锁
- 重量级锁
首先是无锁,判断是否要加锁,加锁后,降级为偏差锁,偏差锁意思为如果一个线程取得锁,再次申请的时候不须要再去取得锁,如果还有其余线程来拜访,就降级为轻量级锁,通过 CAS 去取得锁,如果 CAS 失败,自旋到肯定次数后如果还没有胜利就降级为重量级锁。(自旋超过了限定次数,默认是 10 次没有胜利取得锁,就该当挂起线程)
39. 什么是 volatile?
Volatile 是 Java 虚拟机提供的轻量级的同步机制
1. 保障可见性
2. 不保障原子性
3. 禁止指令重排(有序性)
(1)、为什么不反对原子性?
因为批改 volatile 变量分为以下四步:
- 读取 volatile 变量到本地内存。
- 批改变量值
- 将值写回主内存。
- 插入内存屏障,即 Lock 指令,让其余线程可见。
因为在前三步不是线程平安的,不能保障取值和写回之间没有被其余线程批改。原子性须要锁来保障。
(2)、那如果保障原子性?
1. 应用 synchronized
2. 应用 Atomic 原子类
(3)、为什么 Atomic 原子类保障原子性?
因为应用了 CAS。
CAS 是:比拟并替换
boolean AtomicInteger.compareAndSwap(期望值,更新值)
是一条 CPU 并发原语。
应用 Unsafe 类,大多数应用 native 润饰的办法。
Unsafe 类依据内存偏移地址去获取数据。
(4)、原子类 AtomicInteger 的 ABA 问题谈谈?
ABA 问题就是在主内存中本来是 A 起初有另外一个线程批改为了 B 后又改回了 A,第一个线程回来看后还是 A 认为没有变动,实际上曾经有了变动。
如何解决 ABA 问题?
AtomicStampedReference
减少版本号,进行版本号判断。
39. 创立线程池有哪几种?
- Executors.newFixedThreadPool(int) : 固定线程池大小。
- Executors.newSingleThreadExecutor():单例线程池
- Executors.newChachedThreadPool():可扩容缓存的线程池。
40. execute 和 submit 有什么区别?
execute 和 submit 都属于线程池的办法,execute 只能提交 Runnable 类型的工作,而 submit 既能提交 Runnable 类型工作也能提交 Callable 类型工作。
execute 会间接抛出工作执行时的异样,submit 会吃掉异样,可通过 Future 的 get 办法将工作执行时的异样从新抛出。
execute 所属顶层接口是 Executor,submit 所属顶层接口是 ExecutorService,实现类 ThreadPoolExecutor 重写了 execute 办法, 抽象类 AbstractExecutorService 重写了 submit 办法。
41.ThreadPoolExcutor 参数都有哪些?
42. 说说线程池底层原理?
- 当调用 execute()办法增加一个申请工作时,线程池会走如下判断:
- 如果以后 corePoolSize 没满,间接创立线程运行工作。
- 如果 corePoolSize 满了,那么将线程放入阻塞队列。
- 如果阻塞队列满了且小于 maxmumPoolSize,则创立非核心线程来运行该工作。
- 如果阻塞队列满了,并且 maxmumPoolSize 也满了,则会应用回绝策略来执行。
- 当一个工作实现时候,会从队列中获取下一个来执行工作。
- 当一个线程鸿鹄之志超过肯定工夫,则会被停掉,当所有线程工作实现后,线程池会膨胀到 corePoolSize 大小。
43. 线程池 4 种回绝策略?
- AbortPolicy(默认):间接抛出 RejectedExecutionException 异样阻止零碎失常运行。
- CallerRunsPolicy:不摈弃工作也不抛出异样,将工作回退给调用方。
- DiscardOldestPolicy:摈弃队列中期待最久的工作,而后将当前任务减少进去。
- DiscardPolicy:间接抛弃工作,不进行任何解决也不抛出异样。
44. 为什么不容许应用 Executors 来创立线程池呢?
- FixedThreadPool 和 SingleThreadPool:
容许的申请队列长度为 Integer.MAX_VALUE,可能会沉积大量的申请,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool:
容许创立的线程数量为 Integer.MAX_VALUE,可能会沉积大量的线程,从而导致 OOM。
45. 如何创立自定义线程池?
public static void main(String[] args) {
ExecutorService theadPool = new ThreadPoolExecutor(
2,
5,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); // 默认回绝策略
try {for (int i = 0; i < 30; i++) {theadPool.execute(() -> System.out.println(Thread.currentThread().getName() +"来办理业务"));
}
}catch (Exception e) {e.printStackTrace();
}finally {theadPool.shutdown();
}
}
46. 阻塞队列有哪些?
- ArrayBlockingQueue:是一个基于数组构造的有界阻塞队列,此队列按 FIFO(先进先出)准则对数据进行排序。
- LinkedBlockingQueue:是一个基于链表构造的有界阻塞队列,此队列按 FIFO 排序元素,吞吐量要高于 ArrayBlockingQueue。默认大小为 int 最大值(因为读写能够同时进行,不是同一把锁)
- SynchroniousQueue:一个不存储元素的队列,每个插入操作都要等到另一个线程的移除操作。不然就始终阻塞。吞吐量要高于 LinkedBlockingQueue。
47. 为什么须要 BlockingQueue?
益处是咱们不须要再关怀什么时候阻塞线程,什么时候唤醒线程,阻塞队列会在满的时候主动阻塞增加线程。在空的时候阻塞获取线程。一旦阻塞队列不再满了,主动唤醒增加线程进行增加。
48.BlockingQueue 外围办法有哪些?
49. 如何防止虚伪唤醒?
应用 while,不要应用 if。因为 while 会在唤醒后从新走一下条件判断。
50. Lock 和 synchronized 有什么区别?
- synchronized 是 JVM 层面,底层是通过 monitor 对象来实现,wait/notify 等办法都依赖于 monitor 对象。和 monitorexit。蕴含失常退出和异样退出。Lock 是具体的一个接口,是 api 层面的锁。
- synchronized 不须要用户手动开释锁,lock 须要手动开释锁,不然可能导致死锁。
- synchronized 不可中断,lock 能够设置超时办法 trylock() , 调用 interrupt()办法可中断。
- synchronized 是非偏心锁。lock 能够是偏心或者不偏心锁。
- lock 能够精准唤醒某个线程,而 synchronized 只能全副或者随机唤醒。
JVM 篇
1.JVM 垃圾回收的时候如何确定垃圾?是否晓得什么是 GC Roots?
GC roots就是一组必须沉闷的援用
java 应用了可达性剖析的办法来判断对象是否是垃圾。
基本思路就是从 GC Roots 对象作为终点向下搜寻,如果一个对象到 GC Roots 没有任何的援用链时候,则阐明对象不可用。
2. 能够作为 GC Roots 的对象有哪些?
虚拟机栈。办法区中的类动态属性的对象。办法区中常量援用的对象。本地办法栈中 native 办法援用的对象。
3. 如何盘点查看 JVM 零碎默认值
- Boolean 类型 公式:-XX:+ 或者 - 某个属性值 + 代表开启 - 代表敞开 例子:jinfo -flag PrintGCDetails 端口 -XX:-PrintGCDetails
- KV 设值类型 公式:-XX: 属性 key= 属性值 value 例子:-XX:MetaspaceSize=128m
java -XX:+PrintFlagsInitial 查看初始值 java -XX:+PrintFlagsFinal 查看最终值
4. 如何在运行 java 命令时候,同时打印参数
java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m 运行类名
5. 新生代转移到老年代的触发条件
- 长期存活的对象。
- 大对象间接进入老年代。
- 在年老代 GC 后依然放不下的对象。
- 动静年龄判断,大于等于某个年龄的对象超过了 survivor 空间一半。
6. 什么时候产生 YoungGC 和 FulGC
- Eden 空间占满了,会触发 Young GC
- Minor(Scavenge)GC 后依然存活的对象会被复制到 S0(survivor 0)中去。这样 Eden 就被清空能够调配给新的对象。又触发了一次 Minor GC,
S0 和 Eden 中存活的对象被复制到 S1 中,并且 S0 和 Eden 被清空。在同一时刻, 只有 Eden 和一个 Survivor Space 同时被操作。当每次对象从 Eden 复制到 Survivor Space 或者从 Survivor Space 中的一个复制到另外一个,有一个计数器会主动增加值。默认状况下如果复制产生超过 15. 次,JVM 会进行复制并把他们移到老年代中去. 同样的如果一个对象不能在 Eden 中被创立,它会间接被创立在老年代中。
-
FulGC:
- a) 老年代(Tenured)被写满;
- b) 元空间满;
- System.gc()被显示调用;
- 上一次 GC 之后 Heap 的各域调配策略动态变化。
7. 类加载过程是什么?
类的加载分为 加载、链接(包含验证、筹备、解析三步)、初始化。
- 加载是文件到内存的过程。通过类的齐全限定名查找此类字节码文件,并利用字节码文件创建一个 Class 对象。
- 验证是否合乎虚拟机要求,保障不会危害虚拟机本身平安,蕴含四种:文件格式验证,元数据验证,字节码验证,符号援用验证。
筹备阶段是进行内存调配。为类变量也就是类中由 static 润饰的变量分配内存,并且设置初始值,这里要留神,初始值是 0 或者 null,而不是代码中设置的具体值,代码中设置的值是在初始化阶段实现的。另外这里也不蕴含用 final 润饰的动态变量,因为 final 在编译的时候就会调配了。
解析次要是解析字段、接口、办法。次要是将常量池中的符号援用替换为间接援用的过程。间接援用就是间接指向指标的指针、绝对偏移量等。
- 初始化就是实现对动态块和动态变量赋值。
8. 对象的加载过程?
①类加载查看:虚拟机遇到一条 new 指令时,首先将去查看这个指令的参数是否能在常量池中定位到这个类的符号援用,并且查看这个符号援用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
举个例子,咱们的 string 就算是对象创立,既是 String str= new String(“abc”); 首先也会在常量池中定位是否存在值。若不存在,则会在常量池也建设一个 abc.
②分配内存:在类加载查看通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载实现后便可确定,为对象调配空间的工作等同于把一块确定大小的内存从 Java 堆中划分进去。调配形式有“指针碰撞”和“闲暇列表”两种,抉择那种调配形式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采纳的垃圾收集器是否带有压缩整顿性能决定。
③初始化零值:内存调配实现后,虚拟机须要将调配到的内存空间都初始化为零值(不包含对象头),这一步操作保障了对象的实例字段在 Java 代码中能够不赋初始值就间接应用,程序能拜访到这些字段的数据类型所对应的零值。
④设置对象头:初始化零值实现之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何能力找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。这些信息寄存在对象头中。另外,依据虚拟机以后运行状态的不同,如是否启用偏差锁等,对象头会有不同的设置形式。
⑤执行 init 办法:在下面工作都实现之后,从虚拟机的视角来看,一个新的对象曾经产生了,但从 Java 程序的视角来看,对象创立才刚开始,办法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 办法,把对象依照程序员的志愿进行初始化,这样一个真正可用的对象才算齐全产生进去。
9.GC 垃圾回收四大算法?
- 援用计数法
- 复制算法
年老代中 Minor GC 应用的就是复制算法。
- 标记革除(Mark-Sweep)
老年代 个别是由标记革除,或者是标记革除和标记整顿实现。
先扫描须要被回收的对象,而后标记起来,进行整体革除。
长处:不须要额定的空间。
毛病:会造成内存碎片。
-
标记压缩(Mark-Compact)
老年代 个别是由标记革除,或者是标记革除和标记整顿实现。
serial 串行垃圾回收器:它为单线程环境设置且只应用一个线程进行垃圾回收,会暂停所有用户线程。不适于用服务器。
Parallel 并行垃圾回收器:多个垃圾收集线程并行工作,此时用户线程是暂停的,实用于弱交互场景。
CMS 并发标记革除:用户线程和垃圾收集线程同时执行,不肯定是并行也可能是交替执行。实用于对响应工夫有要求的场景。
10. 怎么查看默认的垃圾收集器是哪个?
java -XX:+PrintCommandLineFlags -version
11. 串行垃圾回收器:Serial
只应用一个线程进行垃圾回收,会暂停所有用户线程。是 java 虚拟机运行在 32 位 client 模式下的默认新生代垃圾收集器。
长处:简略高效。
毛病:须要暂停其余线程。
对应的 JVM 参数是 -XX:+UseSerialGC
开启后就会在 Young 区和 Old 区应用。
新生代应用的是复制算法,老年代应用的是标记整顿(压缩)算法。12. ParNew(并行)收集器
应用多线程进行垃圾回收,在垃圾收集时,会 Stop-The-World 暂停其余所有工作线程直到他收集完结。
ParNew 收集器其实就是 Serial新生代
的多线程版本老年代还是单线程。
对应的 JVM 参数是 -XX:+UseParNewGC开启后只影响新生代的收集,不影响老年代。
新生代应用的是复制算法,老年代应用的是标记整顿(压缩)算法。13. Parallel Scavenge 并行收集器
是一个新生代的垃圾收集器,应用的是复制算法。
在新生代和老年代都是并行化。
JDK8 应用的就是 Parallel + Parallel Old 收集器
UseParallelGC 即 Parallel Scavenge 和 Parallel Old。
对应的 JVM 参数是 -XX:+UseParallelGC 或者 -XX:+UseParallelOldGC
开启该参数后:新生代应用的是复制算法,老年代应用的是标记整顿(压缩)算法。14. CMS 收集器(Concurrent Mark Sweep:并发标记革除)
是一种以获取最短回收进展工夫为指标的收集器。
并发收集低进展,能够和用户线程一起执行。
对应的 JVM 参数是 -XX:+UseConcMarkSweepGC 开启后主动将 -XX:+UseParNewGC 关上。
应用 ParNew(Young 区)+CMS(Old 区)+ Serial Old 收集器组合,Serial Old 将作为 CMS 出错的后备收集器。
CMS 的四步过程:- 初始标记:只是标记一下 GC Roots 能关联的对象,速度很快,依然须要暂停其余线程。
- 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不须要暂停其余线程。次要标记全副对象。
- 从新标记:为了批改在并发标记期间,产生变动的对象,进行从新标记,须要暂停所有线程。
- 并发革除:革除 GC Roots 中不可达的对象,不须要暂停其余线程。
长处:并发收集低进展
毛病:并发执行,对 CPU 资源压力大。采纳的是标记革除算法,会产生内存碎片。
## 15. G1 收集器
是一个能够在年老代和老年代一起应用的垃圾收集器,能够并发执行,设计的目标是为了代替 CMS 收集器
G1 采纳的是标记整顿(压缩)算法,部分是通过复制算法,不会产生内存碎片。
长处:
- G1 是一个有整顿内存过程的垃圾收集器,不会产生很多内存碎片。
- G1 的 STW 更可控,在进展工夫上减少了预测机制,用户能够指定冀望进展的工夫。
- 将内存划分为多个独立的子区域,其外部在小范畴内还是要进行年老代和老年代的辨别。
底层原理:
- 每个块的大小为 1MB-32MB,最多有 2048 个区域。
- 如果有大对象,默认间接调配到老年代,G1 划分了一个 Humongous 区来寄存,如果一个 H 区不够的话,会找间断的 H 区来寄存。为了能找到间断的 H 区,有时候不得不 Full GC。
## 16. 生产环境如果 CPU 过高,怎么定位?
先用 top 命令查看 cpu 占比最高的,而后用 jps 来查看是那一个后盾过程。
jps -l
而后应用
ps - mp 过程 ID -o THREAD,tid,time
查看是具体哪个线程占比最高。
而后应用
printf "%x\n" 线程 ID
失去转换后的 16 进制格局 小写。
而后应用
jstack 过程 ID | grep 线程 id -A60
## 17. 什么是双亲委派机制?
java 虚拟机对 class 文件采纳的是按需加载,只有当须要用到的时候才会加载到内存生成 class 对象,采纳的是双亲委派机制,把申请交给父类解决。是一种工作委派模式。
长处:
- 防止类的反复加载。
- 防止外围 API 被篡改。
Spring 篇
1.Spring 是什么?
Spring 是一个轻量级的 Ioc 和 AOP 容器框架,简化开发,只须要关注业务需要。
2. Spring 的长处?
- Spring 属于低侵入式设计,代码净化极低。
- Spring 的 DI 机制能够让容器帮咱们治理对象之间的依赖,缩小组件之间的耦合度。
- Spring 还提供了 AOP 技术能够将日志,通用工作进行集中管理,能够更好的复用。
3. Spring 的 AOP 了解?
AOP 就是面向切面,用于将和业务无关的逻辑代码抽取并封装成一个可用的模块,缩小反复代码,升高模块之间的耦合度,进步零碎的可维护性。
AOP 实现的关键在于代理模式,AOP 代理次要分为动态代理和动静代理,动态代理为 AspectJ;动静代理为 Spring AOP。
- AspectJ 是动态代理的加强,就是 AOP 框架会在编译阶段生成 AOP 代理类,将 AspectJ 织入进 Java 字节码中,运行的时候就是加强后的 AOP 对象。
- Spring AOP 应用的是动静代理,所谓动静代理就是 AOP 不会批改字节码,而是每次运行中在内存长期为办法生成一个 AOP 对象,这个 AOP 对象蕴含了指标对象的全副办法,并且在特定的切点做了加强解决,并回调原始对象办法。
-
动静代理分为 JDK 和 CGLib 代理:
- jdk 动静代理是只提供接口的代理,不反对类的代理。外围 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()办法反射来调用指标代码,动静的将和逻辑和业务编织在一起;而后利用 InvocationHandler 动态创建一个合乎某一接口的实例生成指标类的代理对象。
- 如果代理类没有实现 InvocationHandler 接口,Spring AOP 会抉择 CGLib 代理,能够运行时动静的生成一个指定类的子类对象,并笼罩其中办法增加加强代码,CGLib 是通过继承来做动静代理,如果某个类被标记为 final, 则无奈应用 CGLib 做动静代理。
4.Spring 的 IOC 了解?
IOC 就是管制反转,就是对象创立机会由容器来管制,益处就是不须要被动 new 对象,spring 会自动生产,应用 java 的反射机制,依据配置文件去创建对象。
有三种注入形式:1. 结构器注入,2.setter 注入,3. 注解注入。
5. BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory 和 ApplicationContext 都是 Spring 的外围接口,都能够当做 Spring 的容器。BeanFactroy 是应用的延时加载来注入 bean,只有当应用某个 bean 时候,才会进行加载,而 ApplicationContext 是在容器启动的时候就一次性创立所有 Bean。
6. 请解释 Spring Bean 的生命周期?
首先说一下 Servlet 的生命周期:实例化,初始 init,接管申请 service,销毁 destroy;
- 实例化 Bean
- 设置对象属性(依赖注入)
- 解决 Aware 接口
- BeanPostProcessor(是否有自定义解决)
- 调用 init-method
- 清理,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()办法
- 销毁
7.Spring 的几种作用域?
- Singleton:默认,每个容器只有一个,单例。
- prototype:每个申请是一个实例。
- request:每一个申请是一个实例,申请完结后进行垃圾回收。
- session:每个 session 中有一个 bean,如果 session 生效,bean 随之生效。
- global-session:全局作用域。
8.Spring 的单例 bean 是线程平安的吗?
不是,只能自行保障线程平安。
9.Spring 如何解决线程并发问题?
采纳 ThreadLocal 进行解决,解决线程平安问题。
10.Spring 主动拆卸 @Autowired 和 @Resource 之间的区别?
- Autowired 默认依照类型拆卸注入,默认状况下依赖的对象必须存在。
- Resource 默认依照名称来进行拆卸,只有当找不到名称匹配的 bean 的时候才会依照类型来注入。
11.Spring 框架中都用到了哪些设计模式?
(1)工厂模式:BeanFactory 就是简略工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean 默认为单例模式。
(3)代理模式:Spring 的 AOP 性能用到了 JDK 的动静代理和 CGLIB 字节码生成技术;
(4)模板办法:用来解决代码反复的问题。比方. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态产生扭转时,所有依赖于它的对象都会失去告诉被制动更新,如 Spring 中 listener 的实现 –ApplicationListener。
12. spring 的事务流传行为?
① PROPAGATION_REQUIRED:如果以后没有事务,就创立一个新事务,如果以后存在事务,就退出该事务,该设置是最罕用的设置。
② PROPAGATION_SUPPORTS:反对以后事务,如果以后存在事务,就退出该事务,如果以后不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:反对以后事务,如果以后存在事务,就退出该事务,如果以后不存在事务,就抛出异样。
④ PROPAGATION_REQUIRES_NEW:创立新事务,无论以后存不存在事务,都创立新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务形式执行操作,如果以后存在事务,就把以后事务挂起。
⑥ PROPAGATION_NEVER:以非事务形式执行,如果以后存在事务,则抛出异样。
⑦ PROPAGATION_NESTED:如果以后存在事务,则在嵌套事务内执行。如果以后没有事务,则按 REQUIRED 属性执行。
13. Spring 中的隔离级别?
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,应用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,容许另外一个事务能够看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保障一个事务批改的数据提交后能力被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可反复读,保障一个事务批改的数据提交后能力被另一事务读取,然而不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中齐全看不到其余事务对数据库所做的更新。
14.Spring 告诉有哪些类型?
- 前置告诉
- 返回后告诉
- 抛出异样告诉
- 后置告诉
- 盘绕告诉
15.Spring MVC 运行流程
- Spring MVC 会将所有申请交给 DispatchServlet。
- DispatcherServlet 查问一个或多个 HandlerMapping,找到解决申请的 controller。
- 而后 DispatcherServlet 将申请提交到指标 Controller。
- Controller 进行业务逻辑解决后,返回一个 ModelANdView。
- Dispatcher 查问一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。
- 视图渲染后返回给客户端。
16.Spring 单例实现原理
Spring 对 Bean 实例创立采纳的是单例注册表进行实现的,这个注册表缓存是 ConcurrentHashMap 对象。
17. 请介绍你相熟的 Spring 外围类,并阐明有什么作用?
- BeanFactory: 产生一个新的实例,实现单例模式
- ApplicationContext:提供框架的实现,因为是 BeanFactory 的子接口所以蕴含 BeanFactory 的所有性能。
18. 介绍一下 Spring 的事务的理解?
应用 @Transactional 注解,申明式注解是建设在 AOP 上,对办法的前后进行拦挡,而后依据指标办法来判断是提交还是回滚事务。
19. 介绍一下 Spring 的事务实现形式?
1、编程式事务,在代码中硬编码。(不举荐应用)
2、申明式事务,在配置文件中配置(举荐应用)
申明式事务又分为两种:
a、基于 XML 的申明式事务
b、基于注解的申明式事务
20.Spring Beans 了解?
Spring Beans 是 Spring 利用的外围对象,由 IOC 容器实例化、组装、治理。这些对象通过容器中配置的元数据进行创立。
21. 5 种主动拆卸的各种模式?
用来拆卸 Beans 之间的依赖注入
- no:默认的形式是不进行主动拆卸,通过手工 ref 属性来拆卸。
- byName:通过参数名主动拆卸,Spring 容器会试图匹配、拆卸和该 bean 属性雷同名字的 bean。
- byType: 通过参数的数据类型主动拆卸,Spring 容器试图匹配和拆卸和该 bean 的属性类型一样的 bean,如果有多个 bean 符合条件则报错。
- constructor:实用于结构器雷同参数的 bean,如果在容器中没有找到雷同的 bean,则抛出异样。
- autodetect:主动探测应用结构器还是 byType 主动拆卸,优先结构器,如果没有找到绝对应的结构器,则实用 byType 进行主动拆卸。
22.IOC 有什么长处?
单例,提早加载,松耦合。
23.SpringBoot 的外围启动主函数 (main 函数) 的作用. 用到哪些注解. 注解的作用.
- SpringBootConfiguration:组合了 @Configuration 注解,实现了配置文件性能。
- EnableAutoConfiguration:关上主动配置。
- ComponentScan:Spring 组件扫描。
24.springboot 中罕用的 starter 的组件有哪些?
- spring-boot-starter-parent //boot 我的项目继承的父我的项目模块.
- spring-boot-starter-web //boot 我的项目集成 web 开发模块.
- spring-boot-starter-tomcat //boot 我的项目集成 tomcat 内嵌服务器.
- spring-boot-starter-test //boot 我的项目集成测试模块.
- mybatis-spring-boot-starter //boot 我的项目集成 mybatis 框架.
- spring-boot-starter-jdbc //boot 我的项目底层集成 jdbc 实现数据库操作反对.
25. 在 Spring AOP 中,关注点和横切关注的区别是什么?
关注点是利用中一个模块的行为,一个关注点可能会被定义成一个咱们想实现的一个性能。
横切关注点是一个关注点,此关注点是整个利用都会应用的性能,并影响整个利用,比方日志,平安和数据传输,简直利用的每个模块都须要的性能。因而这些都属于横切关注点。
26. 切点
切入点是一个或一组连接点,告诉将在这些地位执行。能够通过表达式或匹配的形式指明切入点。
MySQL 篇
1.MySQL 中 myisam 与 innodb 的区别,至多 5 点
(1)、问 5 点不同;
- 1>.InnoDB 反对事物,而 MyISAM 不反对事物
- 2>.InnoDB 反对行级锁,而 MyISAM 反对表级锁
- 3>.InnoDB 反对 MVCC, 而 MyISAM 不反对
- 4>.InnoDB 反对外键,而 MyISAM 不反对
- 5>.InnoDB 不反对全文索引,而 MyISAM 反对。
(2)、innodb 引擎的 4 大个性
插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(ahi)、预读(read ahead)。
(3)、2 者 select count(*)哪个更快,为什么
myisam 更快,因为 myisam 外部保护了一个计数器,能够间接调取。
2.innodb 的日志的实现有哪些?
谬误日志:记录出错信息,也记录一些正告信息或者正确的信息。
查问日志:记录所有对数据库申请的信息,不管这些申请是否失去了正确的执行。
慢查问日志:设置一个阈值,将运行工夫超过该值的所有 SQL 语句都记录到慢查问的日志文件中。
二进制日志:记录对数据库执行更改的所有操作。
中继日志:中继日志也是二进制日志,用来给 slave 库复原
事务日志:重做日志 redo 和回滚日志 undo
3.sql 优化各种办法
(1)、explain 进去的各种 item 的意义;
select_type
示意查问中每个 select 子句的类型
type
示意 MySQL 在表中找到所需行的形式,又称“拜访类型”
possible_keys
指出 MySQL 能应用哪个索引在表中找到行,查问波及到的字段上若存在索引,则该索引将被列出,但不肯定被查问应用
key
显示 MySQL 在查问中理论应用的索引,若没有应用索引,显示为 NULL
key_len
示意索引中应用的字节数,可通过该列计算查问中应用的索引的长度
ref
示意上述表的连贯匹配条件,即哪些列或常量被用于查找索引列上的值
Extra
蕴含不适宜在其余列中显示但非常重要的额定信息
4.MySQL 中 InnoDB 引擎的行锁是通过加在什么上实现 (或称实现) 的?为什么是这样子的?
InnoDB 是基于索引来实现行锁
例: select * from tab_with_index where id = 1 for update;
for update 能够依据条件来实现行锁锁定, 并且 id 是有索引键的列,
如果 id 不是索引键那么 InnoDB 将实现表锁。
5. 什么是索引?
索引是一种数据结构,不便咱们疾速的进行数据查找。
6. 索引是什么样的数据结构?
InnoDB 应用的是 B + 树索引。
7. 什么是回表查问?
例如:
select * from t where name=’lisi’;
是如何执行的呢?
如粉红色门路,须要扫码两遍索引树:
(1)先通过一般索引定位到主键值 id=5;
(2)再通过汇集索引定位到行记录;
这就是所谓的回表查问,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
①汇集索引(聚簇索引):以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即便你不创立主键,零碎也会帮你创立一个隐式的主键。
这是因为 InnoDB 是把数据寄存在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。
这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,咱们称之为汇集索引。
②非汇集索引(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,咱们称之为非汇集索引。
非汇集索引与汇集索引的区别在于非汇集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据咱们还须要依据主键再去汇集索引中进行查找,这个再依据汇集索引查找数据的过程,咱们称为回表。
明确了汇集索引和非汇集索引的定义,咱们应该明确这样一句话:数据即索引,索引即数据。
8.Hash 树和 B + 树有什么区别?
Hash 树底层就是 Hash 表,进行查找时,调用一次 hash 函数就能够取得到响应的键值,之后进行回表查问取得理论数据。
B+ 树等底层是多路均衡查找树,对每一次查问都是从根节点登程,找到叶子节点后取得所查的键值,而后依据查问判断是否要回表操作。
- hash 索引进行等值查问更快,无奈进行范畴查问。(因为 hash 索引在通过 hash 函数建设索引之后,索引的程序与原程序无奈保持一致)
- hash 不反对含糊查问以及多列索引最左前缀匹配。因为 hash 不晓得 AAAA 和 AAAAB 索引有没有相关性。
- hash 任何时候都要进行回表查问,而 B + 树在某些条件下只用索引就能够实现查问。
- hash 如果某个键值存在大量反复时候会产生 hash 碰撞,效率会很低,而 B + 树的查问比较稳定,每次都是从根节点到子节点,且树的高度较低。
9.B+ 树和 B 树有什么区别?
B 树,叶子节点和非叶子节点都存储数据。
B+ 树,只有在叶子节点上存储数据。
B 树是有序数组 + 均衡多叉树。
B+ 树是有序数组链表 + 均衡多叉树。
B+ 树只须要便当叶子节点就能够对整棵树进行遍历,而 B 树须要查问非叶子节点效率不高。
10. 什么是汇集索引?
在 B + 树的索引种,叶子节点除了可能存储了以后的 K 值,也可能存储了以后 K 值和整行数据,这就是汇集索引和非汇集索引。
在 InnoDB 中,只有主键是汇集索引,如果没有主键,则筛选一个惟一键来建设汇集索引,如果没有惟一键,则隐式的生成一个键来建设汇集索引。
当应用汇集索引时候,对应的叶子节点能够获取整行数据,所以不必再次回表查问。
11. 非汇集索引肯定会回表查问吗?
不肯定,如果查问的字段如果全副命中索引,就没有必要进行回表查问。
12. 在建设索引的时候,须要思考哪些因素?
- 字段的应用频率。
- 联结索引的话,还须要思考程序。
- 不常常计算的字段。
13. 联结索引是什么?为什么须要留神联结索引的程序?
Mysql 能够用多个字段同时建设一个索引,叫做联结索引。
在联结索引中如果命中索引,则须要依照建设索引的字段程序挨个应用,否则无奈命中索引。
MySQL 应用索引时须要索引有序, 假如当初建设了 ”name,age,school” 的联结索引, 那么索引的排序为: 先依照 name 排序, 如果 name 雷同, 则依照 age 排序, 如果 age 的值也相等, 则依照 school 进行排序.
能够看到 a 的值是有程序的,1,1,2,2,3,3,而 b 的值是没有程序的 1,2,1,4,1,2。所以 b = 2 这种查问条件没有方法利用索引,因为联结索引首先是按 a 排序的,b 是无序的。
同时咱们还能够发现在 a 值相等的状况下,b 值又是按顺序排列的,然而这种程序是绝对的。所以最左匹配准则遇上范畴查问就会进行,剩下的字段都无奈应用索引。例如 a = 1 and b = 2 a,b 字段都能够应用索引,因为在 a 值确定的状况下 b 是绝对有序的,而 a >1and b=2,a 字段能够匹配上索引,但 b 值不能够,因为 a 的值是一个范畴,在这个范畴中 b 是无序的。
最左匹配准则: 最左优先,以最右边的为终点任何间断的索引都能匹配上。同时遇到范畴查问 (>、<、between、like) 就会进行匹配。
14. 哪些状况会让索引生效?
- 参加数学运算或者函数。
- 在 like 右边应用了 %。
- 当 mysql 剖析全表扫描比应用索引快时。
- 当应用联结索引时,不合乎最左前缀匹配准则。
- varchar 不加引号。
- 应用 not <>!= 都会生效。
15. 什么是事务?
事务就是一系列的操作,要么全副胜利,要么全副失败。须要合乎 ACID 个性。
16.ACID 是什么?
A=Atomicity
原子性:要么全副胜利,要么全副失败。不可能只执行一部分操作。
C=Consistency
一致性:零碎 (数据库) 总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态。
I=Isolation
隔离性:通常来说一个事务在齐全提交之前,对其余事务是不可见的。
D=Durability
持久性:一旦事务提交,那么就永远是这样子了,哪怕零碎解体也不会影响到这个事务的后果。
17. 同时有多个事务在进行会怎么样?
- 脏读:A 事务读取到了 B 事务未提交的内容,而 B 事务前面进行了回滚。
- 不可反复读:当设置 A 事务只能读取 B 事务曾经提交的局部,会造成 A 事务内的两次查问后果不一样,因为 B 在此期间对事务进行了提交。
- 幻读:A 事务读取了一个范畴的内容,同时 B 事务在此期间插入了一条数据,造成”幻觉“。
18. 如何解决这些问题?MySQL 的事务隔离级别理解吗?
MySQL 的四种隔离级别如下:
- 未提交读(READ UNCOMMITTED)
这个隔离级别下,其余事务能够看到本事务没有提交的局部批改。因而会造成脏读的问题(读取到了其余事务未提交的局部, 而之后该事务进行了回滚)。
这个级别的性能没有足够大的劣势,然而又有很多的问题,因而很少应用。
- 已提交读(READ COMMITTED)
其余事务只能读取到本事务曾经提交的局部。这个隔离级别有不可反复读的问题,在同一个事务内的两次读取,拿到的后果居然不一样,因为另外一个事务对数据进行了批改。
- REPEATABLE READ(可反复读)
可反复读隔离级别解决了下面不可反复读的问题(看名字也晓得),然而依然有一个新问题, 就是 幻读, 当你读取 id> 10 的数据行时,对波及到的所有行加上了读锁,此时例外一个事务新插入了一条 id=11 的数据,因为是新插入的,所以不会触发下面的锁的排挤,那么进行本事务进行下一次的查问时会发现有一条 id=11 的数据,而上次的查问操作并没有获取到,再进行插入就会有主键抵触的问题。
- SERIALIZABLE(可串行化)
这是最高的隔离级别,能够解决下面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速降落,因而也不是很罕用。
隔离级别 | 脏读 | 不可反复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | √ | √ | √ |
READ COMMITTED | √ | √ | |
REPEATABLE READ | √ | ||
SERIALIZABLE |
查看以后的事务隔离级别:
SELECT @@global.tx_isolation;
批改以后事务隔离级别:
set global transaction isolation level read committed;
19.InnoDB 应用的是那种隔离级别?
InnoDB 默认应用的是可反复读隔离级别。
20.MySQL 都有哪些锁?
从类别上来讲,有共享锁和排它锁。
共享锁,又叫读锁:当用户要进行数据的读取时候,对数据加上共享锁,共享锁能够有多个。
排它锁,又叫写锁:当用户进行数据写入时,对数据加上排它锁,排它锁只能有一个。
锁的粒度取决于具体的存储引擎,InnoDB 实现了行级锁、页级锁、表级锁。
加锁的开销从大到小,并发能力从大大小。
21. 主键应用自增 ID 还是 UUID?
举荐应用自增 ID,不要应用 UUID。
因为在 InnoDB 存储引擎中,主键索引是作为汇集索引存在的,也就是说,主键索引的 B + 树节点上存储了主键索引以及全副数据。如果是自增 ID 的话,只须要往后排列即可。如果是 UUID,每个 ID 的大小不确定,会导致数据挪动,产生垃圾碎片,从而造成性能降落。
图片来源于《高性能 MySQL》: 其中默认后缀为应用自增 ID,_uuid 为应用 UUID 为主键的测试, 测试了插入 100w 行和 300w 行的性能。
22. 如果要存储用户明码,用什么字段适合?
明码散列,用户身份证号等固定长度的字符串都应该应用 char 而不是 varchar,这样能够节俭空间进步检索效率。
23.char 和 varchar 有什么区别?
char 是一个定长字段,如果申请的长度为 10 的空间,无论理论存储多少内容,该字段都占用 10 个字符。
varchar 是变长的,也就是说申请的只是最大长度,占用的空间为理论字符长度 +1,最初一个字符存储应用了多少空间。
在检索效率上来讲,char > varchar
因而在应用中,如果确定某个字段的值的长度,能够应用 char,否则应该尽量应用 varchar。例如存储用户 MD5 加密后的明码,则应该应用 char。
24. 什么是存储过程?有哪些优缺点?
存储过程是一些预编译的 SQL 语句。1、更加直白的了解:存储过程能够说是一个记录集,它是由一些 T -SQL 语句组成的代码块,这些 T -SQL 语句代码像一个办法一样实现一些性能(对单表或多表的增删改查),而后再给这个代码块取一个名字,在用到这个性能的时候调用他就行了。2、存储过程是一个预编译的代码块,执行效率比拟高, 一个存储过程代替大量 T_SQL 语句,能够升高网络通信量,进步通信速率, 能够肯定水平上确保数据安全。
25.说一说三个范式
第一范式:每个列都不可再拆分。
第二范式:非主键列齐全依赖主键,而不是主键的一部分。
第三范式:非主键列只依赖主键,不依赖其余非主键。
Redis 篇
1. 什么是 Redis?
Redis 实质是始终 Key-Value 类型的内存数据库。因为是纯内存操作所以性能十分好,而且还反对多种数据结构。另外 Redis 能够对存入的 Key-Value 设置 expire 工夫。毛病是数据库容量受到物理内存的限度,不能做海量数据的高性能读写。
2.Redis 反对哪几种数据类型?
数据类型 | 应用场景 |
---|---|
String | 缓存,计数器,session |
Hash | 存储用户信息,【key,field,value】语法:Hset key field value |
List | 最新消息的排行,还能够利用 list 的 push 命令,将工作存在 list 汇合中,同时应用 pop 命令,将工作从汇合中取出。模仿音讯队列。 |
Set | 无序汇合。汇合成员是惟一的,这就意味着汇合中不能呈现反复的数据。 |
Zset | Redis 有序汇合和汇合一样也是 string 类型元素的汇合, 且不容许反复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为汇合中的成员进行从小到大的排序。有序汇合的成员是惟一的, 但分数 (score) 却能够反复。 |
3.Redis 的过期策略以及内存淘汰机制?
redis 采纳的是 定期删除 + 惰性删除。
为什么不应用定时删除?
因为定时删除是应用一个定时器去监督 key,过期则主动删除。尽管内存及时开释,然而非常耗费 cpu 资源。在高并发环境下,cpu 应该将工夫利用在解决申请上,而不是删除 key。
定期删除 + 惰性删除是如何工作的呢?
定期删除:redis 每隔 100ms 查看是否有过期的 key,如果有则删除,须要阐明的是,redis 并不是一下查看所有 key,而是随机抽取查看。
因而定期删除可能会导致很多 key 没有被查看到,这个时候惰性删除就派上用场。
惰性删除:在每一次获取某个 key 时,去查看 key 是否过期,如果过期则删除。
如果惰性删除也没有获取到某个 key,这个时候采纳 淘汰机制
在 redis.conf 中有一行配置
maxmemory-policy volatile-lru
volatile-lru: 从设置过期工夫的数据集中筛选最近起码应用的数据淘汰。
volatile-ttl: 从设置过期工夫的数据集中筛选将要过期的数据淘汰。
volatile-random: 从设置过期工夫的数据集中随机抉择数据淘汰。
allkeys-lru: 从数据集中筛选最近起码应用的数据淘汰。
allkeys-random: 从数据集中任意抉择数据淘汰。
no-eviction(驱赶): 禁止驱赶数据,新写入操作会报错。
redis 默认应用的是 no-eviction。
4. 为什么 Redis 的操作是原子性的?
因为 redis 是单线程的,然而如果在多个命令并发中不是原子性的。
如果想实现原子性能够应用 incr 命令,或者 redis 事务,或者应用 redis-lua 的形式。
5. 什么是 Redis 事务?
Redis 事务能够一次执行多个命令,并且带有以下三个重要的保障:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令仍然被执行。
- 在事务执行过程,其余客户端提交的命令申请不会插入到事务执行命令序列中。
一个事务从开始到执行会经验以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
以 MULTI 开始一个事务,而后将多个命令入队到事务中,最初由 EXEC 命令触发事务,一并执行事务中的所有命令
命令 | 含意 |
---|---|
MULTI | 标记一个事务的开始 |
EXEC | 执行事务块中的所有命令 |
DISCARD | 勾销事务,放弃执行事务块中的所有命令 |
WATCH | 监督一个或多个 key,如果在事务执行前被其余命令改变,事务将被打断 |
UNWATCH | 勾销 WATCH 命令对所有 key 的监督 |
6.Redis 长久化形式有哪些?
RDB 在指定的工夫距离内将数据以快照的模式写入硬盘,复原时候是间接将文件读取到内存中。
RDB 是如果进行备份的?
redis 会独自创立一个子过程来进行长久化,先将数据写入一个临时文件,等长久化结束后再替换上次的文件,整个过程中主过程是不进行 IO 操作的。
RDB 长处:效率十分高。节俭磁盘空间。
RDB 毛病:RDB都是快照文件,都是默认五分钟甚至更久的工夫才会生成一次,这意味着你这次同步到下次同步这两头五分钟的数据都很可能全副失落掉。AOF则最多丢一秒的数据,数据完整性 上高下立判。
AOF 将 redis 执行中的所有写命令记录下来,只追加文件不改写文件,redis 启动之初会读取该文件从新加载数据。
AOF 长处:备份机制持重,失落概率低。可读的日志文件 能够解决误操作。
AOF 毛病:更占用磁盘空间,复原速度慢,每次读写同步的话性能会有压力。
7. Redis 的并发竞争问题如何解决?
次要是产生在并发写竞争。
1. 应用乐观锁的形式进行解决;(watch 机制配合事务锁)
2. 排队的机制进行。将所有须要对同一个 key 的申请进行入队操作,而后用一个消费者线程从队头顺次读出申请,并对相应的 key 进行操作。
这样对于同一个 key 的所有申请就都是程序拜访,失常逻辑下则不会有写失败的状况下产生。从而最大化写逻辑的总体效率。
1. 客户端角度,为保障每个客户端间失常有序与 Redis 进行通信,对连贯进行池化,同时对客户端读写 Redis 操作采纳外部锁 synchronized。
2. 服务器角度,利用 setnx 实现锁。
8.Redis 缓存穿透是什么?
缓存穿透就是指 缓存和数据库中都没有的数据,而用户一直发动申请,因为缓存中查不到数据,导致每次都须要去数据库查问,失去了缓存的意义。
比方发动一个 id 为”-1“的值或者一个特地大的不存在的值,这个时候用户很可能是攻击者,会导致数据库压力过大。
解决办法:
- 减少接口校验,比方 id<= 0 间接拦挡,用户权限校验。
- 能够从代码中解决:如果缓存中取不到且数据库中也没有,这个时候能够将 key-value 写为key-null,缓存无效工夫设置短点,比方 30 秒。能够避免用户重复对同一个 id 暴力攻打。
- 布隆过滤器(BloomFilter)利用高效的数据结构和算法疾速判断出你这个 Key 是否在数据库中存在,不存在你 return 就好了,存在你就去查了 DB 刷新 KV 再 return。
9.Redis 缓存击穿是什么?
缓存击穿是指缓存中没有但数据库中有的数据(个别是缓存工夫到期),这时因为并发用户特地多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力霎时增大,造成过大压力。
解决办法:
- 设置热点信息永不过期。
- 接口设置限流,熔断,降级。
- 加互斥锁。参考代码如下:
10.Redis 缓存雪崩是什么?
缓存雪崩是指缓存中数据大批量的达到过期工夫,而查问数据量微小,引起数据库压力过大。
解决方案:
- 缓存数据的过期工夫设置随机,避免同一时间大量数据过期。
- 如果缓存数据库是分布式部署,将热点数据平均在不同 Redis 中。
- 设置热点数据永不过期。
11. Redis 为什么这么快?
- 齐全基于内存,绝大部分申请是纯正的内存操作,十分疾速。它的数据存在内存中,相似于 HashMap,HashMap 的劣势就是查找和操作的工夫复杂度都是 O(1);
- 应用多路 I / O 复用模型,非阻塞 IO;
- 采纳单线程,防止了不必要的上下文切换和竞争条件,也不存在多过程或者多线程导致的切换而耗费 CPU,不必去思考各种锁的问题,不存在加锁开释锁操作,没有因为可能呈现死锁而导致的性能耗费;
12. 什么是哨兵模式?
哨兵模式是一种非凡的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的过程,作为过程,能够独立运行。其原理是哨兵通过发送命令,期待 Redis 服务器响应,从而监控运行多个 Redis 实例。
监控(Monitoring): 哨兵(sentinel) 会一直地查看你的 Master 和 Slave 是否运作失常。
揭示(Notification): 当被监控的某个 Redis 呈现问题时, 哨兵(sentinel) 能够通过 API 向管理员或者其余应用程序发送告诉。
主动故障迁徙(Automatic failover): 当一个 Master 不能失常工作时,哨兵(sentinel) 会开始一次主动故障迁徙操作, 它会将生效 Master 的其中一个 Slave 降级为新的 Master, 并让生效 Master 的其余 Slave 改为复制新的 Master; 当客户端试图连贯生效的 Master 时, 集群也会向客户端返回新 Master 的地址。
RabbitMQ 篇
1. 什么是 rabbitmq?
采纳 AMQP 高级音讯队列协定的一种音讯队列技术,最大的特点就是生产方并不需要确保提供方存在,实现了服务之间的高度解耦。
2. 为什么应用 rabbitmq?
- 领有长久化机制。
- 实现生产者和消费者之间解耦。
- 利用音讯队列能够使同步拜访变为串行拜访达到一定量的限流。
3. 应用 rabbitmq 的场景?
- 服务之间异步通信。
- 程序生产。
- 定时工作。
- 申请削峰。
4. 如何确保音讯正确的发送至 rabbitmq?如何确保音讯接管方生产了音讯?
发送方确认模式
将信道设置为 confirm 模式,则所有信道公布的音讯都会被指派一个惟一的 ID。
一旦生产被投递到指标队列后,或者音讯被写入磁盘后,信道会发送一个确认信息给生产者。
如果 rabbitmq 产生外部谬误导致音讯失落,会发送一条 nack 音讯。
发送方的确认模式是异步的,生产者在期待的确认的同时,能够持续发送音讯。当音讯 nack 后会触发回调办法来解决确认音讯。
接管方确认机制
消费者承受每一条信息后都必须进行确认。只有消费者确认了音讯,rabbitmq 才能够平安的将音讯从队列中删除。
消费者在订阅队列时,能够指定 autoAck
参数:
- 当
autoAck
设置为 true 时,rabbitmq 会主动把发送进来的音讯置为确认,而后从内存 (或磁盘) 中删除,而不论是否真的生产到了音讯。 - 当
autoAck
设置为 false 时,rabbitmq 会期待消费者显式的回复确认信号后才会将内存 (或磁盘) 数据删除。
5. 如何防止音讯反复投递?
在音讯生产时,rabbitmq 外部针对每条生产者发送的音讯生成一个 inner-msg-id,作为去重的根据(音讯投递失败并重传),防止反复音讯进入队列。
6. 如何防止音讯反复生产?
在音讯生产时,要求音讯体重必须有一个 bizId(对同一业务全局惟一)作为去重和幂等的根据,防止反复生产。
举例:
- 比方拿到这条音讯做数据库的 insert 操作,给这个音讯做一个惟一主键,就算呈现了反复生产的状况,就会导致主键抵触,防止脏数据的产生。
- 如果要存入 redis 的话,不须要任何操作,因为无论 set 多少次后果都一样,set 是幂等操作。
- 如果还不行的话就在生产的时候先存入 redis,下次反复生产的时候先查问一下 redis 里有没有,如果有的话则不生产。
7. 音讯基于什么传输?
因为 TCP 连贯的创立和销毁开销较大,且并发数受系统资源限度,会造成性能瓶颈。rabbitmq 应用的是信道的形式来传输数据。信道是建设在真是的 TCP 连贯内的虚构连贯,且每条 TCP 连贯上的信道是没有数量限度的。
8. 音讯怎么路由?
音讯提供方 –> 路由 –> 一个或者多个队列音讯公布的交换机上,音讯将领有一个路由键(routing key),在音讯创立时设定。通过队列路由器,能够把队列绑定到交换机上。音讯达到交换机后,rabbitmq 会将音讯的路由键与队列的路由键进行匹配(针对不同的交换机有不同的路由规定)。
交换机分为以下三种:
- fanout:如果交换机收到音讯,将会播送到所有绑定的队列上。
- direct:如果路由键齐全匹配,音讯就投递到响应的队列上。
- topic:能够使来自不同的源头的音讯可能达到同一队列,应用 topic 交换器时,能够应用通配符。
9. 如何确保音讯不失落?
音讯长久化,当然前提是队列必须长久化。
rabbitmq 确保持久性音讯能从服务器重启中复原的形式是,将它们写入磁盘的一个长久化日志,当公布一条持久性音讯到长久交换机上时,rabbitmq 会把音讯提交到日志文件后才发送响应。
一旦消费者从长久化队列中生产了一条长久化的音讯,rabbitmq 会在长久化日志中将这条音讯标记为期待垃圾收集。如果长久化音讯在被生产之前 rabbitmq 重启,那么 rabbitmq 会主动重建交换机和队列以及绑定,并从新公布长久化日志文件中的音讯到适合的队列。
Dubbo 篇
1.Dubbo 是什么?
Dubbo 是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架。
2. 为什么应用 Dubbo?
因为是阿里开源我的项目,国内很多互联网公司都在应用,曾经通过了很多线上考验。
外部应用 Netty、Zookeeper,保障了高性能高可用性。
应用 dubbo 能够将外围业务抽取进去,作为独立的服务,用于进步业务复用灵便扩大。
3.dubbo 反对什么协定,举荐用哪种?
- dubbo://(举荐)
- rmi://
- hessian://
- http://
- webservice://
- thrift://
- memcached://
- redis://
- rest://
4.Dubbo 须要 Web 容器吗?
不须要,如果硬要用 Web 容器,只会减少复杂性,也浪费资源。
5.Dubbo 内置了哪几种服务容器?
- Spring Container
- Jetty Container
- Log4j Container
Dubbo 的服务容器只是一个简略的 Main 办法,并加载一个简略的 Spring 容器,用于裸露服务。
6. Dubbo 外面有几种节点角色?
节点 | 角色阐明 |
---|---|
Provider | 裸露服务的服务提供方 |
Consumer | 调用近程服务的服务生产方 |
Registry | 服务注册与发现的注册核心 |
Monitor | 统计服务的调用次数和调用工夫的监控核心 |
Container | 服务运行容器 |
7. 服务注册与发现的流程图?
8.Dubbo 外围的配置有哪些?
配置 | 配置阐明 | 解释 |
---|---|---|
<dubbo:service/> | 服务配置 | 用于裸露一个服务,定义服务的元信息,一个服务能够用多个协定裸露,一个服务也能够注册到多个注册核心 |
<dubbo:reference/> | 援用配置 | 用于创立一个近程服务代理,一个援用能够指向多个注册核心 |
<dubbo:protocol/> | 协定配置 | 用于配置提供服务的协定信息,协定由提供方指定,生产方被动承受 |
<dubbo:application/> | 利用配置 | 用于配置以后利用信息,不论该利用是提供者还是消费者 |
<dubbo:module/> | 模块配置 | 用于配置以后模块信息,可选 |
<dubbo:registry/> | 注册核心配置 | 用于配置连贯注册核心相干信息 |
<dubbo:monitor/> | 监控核心配置 | 用于配置连贯监控核心相干信息,可选 |
<dubbo:provider/> | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采纳此缺省值,可选 |
<dubbo:consumer/> | 生产方配置 | 当 ReferenceConfig 某属性没有配置时,采纳此缺省值,可选 |
<dubbo:method/> | 办法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定办法级的配置信息 |
<dubbo:argument/> | 参数配置 | 用于指定办法参数配置 |
不同粒度配置的笼罩关系
以 timeout 为例,下图显示了配置的查找程序,其它 retries, loadbalance, actives 等相似:
- 办法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则生产方优先,提供方次之。
其中,服务提供方配置,通过 URL 经由注册核心传递给生产方。
(倡议由服务提供方设置超时,因为一个办法须要执行多长时间,服务提供方更分明,如果一个生产方同时援用多个服务,就不须要关怀每个服务的超时设置)。
实践上 ReferenceConfig 中除了 interface
这一项,其余所有配置项都能够缺省不配置,框架会主动应用 ConsumerConfig,ServiceConfig, ProviderConfig 等提供的缺省配置。
在 Provider 上能够配置的 Consumer 端的属性有哪些?
1)timeout:办法调用超时
2)retries:失败重试次数,默认重试 2 次
3)loadbalance:负载平衡算法,默认随机
4)actives 消费者端,最大并发调用限度
9. Dubbo 有哪几种集群容错计划?
集群容错计划 | 阐明 |
---|---|
Failover Cluster | 失败主动切换,主动重试其余服务器(默认) |
Failfast Cluster | 疾速失败,立刻报错,只发动一次调用 |
Failsafe Cluster | 失败平安,出现异常时,间接疏忽 |
Failback Cluster | 失败主动复原,记录失败申请,定时重发 |
ForKing Cluster | 并行调用多个服务器,只有一个胜利即返回 |
Broadcast Cluster | 播送一一调用所有提供者,任意一个报错则报错 |
10.Dubbo 有哪几种负载平衡策略?
负载平衡策略 | 阐明 |
---|---|
Random LoadBalance | 随机,按权重设置随机概率(默认) |
RoundRobin LoadBalance | 轮训,按公约后的权重设置轮询比率 |
LeastActive LoadBalance | 起码沉闷调用数,雷同或束缚的随机 |
ConsistentHash LoadBalance | 一致性 Hash,雷同参数的申请总是发送到同一提供者 |
11. 当一个服务接口有多种实现时怎么做?
能够应用 group 属性来分组,服务提供方和生产方都指定同一个 group 即可。
12.Dubbo 服务之间的调用是阻塞的吗?
默认是同步期待后果阻塞的,反对异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不须要启动多线程即可实现并行调用多个近程服务,绝对多线程开销较小,异步调用会返回一个 Future 对象。
13. 说说 Dubbo 服务裸露的过程
Dubbo 会在 Spring 实例化 Bean 之后,在刷新容器最初一步公布 ContextRefreshEvent 事件,告诉实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件办法,Dubbo 会在这个办法中调用 ServiceBean 父类 ServiceConfig 的 export 办法,从而实现了服务公布。