1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

Merge pull request #1 from Snailclimb/master

update my repo by matser
This commit is contained in:
【张鑫】 2019-07-25 10:12:43 +08:00 committed by GitHub
commit ac3a10a1ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
150 changed files with 15824 additions and 6999 deletions

1
.gitattributes vendored
View File

@ -1,4 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto * text=auto
*.js linguist-language=java *.js linguist-language=java
*.css linguist-language=java *.css linguist-language=java

View File

@ -1,67 +0,0 @@
Java面试通关手册Java学习指南github地址欢迎star和pull[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide)
下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。
> ### 常见面试题
[深入理解虚拟机之Java内存区域](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)
1. 介绍下Java内存区域运行时数据区
2. 对象的访问定位的两种方式。
[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)
1. 如何判断对象是否死亡(两种方法)。
2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
3. 垃圾收集有哪些算法,各自的特点?
4. HotSpot为什么要分为新生代和老年代
5. 常见的垃圾回收器有那些?
6. 介绍一下CMS,G1收集器。
7. Minor Gc和Full GC 有什么不同呢?
[虚拟机性能监控和故障处理工具](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483922%26idx%3D1%26sn%3D0695ff4c2700ccebb8fbc39011866bd8%26chksm%3Dfd985473caefdd6583eb42dbbc7f01918dc6827c808292bb74a5b6333e3d526c097c9351e694%23rd)
1. JVM调优的常见命令行工具有哪些
[深入理解虚拟机之类文件结构](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483926%26idx%3D1%26sn%3D224413da998f7e024f7b8d87397934d9%26chksm%3Dfd985477caefdd61a2fe1a3f0be29e057082252e579332f5b6d9072a150b838cefe2c47b6e5a%23rd)
1. 简单介绍一下Class类文件结构常量池主要存放的是那两大常量Class文件的继承关系是如何确定的字段表、方法表、属性表主要包含那些信息
[深入理解虚拟机之虚拟机类加载机制](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483934&idx=1&sn=f247f9bee4e240f5e7fac25659da3bff&chksm=fd98547fcaefdd6996e1a7046e03f29df9308bdf82ceeffd111112766ffd3187892700f64b40#rd)
1. 简单说说类加载过程,里面执行了哪些操作?
2. 对类加载器有了解吗?
3. 什么是双亲委派模型?
4. 双亲委派模型的工作过程以及使用它的好处。
> ### 推荐阅读
[深入理解虚拟机之虚拟机字节码执行引擎](https://juejin.im/post/5aebcb076fb9a07a9a10b5f3)
[《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章)
[全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461)
**欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创分享美文分享各种Java学习资源**
![微信公众号](https://user-gold-cdn.xitu.io/2018/3/19/1623c870135a3609?w=215&h=215&f=jpeg&s=29172)

View File

@ -1,353 +0,0 @@
<!-- MarkdownTOC -->
1. [ListSet,Map三者的区别及总结](#listsetmap三者的区别及总结)
1. [Arraylist 与 LinkedList 区别](#arraylist-与-linkedlist-区别)
1. [ArrayList 与 Vector 区别为什么要用Arraylist取代Vector呢](#arraylist-与-vector-区别)
1. [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
1. [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)
1. [HashMap 和 ConcurrentHashMap 的区别](#hashmap-和-concurrenthashmap-的区别)
1. [HashSet如何检查重复](#hashset如何检查重复)
1. [comparable 和 comparator的区别](#comparable-和-comparator的区别)
1. [Comparator定制排序](#comparator定制排序)
1. [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)
1. [如何对Object的list排序](#如何对object的list排序)
1. [如何实现数组与List的相互转换](#如何实现数组与list的相互转换)
1. [如何求ArrayList集合的交集 并集 差集 去重复并集](#如何求arraylist集合的交集-并集-差集-去重复并集)
1. [HashMap 的工作原理及代码实现](#hashmap-的工作原理及代码实现)
1. [ConcurrentHashMap 的工作原理及代码实现](#concurrenthashmap-的工作原理及代码实现)
1. [集合框架底层数据结构总结](#集合框架底层数据结构总结)
1. [- Collection](#--collection)
1. [1. List](#1-list)
1. [2. Set](#2-set)
1. [- Map](#--map)
1. [集合的选用](#集合的选用)
1. [集合的常用方法](#集合的常用方法)
<!-- /MarkdownTOC -->
## <font face="楷体">ListSet,Map三者的区别及总结</font>
- **List对付顺序的好帮手**
List接口存储一组不唯一可以有多个元素引用相同的对象有序的对象
- **Set:注重独一无二的性质**
不允许重复的集合。不会有多个元素引用相同的对象。
- **Map:用Key来搜索的专家**
使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象但Key不能重复典型的Key是String类型但也可以是任何对象。
## <font face="楷体">Arraylist 与 LinkedList 区别</font>
Arraylist底层使用的是数组存读数据效率高插入删除特定位置效率低LinkedList 底层使用的是双向链表数据结构插入删除效率特别高JDK1.6之前为循环链表JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储插入删除元素时间复杂度不受元素位置的影响都是近似O1而数组为近似On因此当数据特别多而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了因为一般数据量都不会蛮大Arraylist是使用最多的集合类。
## <font face="楷体">ArrayList 与 Vector 区别</font>
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector
代码要在同步操作上耗费大量的时间。Arraylist不是同步的所以在不需要同步时建议使用Arraylist。
## <font face="楷体">HashMap 和 Hashtable 的区别</font>
1. HashMap是非线程安全的HashTable是线程安全的HashTable内部的方法基本都经过synchronized修饰。
2. 因为线程安全的问题HashMap要比HashTable效率高一点HashTable基本被淘汰。
3. HashMap允许有null值的存在而在HashTable中put进的键值只要有一个null直接抛出NullPointerException。
Hashtable和HashMap有几个主要的不同线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable而如果你使用Java5或以上的话请使用ConcurrentHashMap吧
## <font face="楷体">HashSet 和 HashMap 区别</font>
![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)
## <font face="楷体">HashMap 和 ConcurrentHashMap 的区别</font>
[HashMap与ConcurrentHashMap的区别](https://blog.csdn.net/xuefeng0707/article/details/40834595)
1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment)然后在每一个分段上都用lock锁进行保护相对于HashTable的synchronized锁的粒度更精细了一些并发性能更好而HashMap没有锁机制不是线程安全的。JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。
2. HashMap的键值对允许有null但是ConCurrentHashMap都不允许。
## <font face="楷体">HashSet如何检查重复</font>
当你把对象加入HashSet时HashSet会先计算对象的hashcode值来判断对象加入的位置同时也会与其他加入的对象的hashcode值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象这时会调用equals方法来检查hashcode相等的对象是否真的相同。如果两者相同HashSet就不会让加入操作成功。摘自我的Java启蒙书《Head fist java》第二版
**hashCode与equals的相关规定**
1. 如果两个对象相等则hashcode一定也是相同的
2. 两个对象相等,对两个equals方法返回true
3. 两个对象有相同的hashcode值它们也不一定是相等的
4. 综上equals方法被覆盖过则hashCode方法也必须被覆盖
5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()则该class的两个对象无论如何都不会相等即使这两个对象指向相同的数据
**==与equals的区别**
1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
3. ==指引用是否相同 equals()指的是值是否相同
## <font face="楷体">comparable 和 comparator的区别</font>
- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
一般我们需要对一个集合使用自定义排序时我们就要重写compareTo方法或compare方法当我们需要对某一个集合实现两种排序方式比如一个song对象中的歌名和歌手名分别采用一种排序方法的话我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序第二种代表我们只能使用两个参数版的Collections.sort().
### <font face="楷体">Comparator定制排序<font face="楷体">
```java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* TODO Collections类方法测试之排序
* @author 寇爽
* @date 2017年11月20日
* @version 1.8
*/
public class CollectionsSort {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始数组:");
System.out.println(arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
/*
* void rotate(List list, int distance),旋转。
* 当distance为正数时将list后distance个元素整体移到前面。当distance为负数时
* list的前distance个元素整体移到后面。
Collections.rotate(arrayList, 4);
System.out.println("Collections.rotate(arrayList, 4):");
System.out.println(arrayList);*/
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// void shuffle(List list),随机排序
Collections.shuffle(arrayList);
System.out.println("Collections.shuffle(arrayList):");
System.out.println(arrayList);
// 定制排序的用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
}
}
```
### <font face="楷体">重写compareTo方法实现按年龄来排序</font>
```java
package map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMap2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("小红", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
}
// person对象没有实现Comparable接口所以必须实现这样才不会出错才可以使treemap中的数据按顺序排列
// 前面一个例子的String类已经默认实现了Comparable接口详细可以查看String类的API文档另外其他
// 像Integer类等都已经实现了Comparable接口所以不需要另外实现了
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* TODO重写compareTo方法实现按年龄来排序
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
if (this.age > o.getAge()) {
return 1;
} else if (this.age < o.getAge()) {
return -1;
}
return age;
}
}
```
## <font face="楷体">如何对Object的list排序</font>
- 对objects数组进行排序我们可以用Arrays.sort()方法
- 对objects的集合进行排序需要使用Collections.sort()方法
## <font face="楷体">如何实现数组与List的相互转换</font>
List转数组toArray(arraylist.size()方法数组转List:Arrays的asList(a)方法
```java
List<String> arrayList = new ArrayList<String>();
arrayList.add("s");
arrayList.add("e");
arrayList.add("n");
/**
* ArrayList转数组
*/
int size=arrayList.size();
String[] a = arrayList.toArray(new String[size]);
//输出第二个元素
System.out.println(a[1]);//结果e
//输出整个数组
System.out.println(Arrays.toString(a));//结果:[s, e, n]
/**
* 数组转list
*/
List<String> list=Arrays.asList(a);
/**
* list转Arraylist
*/
List<String> arrayList2 = new ArrayList<String>();
arrayList2.addAll(list);
System.out.println(list);
```
## <font face="楷体">如何求ArrayList集合的交集 并集 差集 去重复并集</font>
需要用到List接口中定义的几个方法
- addAll(Collection<? extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
实例代码:
- retainAll(Collection<?> c): 仅保留此列表中包含在指定集合中的元素。
- removeAll(Collection<?> c) :从此列表中删除指定集合中包含的所有元素。
```java
package list;
import java.util.ArrayList;
import java.util.List;
/**
*TODO 两个集合之间求交集 并集 差集 去重复并集
* @author 寇爽
* @date 2017年11月21日
* @version 1.8
*/
public class MethodDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
list1.add(3);
list1.add(4);
List<Integer> list2 = new ArrayList<Integer>();
list2.add(2);
list2.add(3);
list2.add(4);
list2.add(5);
// 并集
// list1.addAll(list2);
// 交集
//list1.retainAll(list2);
// 差集
// list1.removeAll(list2);
// 无重复并集
list2.removeAll(list1);
list1.addAll(list2);
for (Integer i : list1) {
System.out.println(i);
}
}
}
```
## <font face="楷体">HashMap 的工作原理及代码实现</font>
[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)
## <font face="楷体">ConcurrentHashMap 的工作原理及代码实现</font>
[ConcurrentHashMap实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html)
## <font face="楷体">集合框架底层数据结构总结</font>
### - Collection
#### 1. List
- Arraylist数组查询快,增删慢 线程不安全,效率高
- Vector数组查询快,增删慢 线程安全,效率低
- LinkedList链表查询慢,增删快 线程不安全,效率高
#### 2. Set
- HashSet无序唯一:哈希表或者叫散列集(hash table)
- LinkedHashSet链表和哈希表组成 。 由链表保证元素的排序 由哈希表证元素的唯一性
- TreeSet有序唯一红黑树(自平衡的排序二叉树。)
### - Map
- HashMap基于哈希表的Map接口实现哈希表对键进行散列Map结构即映射表存放键值对
- LinkedHashMap:HashMap 的基础上加上了链表数据结构
- HashTable:哈希表
- TreeMap:红黑树(自平衡的排序二叉树)
## <font face="楷体">集合的选用</font>
主要根据集合的特点来选用比如我们需要根据键值获取到元素值时就选用Map接口下的集合需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时就选择实现Collection接口的集合需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet不需要就选择实现List接口的比如ArrayList或LinkedList然后再根据实现这些接口的集合的特点来选用。
2018/3/11更新
## <font face="楷体">集合的常用方法</font>
今天下午无意看见一道某大厂的面试题面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。
会持续更新。。。
**参考书籍:**
《Head first java 》第二版 推荐阅读真心不错 (适合基础较差的)
《Java核心技术卷1》推荐阅读真心不错 (适合基础较好的)
《算法》第四版 适合想对数据结构的Java实现感兴趣的

View File

@ -1,337 +0,0 @@
> 个人觉得这一节掌握基本的使用即可!
**本节思维导图:**
![](https://user-gold-cdn.xitu.io/2018/10/30/166c58b785368234?w=1200&h=657&f=png&s=49615)
### 1 Atomic 原子类介绍
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267)
根据操作的数据类型可以将JUC包中的原子类分为4类
**基本类型**
使用原子的方式更新基本类型
- AtomicInteger整形原子类
- AtomicLong长整型原子类
- AtomicBoolean :布尔型原子类
**数组类型**
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray整形数组原子类
- AtomicLongArray长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
**引用类型**
- AtomicReference引用类型原子类
- AtomicStampedRerence原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
**对象的属性修改类型**
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater原子更新长整形字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
下面我们来详细介绍一下这些原子类。
### 2 基本类型原子类
#### 2.1 基本类型原子类介绍
使用原子的方式更新基本类型
- AtomicInteger整形原子类
- AtomicLong长整型原子类
- AtomicBoolean :布尔型原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
**AtomicInteger 类常用方法**
```java
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值则以原子方式将该值设置为输入值update
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
```
#### 2.2 AtomicInteger 常见方法使用
```java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
AtomicInteger i = new AtomicInteger(0);
temvalue = i.getAndSet(3);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3
temvalue = i.getAndIncrement();
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4
temvalue = i.getAndAdd(5);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9
}
}
```
#### 2.3 基本数据类型原子类的优势
通过一个简单例子带大家看一下基本数据类型原子类的优势
**①多线程环境不使用原子类保证线程安全(基本数据类型)**
```java
class Test {
private volatile int count = 0;
//若要线程安全执行执行count++,需要加锁
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
```
**②多线程环境使用原子类保证线程安全(基本数据类型)**
```java
class Test2 {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
//使用AtomicInteger之后不需要加锁也可以实现线程安全。
public int getCount() {
return count.get();
}
}
```
#### 2.4 AtomicInteger 线程安全原理简单分析
AtomicInteger 类的部分源码:
```java
// setup to use Unsafe.compareAndSwapInt for updates更新操作时提供“比较并替换”的作用
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
```
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量在内存中可见因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
### 3 数组类型原子类
#### 3.1 数组类型原子类介绍
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray整形数组原子类
- AtomicLongArray长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。
**AtomicIntegerArray 类常用方法**
```java
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值并将其设置为新值newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值update
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
```
#### 3.2 AtomicIntegerArray 常见方法使用
```java
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(i.get(j));
}
temvalue = i.getAndSet(0, 2);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndIncrement(0);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndAdd(0, 5);
System.out.println("temvalue:" + temvalue + "; i:" + i);
}
}
```
### 4 引用类型原子类
#### 4.1 引用类型原子类介绍
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。
- AtomicReference引用类型原子类
- AtomicStampedRerence原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
#### 4.2 AtomicReference 类使用示例
```java
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<Person> ar = new AtomicReference<Person>();
Person person = new Person("SnailClimb", 22);
ar.set(person);
Person updatePerson = new Person("Daisy", 20);
ar.compareAndSet(person, updatePerson);
System.out.println(ar.get().getName());
System.out.println(ar.get().getAge());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
```
Daisy
20
```
### 5 对象的属性修改类型原子类
#### 5.1 对象的属性修改类型原子类介绍
如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater原子更新长整形字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。
#### 5.2 AtomicIntegerFieldUpdater 类使用示例
```java
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22);
System.out.println(a.getAndIncrement(user));// 22
System.out.println(a.get(user));// 23
}
}
class User {
private String name;
public volatile int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
输出结果:
```
22
23
```

View File

@ -1,84 +0,0 @@
## final 关键字
**final关键字主要用在三个地方变量、方法、类。**
1. **对于一个final变量如果是基本数据类型的变量则其数值一旦在初始化之后便不能更改如果是引用类型的变量则在对其初始化之后便不能再让其指向另一个对象。**
2. **当用final修饰一个类时表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。**
3. 使用final方法的原因有两个。第一个原因是把方法锁定以防任何继承类修改它的含义第二个原因是效率。在早期的Java实现版本中会将final方法转为内嵌调用。但是如果方法过于庞大可能看不到内嵌调用带来的任何性能提升现在的Java版本已经不需要使用final方法进行这些优化了。类中所有的private方法都隐式地指定为final。
## static 关键字
**static 关键字主要有以下四种使用场景:**
1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类不属于单个这个类的某个对象被类中所有对象共享可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
3. **静态内部类static修饰类的话只能修饰内部类** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用该引用是指向创建它的外围类但是静态内部类却没有。没有这个引用就意味着1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
4. **静态导包(用来导入类中的静态资源1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
## this 关键字
this关键字用于引用类的当前实例。 例如:
```java
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
```
在上面的示例中this关键字用于两个地方
- this.employees.length访问类Manager的当前实例的变量。
- this.report调用类Manager的当前实例的方法。
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
## super 关键字
super关键字用于从子类访问父类的变量和方法。 例如:
```java
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
```
在上面的例子中Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber` 方法。
**使用 this 和 super 要注意的问题:**
- super 调用父类中的其他构造方法时调用时要放在构造方法的首行this 调用本类中的其他构造方法时,也要放在首行。
- this、super不能用在static方法中。
**简单解释一下:**
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西而静态方法是属于类范畴的东西**
## 参考
- https://www.codejava.net/java-core/the-java-language/java-keywords
- https://blog.csdn.net/u013393958/article/details/79881037

View File

@ -1,341 +0,0 @@
## 写在前面(常见面试题)
### 基本问题:
- **介绍下 Java 内存区域(运行时数据区)**
- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)**
- **对象的访问定位的两种方式(句柄和直接指针两种方式)**
### 拓展问题:
- **String类和常量池**
- **8种基本类型的包装类和常量池**
## 1 概述
对于 Java 程序员来说在虚拟机自动内存管理机制下不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
## 2 运行时数据区域
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
![运行时数据区域](https://user-gold-cdn.xitu.io/2018/4/27/16306a34cd8a4354?w=513&h=404&f=png&s=132068)
这些组成部分一些是线程私有的,其他的则是线程共享的。
**线程私有的:**
- 程序计数器
- 虚拟机栈
- 本地方法栈
**线程共享的:**
- 堆
- 方法区
- 直接内存
### 2.1 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。**
另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。**
**从上面的介绍中我们知道程序计数器主要有两个作用:**
1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
**注意程序计数器是唯一一个不会出现OutOfMemoryError的内存区域它的生命周期随着线程的创建而创建随着线程的结束而死亡。**
### 2.2 Java 虚拟机栈
**与程序计数器一样Java虚拟机栈也是线程私有的它的生命周期和线程相同描述的是 Java 方法执行的内存模型。**
**Java 内存可以粗糙的区分为堆内存Heap和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** 实际上Java虚拟机栈是由一个个栈帧组成而每个栈帧中都拥有局部变量表、操作数栈、动态链接、方法出口信息。
**局部变量表主要存放了编译器可知的各种数据类型**boolean、byte、char、short、int、float、long、double、**对象引用**reference类型它不同于对象本身可能是一个指向对象起始地址的引用指针也可能是指向一个代表对象的句柄或其他与此对象相关的位置
**Java 虚拟机栈会出现两种异常StackOverFlowError 和 OutOfMemoryError。**
- **StackOverFlowError** 若Java虚拟机栈的内存大小不允许动态扩展那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候就抛出StackOverFlowError异常。
- **OutOfMemoryError** 若 Java 虚拟机栈的内存大小允许动态扩展且当线程请求栈时内存用完了无法再动态扩展了此时抛出OutOfMemoryError异常。
Java 虚拟机栈也是线程私有的每个线程都有各自的Java虚拟机栈而且随着线程的创建而创建随着线程的死亡而死亡。
### 2.3 本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
### 2.4 堆
Java 虚拟机所管理的内存中最大的一块Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆Garbage Collected Heap**.从垃圾回收的角度由于现在收集器基本都采用分代垃圾收集算法所以Java堆还可以细分为新生代和老年代再细致一点有Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946)
**在 JDK 1.8中移除整个永久代取而代之的是一个叫元空间Metaspace的区域永久代使用的是JVM的堆内存空间而元空间使用的是物理内存直接受到本机的物理内存限制。**
推荐阅读:
- 《Java8内存模型—永久代(PermGen)和元空间(Metaspace)》:[http://www.cnblogs.com/paddix/p/5309550.html](http://www.cnblogs.com/paddix/p/5309550.html)
### 2.5 方法区
**方法区与 Java 堆一样是各个线程共享的内存区域它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分但是它却有一个别名叫做 Non-Heap非堆目的应该是与 Java 堆区分开来。**
HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
**相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。**
### 2.6 运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
**JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆Heap中开辟了一块区域存放运行时常量池。**
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg)
——图片来源https://blog.csdn.net/wangbiao007/article/details/78545189
推荐阅读:
- 《Java 中几种常量池的区分》: [https://blog.csdn.net/qq_26222859/article/details/73135660](https://blog.csdn.net/qq_26222859/article/details/73135660)
### 2.7 直接内存
直接内存并不是虚拟机运行时数据区的一部分也不是虚拟机规范中定义的内存区域但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。
JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道Channel** 与**缓存区Buffer** 的 I/O 方式它可以直接使用Native函数库直接分配堆外内存然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。
本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
## 3 HotSpot 虚拟机对象探秘
通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
### 3.1 对象的创建
下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
![Java对象的创建过程](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4135869?w=950&h=279&f=png&s=28529)
**①类加载检查:** 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
**②分配内存:** 在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。
**内存分配的两种方式:(补充内容,需要掌握)**
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a40a2c3d?w=1426&h=333&f=png&s=26346)
**内存分配并发问题(补充内容,需要掌握)**
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。**
- **TLAB** 为每一个线程预先在Eden区分配一块儿内存JVM在给线程中的对象分配内存时首先在TLAB分配当对象大于TLAB中的剩余内存或TLAB的内存已用尽时再采用上述的CAS进行内存分配
**③初始化零值:** 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
**④设置对象头:** 初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
**⑤执行 init 方法:** 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`<init>` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `<init>` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
### 3.2 对象的内存布局
在 Hotspot 虚拟机中对象在内存中的布局可以分为3块区域**对象头**、**实例数据**和**对齐填充**。
**Hotspot虚拟机的对象头包括两部分信息****第一部分用于存储对象自身的自身运行时数据**哈希码、GC分代年龄、锁状态标志等等**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。
**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数1倍或2倍因此当对象实例数据部分没有对齐时就需要通过对齐填充来补全。
### 3.3 对象的访问定位
建立对象就是为了使用对象我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种:
1. **句柄:** 如果使用句柄的话那么Java堆中将会划分出一块内存来作为句柄池reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201)
2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息而reference 中存储的直接就是对象的地址。
![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172)
**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。**
## 四 重点补充内容
### String 类和常量池
**1 String 对象的两种创建方式:**
```java
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false
```
这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a59c0873?w=698&h=355&f=png&s=10449)
记住只要使用new方法便需要创建新的对象。
**2 String 类型的常量池比较特殊。它的主要使用方法有两种:**
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
```java
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false因为一个是堆内存中的String对象一个是常量池中的String对象
System.out.println(s3 == s2);//true因为两个都是常量池中的String对象
```
**3 String 字符串拼接**
```java
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
```
![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a4d13f92?w=593&h=603&f=png&s=22265)
尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
### String s1 = new String("abc");这句话创建了几个对象?
**创建了两个对象。**
**验证:**
```java
String s1 = new String("abc");// 堆内存的地值值
String s2 = "abc";
System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
System.out.println(s1.equals(s2));// 输出true
```
**结果:**
```
false
true
```
**解释:**
先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。
### 8种基本类型的包装类和常量池
- **Java 基本类型的包装类的大部分都实现了常量池技术即Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。**
- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**
```java
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出true
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// 输出false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出false
```
**Integer 缓存源代码:**
```java
/**
*此方法将始终缓存-128到127包括端点范围内的值并可以缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
```
**应用场景:**
1. Integer i1=40Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
```java
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//输出false
```
**Integer比较更丰富的一个例子:**
```java
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));
```
结果:
```
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
```
解释:
语句i4 == i5 + i6因为+这个操作符不适用于Integer对象首先i5和i6进行自动拆箱操作进行数值相加即i4 == 40。然后Integer对象无法与数值进行直接比较所以i4自动拆箱转为int值40最终这条语句转为40 == 40进行数值比较。
**参考:**
- 《深入理解Java虚拟机JVM高级特性与最佳实践第二版》
- 《实战java虚拟机》
- https://www.cnblogs.com/CZDblog/p/5589379.html
- https://www.cnblogs.com/java-zhao/p/5180492.html
- https://blog.csdn.net/qq_26222859/article/details/73135660
- https://blog.csdn.net/cugwuhan2014/article/details/78038254

View File

@ -1,374 +0,0 @@
上文回顾:[《可能是把Java内存区域讲的最清楚的一篇文章》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd)
## 写在前面
### 本节常见面试题:
问题答案在文中都有提到
- 如何判断对象是否死亡(两种方法)。
- 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
- 如何判断一个常量是废弃常量
- 如何判断一个类是无用的类
- 垃圾收集有哪些算法,各自的特点?
- HotSpot为什么要分为新生代和老年代
- 常见的垃圾回收器有那些?
- 介绍一下CMS,G1收集器。
- Minor Gc和Full GC 有什么不同呢?
### 本文导火索
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg)
当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
## 1 揭开JVM内存分配与回收的神秘面纱
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。
**JDK1.8之前的堆内存示意图:**
![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946)
从上图可以看出堆内存分为新生代、老年代和永久代。新生代又被进一步分为Eden 区Survivor1 区Survivor2 区。值得注意的是,在 JDK 1.8中移除整个永久代取而代之的是一个叫元空间Metaspace的区域永久代使用的是JVM的堆内存空间而元空间使用的是物理内存直接受到本机的物理内存限制
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/89294547.jpg)
### 1.1 对象优先在eden区分配
目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。
在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢?**
- **新生代GCMinor GC**:指发生新生代的的垃圾收集动作Minor GC非常频繁回收速度一般也比较快。
- **老年代GCMajor GC/Full GC**:指发生在老年代的GC出现了Major GC经常会伴随至少一次的Minor GC并非绝对Major GC的速度一般会比Minor GC的慢10倍以上。
**测试:**
```java
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2;
allocation1 = new byte[30900*1024];
//allocation2 = new byte[900*1024];
}
}
```
通过以下方式运行:
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg)
添加的参数:`-XX:+PrintGCDetails`
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg)
运行结果(红色字体描述有误应该是对应于JDK1.7的永久代)
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg)
从上图我们可以看出eden区内存几乎已经被分配完全即使程序什么也不做新生代也会使用2000多k内存。假如我们再为allocation2分配内存会出现什么情况呢
```java
allocation2 = new byte[900*1024];
```
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg)
**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了我们刚刚讲了当Eden区没有足够空间进行分配时虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去老年代上的空间足够存放allocation1所以不会出现Full GC。执行Minor GC后后面分配的对象如果能够存在eden区的话还是会在eden区分配内存。可以执行如下代码验证
```java
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
allocation1 = new byte[32000*1024];
allocation2 = new byte[1000*1024];
allocation3 = new byte[1000*1024];
allocation4 = new byte[1000*1024];
allocation5 = new byte[1000*1024];
}
}
```
### 1.2 大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
**为什么要这样呢?**
为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
### 1.3 长期存活的对象将进入老年代
既然虚拟机采用了分代收集的思想来管理内存那么内存回收时就必须能识别哪些对象应放在新生代哪些对象应放在老年代中。为了做到这一点虚拟机给每个对象一个对象年龄Age计数器。
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁当它的年龄增加到一定程度默认为15岁就会被晋升到老年代中。对象晋升到老年代的年龄阈值可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
### 1.4 动态对象年龄判定
为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。
## 2 对象已经死亡?
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg)
### 2.1 引用计数法
给对象中添加一个引用计数器每当有一个地方引用它计数器就加1当引用失效计数器就减1任何时候计数器为0的对象就是不可能再被使用的。
**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题如下面代码所示除了对象objA 和 objB 相互引用着对方之外这两个对象之间再无任何引用。但是他们因为互相引用对方导致它们的引用计数器都不为0于是引用计数算法无法通知 GC 回收器回收他们。
```java
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
```
### 2.2 可达性分析算法
这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
![可达性分析算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg)
### 2.3 再谈引用
无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
JDK1.2之前Java中引用的定义很传统如果reference类型的数据存储的数值代表的是另一块内存的起始地址就称这块内存代表一个引用。
JDK1.2以后Java对引用的概念进行了扩充将引用分为强引用、软引用、弱引用、虚引用四种引用强度逐渐减弱
**1强引用**
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空 间不足Java虚拟机宁愿抛出OutOfMemoryError错误使程序异常终止也不会靠随意回收具有强引用的对象来解决内存不足问题。
**2软引用SoftReference**
如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列ReferenceQueue联合使用如果软引用所引用的对象被垃圾回收JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
**3弱引用WeakReference**
如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列ReferenceQueue联合使用如果弱引用所引用的对象被垃圾回收Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
**4虚引用PhantomReference**
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
**虚引用主要用来跟踪对象被垃圾回收的活动**。
**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列ReferenceQueue联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速JVM对垃圾内存的回收速度可以维护系统的运行安全防止内存溢出OutOfMemory等问题的产生**。
### 2.4 不可达的对象并非“非死不可”
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
### 2.5 如何判断一个常量是废弃常量
运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
假如在常量池中存在字符串 "abc"如果当前没有任何String对象引用该字符串常量的话就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
注意:我们在 [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆Heap中开辟了一块区域存放运行时常量池。
### 2.6 如何判断一个类是无用的类
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 **“无用的类”**
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3个条件的无用类进行回收这里说的仅仅是“可以”而并不是和对象一样不使用了就会必然被回收。
## 3 垃圾收集算法
![垃圾收集算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/1142723.jpg)
### 3.1 标记-清除算法
算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题:
1. **效率问题**
2. **空间问题(标记清除后会产生大量不连续的碎片)**
![标记-清除算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg)
### 3.2 复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
![复制算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg)
### 3.3 标记-整理算法
根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
![标记-整理算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg)
### 3.4 分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法这种算法没有什么新的思想只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。**
**延伸面试问题:** HotSpot为什么要分为新生代和老年代
根据上面的对分代收集算法的介绍回答。
## 4 垃圾收集器
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/41460955.jpg)
**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下如果有一种四海之内、任何场景下都适用的完美收集器存在那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。
### 4.1 Serial收集器
Serial串行收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。
**新生代采用复制算法,老年代采用标记-整理算法。**
![ Serial收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg)
虚拟机的设计者们当然知道Stop The World带来的不良用户体验所以在后续的垃圾收集器设计中停顿时间在不断缩短仍然还有停顿寻找最优秀的垃圾收集器的过程仍然在继续
但是Serial收集器有没有优于其他垃圾收集器的地方呢当然有它**简单而高效(与其他收集器的单线程相比)**。Serial收集器由于没有线程交互的开销自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。
### 4.2 ParNew收集器
**ParNew收集器其实就是Serial收集器的多线程版本除了使用多线程进行垃圾收集外其余行为控制参数、收集算法、回收策略等等和Serial收集器完全一样。**
**新生代采用复制算法,老年代采用标记-整理算法。**
![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
它是许多运行在Server模式下的虚拟机的首要选择除了Serial收集器外只有它能与CMS收集器真正意义上的并发收集器后面会介绍到配合工作。
**并行和并发概念补充:**
- **并行Parallel** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- **并发Concurrent**指用户线程与垃圾收集线程同时执行但不一定是并行可能会交替执行用户程序在继续运行而垃圾收集器运行在另一个CPU上。
### 4.3 Parallel Scavenge收集器
Parallel Scavenge 收集器类似于ParNew 收集器。 **那么它有什么特别之处呢?**
```
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 老年代并行
```
**Parallel Scavenge收集器关注点是吞吐量高效率的利用CPU。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间提高用户体验。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。** Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量如果对于收集器运作不太了解的话手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
**新生代采用复制算法,老年代采用标记-整理算法。**
![ParNew收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
### 4.4.Serial Old收集器
**Serial收集器的老年代版本**它同样是一个单线程收集器。它主要有两大用途一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用另一种用途是作为CMS收集器的后备方案。
### 4.5 Parallel Old收集器
**Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。
### 4.6 CMS收集器
**CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。**
**CMSConcurrent Mark Sweep收集器是HotSpot虚拟机第一款真正意义上的并发收集器它第一次实现了让垃圾收集线程与用户线程基本上同时工作。**
从名字中的**Mark Sweep**这两个词可以看出CMS收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- **初始标记:** 暂停所有的其他线程并记录下直接与root相连的对象速度很快
- **并发标记:** 同时开启GC和用户线程用一个闭包结构去记录可达对象。但在这个阶段结束这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- **并发清除:** 开启用户线程同时GC线程开始对为标记的区域做清扫。
![CMS垃圾收集器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg)
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点:
- **对CPU资源敏感**
- **无法处理浮动垃圾;**
- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**
### 4.7 G1收集器
**G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.**
被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备一下特点
- **并行与并发**G1能充分利用CPU、多核环境下的硬件优势使用多个CPUCPU或者CPU核心来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作G1收集器仍然可以通过并发的方式让java程序继续执行。
- **分代收集**虽然G1可以不需要其他收集器配合就能独立管理整个GC堆但是还是保留了分代的概念。
- **空间整合**与CMS的“标记--清理”算法不同G1从整体来看是基于“标记整理”算法实现的收集器从局部上来看是基于“复制”算法实现的。
- **可预测的停顿**这是G1相对于CMS的另一个大优势降低停顿时间是G1 和 CMS 共同的关注点但G1 除了追求低停顿外还能建立可预测的停顿时间模型能让使用者明确指定在一个长度为M毫秒的时间片段内。
G1收集器的运作大致分为以下几个步骤
- **初始标记**
- **并发标记**
- **最终标记**
- **筛选回收**
**G1收集器在后台维护了一个优先列表每次根据允许的收集时间优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)**。这种使用Region划分内存空间以及有优先级的区域回收方式保证了GF收集器在有限时间内可以尽可能高的收集效率把内存化整为零
参考:
- 《深入理解Java虚拟机JVM高级特性与最佳实践第二版》
- https://my.oschina.net/hosee/blog/644618

View File

@ -1,288 +0,0 @@
<!-- MarkdownTOC -->
- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同)
- [补充:数据结构基础之双向链表](#补充:数据结构基础之双向链表)
- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别)
- [HashMap的底层实现](#hashmap的底层实现)
- [JDK1.8之前](#jdk18之前)
- [JDK1.8之后](#jdk18之后)
- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)
- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)
- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)
- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)
- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)
- [JDK1.7(上面有示意图)](#jdk17上面有示意图)
- [JDK1.8 (上面有示意图)](#jdk18-(上面有示意图))
- [集合框架底层数据结构总结](#集合框架底层数据结构总结)
- [Collection](#collection)
- [1. List](#1-list)
- [2. Set](#2-set)
- [Map](#map)
- [推荐阅读:](#推荐阅读:)
<!-- /MarkdownTOC -->
## Arraylist 与 LinkedList 异同
- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- **2. 底层数据结构:** Arraylist 底层使用的是Object数组LinkedList 底层使用的是双向链表数据结构JDK1.6之前为循环链表JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)
- **3. 插入和删除是否受元素位置的影响:****ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O1而数组为近似 On。**
- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间因为要存放直接后继和直接前驱以及数据
-**6.补充内容:RandomAccess接口**
```java
public interface RandomAccess {
}
```
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
在binarySearch方法中它要判断传入的list 是否RamdomAccess的实例如果是调用indexedBinarySearch方法如果不是那么调用iteratorBinarySearch方法
```java
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
```
ArrayList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢我觉得还是和底层数据结构有关ArrayList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O1所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素时间复杂度为 On所以不支持快速随机访问。ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArrayList 实现 RandomAccess 接口才具有快速随机访问功能的!
**下面再总结一下 list 的遍历方式选择:**
- 实现了RandomAccess接口的list优先选择普通for循环 其次foreach,
- 未实现RandomAccess接口的ist 优先选择iterator遍历foreach遍历底层也是通过iterator实现的大size的数据千万不要使用普通for循环
### 补充:数据结构基础之双向链表
双向链表也叫双链表是链表的一种它的每个数据结点中都有两个指针分别指向直接后继和直接前驱。所以从双向链表中的任意一个结点开始都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表如下图所示同时下图也是LinkedList 底层使用的是双向循环链表数据结构。
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-21/88766727.jpg)
## ArrayList 与 Vector 区别
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
Arraylist不是同步的所以在不需要保证线程安全时时建议使用Arraylist。
## HashMap的底层实现
### JDK1.8之前
JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
**JDK 1.8 HashMap 的 hash 方法源码:**
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
```java
static final int hash(Object key) {
int h;
// key.hashCode()返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移忽略符号位空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```
对比一下 JDK1.7的 HashMap 的 hash 方法源码.
```java
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
```
相比于 JDK1.8 的 hash 方法 JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)
### JDK1.8之后
相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。
![JDK1.8之后的HashMap底层数据结构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/67233764.jpg)
>TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷因为二叉查找树在某些情况下会退化成一个线性结构。
**推荐阅读:**
- 《Java 8系列之重新认识HashMap》 [https://zhuanlan.zhihu.com/p/21673805](https://zhuanlan.zhihu.com/p/21673805)
## HashMap 和 Hashtable 的区别
1. **线程是否安全:** HashMap 是非线程安全的HashTable 是线程安全的HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
2. **效率:** 因为线程安全的问题HashMap 要比 HashTable 效率高一点。另外HashTable 基本被淘汰,不要在代码中使用它;
3. **对Null key 和Null value的支持** HashMap 中null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null直接抛出 NullPointerException。
4. **初始容量大小和每次扩充容量大小的不同 ** ①创建时如果不指定容量初始值Hashtable 默认的初始大小为11之后每次扩充容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充容量变为原来的2倍。②创建时如果给定了容量初始值那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
**HasMap 中带有初始容量的构造函数:**
```java
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
```
下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
```java
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
```
## 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的幂次方。**
## HashMap 多线程操作导致死循环问题
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复制链表过程如下:
以下模拟2个线程同时扩容。假设当前 HashMap 的空间为2临界值为1hashcode 分别为 0 和 1在散列地址 0 处有元素 A 和 B这时候要添加元素 CC 经过 hash 运算,得到散列地址为 1这时候由于超过了临界值空间不够需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下:
线程一:读取到当前的 HashMap 情况,在准备扩容时,线程二介入
![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/41aed567e3419e1314bfbf689e3255a2/192)
线程二:读取 HashMap进行扩容
![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/f44624419c0a49686fb12aa37527ee65/191)
线程一:继续执行
![](https://note.youdao.com/yws/public/resource/e4cec65883d9fdc24effba57dcfa5241/xmlnote/79424b2bf4a89902a9e85c64600268e4/193)
这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头A 的前边B.next=A本来 B.next=null到此也就结束了跟线程二一样的过程但是由于线程二扩容的原因将 B.next=A所以这里继续复制A让 A.next=B由此环形链表出现B.next=A; A.next=B
**注意jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647)
## HashSet 和 HashMap 区别
如果你看过 HashSet 源码的话就应该知道HashSet 底层就是基于 HashMap 实现的。HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)
## ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在JDK1.7的时候ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6以后 对 synchronized锁做了很多优化** 整个看起来就像是优化过且线程安全的 HashMap虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
**两者的对比图:**
图片来源http://www.cnblogs.com/chengxiao/p/6842045.html
HashTable:
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)
JDK1.7的ConcurrentHashMap
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg)
JDK1.8的ConcurrentHashMapTreeBin: 红黑二叉树节点
Node: 链表节点):
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg)
## ConcurrentHashMap线程安全的具体实现方式/底层具体实现
### JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。
```java
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
```
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似是一种数组和链表结构一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
### JDK1.8 (上面有示意图)
ConcurrentHashMap取消了Segment分段锁采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点这样只要hash不冲突就不会产生并发效率又提升N倍。
## 集合框架底层数据结构总结
### Collection
#### 1. List
- **Arraylist** Object数组
- **Vector** Object数组
- **LinkedList** 双向链表(JDK1.6之前为循环链表JDK1.7取消了循环)
详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)
#### 2. Set
- **HashSet无序唯一:** 基于 HashMap 实现的,底层采用 HashMap 来保存元素
- **LinkedHashSet** LinkedHashSet 继承与 HashSet并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
- **TreeSet有序唯一** 红黑树(自平衡的排序二叉树。)
### Map
- **HashMap** JDK1.8之前HashMap由数组+链表组成的数组是HashMap的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突.JDK1.8以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间
- **LinkedHashMap:** LinkedHashMap 继承自 HashMap所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析JDK1.8)》](https://www.imooc.com/article/22931)
- **HashTable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- **TreeMap:** 红黑树(自平衡的排序二叉树)
### 推荐阅读:
- [jdk1.8中ConcurrentHashMap的实现原理](https://blog.csdn.net/fjse51/article/details/55260493)
- [HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)
- [HASHMAP、HASHTABLE、CONCURRENTHASHMAP的原理与区别](http://www.yuanrengu.com/index.php/2017-01-17.html)
- [ConcurrentHashMap实现原理及源码分析](https://www.cnblogs.com/chengxiao/p/6842045.html)
- [java-并发-ConcurrentHashMap高并发机制-jdk1.8](https://blog.csdn.net/jianghuxiaojin/article/details/52006118#commentBox)

419
README.md
View File

@ -1,213 +1,268 @@
点击订阅[Java面试进阶指南](https://xiaozhuanlan.com/javainterview?rel=javaguide)(专为Java面试方向准备)。[为什么要弄这个专栏?](https://shimo.im/docs/9BJjNsNg7S4dCnz3/)
为了优化大家的阅读体验,我重新进行了排版,并且增加了较为详细的目录供大家参考!如果有老哥对操作系统比较重要的知识总结过的话,欢迎找我哦! 点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
一些常用资源[公众号](#公众号)后台回复关键字“1”即可免费无套路获取。
【限时福利】极客时间[《Java 并发编程面试必备》](#Java并发编程专栏)专栏限时特惠,购买之后的小伙伴加 [我的微信](#我的微信) 报上自己的极客时间大名可以找我会把24元返现退给大家减轻各位学习成本。 <p align="center">
<a href="https://github.com/Snailclimb/JavaGuide" target="_blank">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/logo - 副本.png" width=""/>
</a>
</p>
<div align="center"> <p align="center">
<img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/49833984.jpg" width=""/> <a href="https://snailclimb.gitee.io/javaguide"><img src="https://img.shields.io/badge/阅读-read-brightgreen.svg" alt="阅读"></a>
</br> <a href="#联系我"><img src="https://img.shields.io/badge/chat-微信群-blue.svg" alt="微信群"></a>
<a href="#公众号"><img src="https://img.shields.io/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-JavaGuide-lightgrey.svg" alt="公众号"></a>
[![QQ群](https://img.shields.io/badge/QQ%E7%BE%A4-984466017-red.svg)](//shang.qq.com/wpa/qunwpa?idkey=10aee68bd241e739e59a8dfb0d4b33690bd3a4b5af0d09142cbdae2cb8c3966a) <a href="#公众号"><img src="https://img.shields.io/badge/PDF-Java面试突击-important.svg" alt="公众号"></a>
</br> <a href="#投稿"><img src="https://img.shields.io/badge/support-投稿-critical.svg" alt="投稿"></a>
微信交流群添加 [我的微信](#我的微信) 后回复关键字“加群”即可入群。 </p>
</div>
<h2 align="center">Special Sponsors</h2>
<p align="center">
<!--
<a href="https://www.aliyun.com/acts/hi618/index?userCode=hf47liqn" target="_blank">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里云外投-1600-300.png" width="390px" height="70px" alt="阿里云618 2折起"/>
</a>
-->
<a href="https://coding.net/?utm_source=JavaGuide" target="_blank">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/006rNwoDgy1g2dw5gau7nj30eg02vwfr.jpg" alt="零成本开启敏捷研发" height="70px" width="390px"/>
</a>
</p>
推荐使用 https://snailclimb.top/JavaGuide/ 在线阅读(访问速度慢的话,请使用 https://snailclimb.gitee.io/javaguide )在线阅读内容本仓库同步一致。这种方式阅读的优势在于有侧边栏阅读体验更好Gitee pages 的访问速度相对来说也比较快。
## 目录 ## 目录
- [:coffee: Java](#coffee-java) - [Java](#java)
- [Java/J2EE 基础](#javaj2ee-基础) - [基础](#基础)
- [Java 集合框架](#java-集合框架) - [容器](#容器)
- [Java 多线程](#java-多线程) - [并发](#并发)
- [Java BIO,NIO,AIO](#java-bionioaio) - [JVM](#jvm)
- [Java 虚拟机 jvm](#java-虚拟机-jvm) - [I/O](#io)
- [:open_file_folder: 数据结构与算法](#open_file_folder-数据结构与算法) - [Java 8](#java-8)
- [数据结构](#数据结构) - [编程规范](#编程规范)
- [算法](#算法) - [网络](#网络)
- [:computer: 计算机网络与数据通信](#computer-计算机网络与数据通信) - [操作系统](#操作系统)
- [网络相关](#网络相关) - [Linux相关](#linux相关)
- [数据通信\(RESTful,RPC,消息队列\)总结](#数据通信restfulrpc消息队列总结) - [数据结构与算法](#数据结构与算法)
- [:iphone: 操作系统](#iphone-操作系统) - [数据结构](#数据结构)
- [Linux相关](#linux相关) - [算法](#算法)
- [:pencil2: 主流框架/软件](#pencil2-主流框架软件) - [数据库](#数据库)
- [Spring](#spring) - [MySQL](#mysql)
- [ZooKeeper](#zookeeper) - [Redis](#redis)
- [:floppy_disk: 数据存储](#floppy_disk-数据存储) - [系统设计](#系统设计)
- [MySQL](#mysql) - [设计模式(工厂模式、单例模式 ... )](#设计模式)
- [Redis](#redis) - [常用框架(Spring、Zookeeper ... )](#常用框架)
- [:punch: 架构](#punch-架构) - [数据通信(消息队列、Dubbo ... )](#数据通信)
- [:musical_note: 面试必备](#musical_note-面试必备) - [网站架构](#网站架构)
- [备战春招/秋招系列](#备战春招秋招系列) - [面试指南](#面试指南)
- [最最最常见的Java面试题总结](#最最最常见的java面试题总结) - [备战面试](#备战面试)
- [Java学习/面试开源仓库推荐](#java学习面试开源仓库推荐) - [常见面试题总结](#常见面试题总结)
- [:art: 闲谈](#art-闲谈) - [面经](#面经)
- [:envelope: 说明](#envelope-说明) - [工具](#工具)
- [Git](#git)
- [Docker](#Docker)
- [资料](#资料)
- [书单](#书单)
- [Github榜单](#Github榜单)
- [待办](#待办)
- [说明](#说明)
## 待办 ## Java
- [ ] Java 8 新特性总结 ### 基础
- [x] BIO,NIO,AIO 总结
- [ ] Netty 总结
- [ ] 数据结构总结重构
* [Java 基础知识回顾](docs/java/Java基础知识.md)
* [Java 基础知识疑难点/易错点](docs/java/Java疑难点.md)
* [一些重要的Java程序设计题](docs/java/Java程序设计题.md)
* [J2EE 基础知识回顾](docs/java/J2EE基础知识.md)
## :coffee: Java ### 容器
### Java/J2EE 基础 * [Java容器常见面试题/知识点总结](docs/java/collection/Java集合框架常见面试题.md)
* [ArrayList 源码学习](docs/java/collection/ArrayList.md)
* [LinkedList 源码学习](docs/java/collection/LinkedList.md)
* [HashMap(JDK1.8)源码学习](docs/java/collection/HashMap.md)
* [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) ### 并发
* [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md)
* [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md)
* [static 关键字详解](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/static.md)
### Java 集合框架 * [Java 并发基础常见面试题总结](docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md)
* [Java 并发进阶常见面试题总结](docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md)
* [并发容器总结](docs/java/Multithread/并发容器总结.md)
* [乐观锁与悲观锁](docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md)
* [JUC 中的 Atomic 原子类总结](docs/java/Multithread/Atomic.md)
* [AQS 原理以及 AQS 同步组件总结](docs/java/Multithread/AQS.md)
* [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) ### JVM
* [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md)
* [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md)
* [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/ArrayList-Grow.md)
* [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md)
* [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md)
### Java 多线程 * [一 Java内存区域](docs/java/jvm/Java内存区域.md)
* [多线程系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/多线程系列.md) * [二 JVM垃圾回收](docs/java/jvm/JVM垃圾回收.md)
* [并发编程面试必备synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) * [三 JDK 监控和故障处理工具](docs/java/jvm/JDK监控和故障处理工具总结.md)
* [并发编程面试必备:乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) * [四 类文件结构](docs/java/jvm/类文件结构.md)
* [并发编程面试必备JUC 中的 Atomic 原子类总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/Atomic.md) * [五 类加载过程](docs/java/jvm/类加载过程.md)
* [并发编程面试必备AQS 原理以及 AQS 同步组件总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/AQS.md) * [六 类加载器](docs/java/jvm/类加载器.md)
* [BATJ都爱问的多线程面试题](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/BATJ都爱问的多线程面试题.md)
* [并发容器总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/并发容器总结.md)
### Java 虚拟机 jvm ### I/O
* [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) * [BIO,NIO,AIO 总结 ](docs/java/BIO-NIO-AIO.md)
* [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) * [Java IO 与 NIO系列文章](docs/java/Java%20IO与NIO.md)
* [《深入理解Java虚拟机》第2版学习笔记](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机jvm.md)
### Java 8
### Java BIO,NIO,AIO * [Java 8 新特性总结](docs/java/What's%20New%20in%20JDK8/Java8Tutorial.md)
* [Java 8 学习资源推荐](docs/java/What's%20New%20in%20JDK8/Java8教程推荐.md)
* [BIO,NIO,AIO 总结 ](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/BIO%2CNIO%2CAIO%20summary.md) ### 编程规范
* [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md)
### 设计模式 - [Java 编程规范](docs/java/Java编程规范.md)
* [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) ## 网络
## :open_file_folder: 数据结构与算法 * [计算机网络常见面试题](docs/network/计算机网络.md)
* [计算机网络基础知识总结](docs/network/干货:计算机网络知识总结.md)
* [HTTPS中的TLS](docs/network/HTTPS中的TLS.md)
### 数据结构 ## 操作系统
* [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md)
### 算法
* [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md)
* [常见安全算法MD5、SHA1、Base64等等总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法MD5、SHA1、Base64等等总结.md)
* [算法总结——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md)
* [算法总结——几道常见的链表算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/Leetcode-LinkList1.md)
## :computer: 计算机网络与数据通信
### 网络相关
* [计算机网络常见面试题](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/计算机网络.md)
* [计算机网络基础知识总结](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/干货:计算机网络知识总结.md)
* [HTTPS中的TLS](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/HTTPS中的TLS.md)
### 数据通信(RESTful,RPC,消息队列)总结
* [数据通信(RESTful、RPC、消息队列)相关知识点总结](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md)
* [Dubbo 总结:关于 Dubbo 的重要知识点](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/dubbo.md)
* [消息队列总结:新手也能看懂,消息队列其实很简单](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/message-queue.md)
* [一文搞懂 RabbitMQ 的重要概念以及安装](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/rabbitmq.md)
## :iphone: 操作系统
### Linux相关 ### Linux相关
* [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) * [后端程序员必备的 Linux 基础知识](docs/operating-system/后端程序员必备的Linux基础知识.md)
* [Shell 编程入门](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/Shell.md) * [Shell 编程入门](docs/operating-system/Shell.md)
## :pencil2: 主流框架/软件
### Spring ## 数据结构与算法
* [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) ### 数据结构
* [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md)
* [SpringMVC 工作原理详解](https://github.com/Snailclimb/JavaGuide/blob/master/主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md)
### ZooKeeper - [数据结构知识学习与面试](docs/dataStructures-algorithms/数据结构.md)
* [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) ### 算法
* [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper数据模型和常见命令.md)
## :floppy_disk: 数据存储 - [算法学习资源推荐](docs/dataStructures-algorithms/算法学习资源推荐.md)
- [几道常见的字符串算法题总结 ](docs/dataStructures-algorithms/几道常见的子符串算法题.md)
- [几道常见的链表算法题总结 ](docs/dataStructures-algorithms/几道常见的链表算法题.md)
- [剑指offer部分编程题](docs/dataStructures-algorithms/剑指offer部分编程题.md)
- [公司真题](docs/dataStructures-algorithms/公司真题.md)
- [回溯算法经典案例之N皇后问题](docs/dataStructures-algorithms/Backtracking-NQueens.md)
## 数据库
### MySQL ### MySQL
* [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) * [MySQL 学习与面试](docs/database/MySQL.md)
* [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) * [一千行MySQL学习笔记](docs/database/一千行MySQL命令.md)
* [MySQL高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md)
* [数据库索引总结](docs/database/MySQL%20Index.md)
* [事务隔离级别(图文详解)](docs/database/事务隔离级别(图文详解).md)
* [一条SQL语句在MySQL中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md)
### Redis ### Redis
* [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) * [Redis 总结](docs/database/Redis/Redis.md)
* [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) * [Redlock分布式锁](docs/database/Redis/Redlock分布式锁.md)
* [如何做可靠的分布式锁Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁Redlock真的可行么.md) * [如何做可靠的分布式锁Redlock真的可行么](docs/database/Redis/如何做可靠的分布式锁Redlock真的可行么.md)
## :punch: 架构 ## 系统设计
* [一文读懂分布式应该学什么](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) ### 设计模式
* [8 张图读懂大型网站技术架构](https://github.com/Snailclimb/JavaGuide/blob/master/架构/8%20张图读懂大型网站技术架构.md)
* [【面试精选】关于大型网站系统架构你不得不懂的10个问题](https://github.com/Snailclimb/JavaGuide/blob/master/架构/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md)
## :musical_note: 面试必备 - [设计模式系列文章](docs/system-design/设计模式.md)
### 备战春招/秋招系列 ### 常用框架
* [【备战春招/秋招系列1】程序员的简历就该这样写](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) #### Spring
* [手把手教你用Markdown写一份高质量的简历](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/手把手教你用Markdown写一份高质量的简历.md)
* [【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/interviewPrepare.md)
* [【备战春招/秋招系列3】Java程序员必备书单](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/books.md)
* [ 【备战春招/秋招系列4】美团面经总结基础篇 (附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团-基础篇.md)
* [ 【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团-进阶篇.md)
* [ 【备战春招/秋招系列5】美团面经总结终结篇篇 (附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团-终结篇.md)
### 最最最常见的Java面试题总结 - [Spring 学习与面试](docs/system-design/framework/spring/Spring.md)
- [Spring 常见问题总结](docs/system-design/framework/spring/SpringInterviewQuestions.md)
- [Spring中bean的作用域与生命周期](docs/system-design/framework/spring/SpringBean.md)
- [SpringMVC 工作原理详解](docs/system-design/framework/spring/SpringMVC-Principle.md)
- [Spring中都用到了那些设计模式?](docs/system-design/framework/spring/Spring-Design-Patterns.md)
这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 #### ZooKeeper
* [第一周2018-8-7](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周2018-8-7.md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) - [ZooKeeper 相关概念总结](docs/system-design/framework/ZooKeeper.md)
* [第二周2018-8-13](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么String为什么是不可变的、什么是反射机制反射机制的应用场景有哪些......) - [ZooKeeper 数据模型和常见命令](docs/system-design/framework/ZooKeeper数据模型和常见命令.md)
* [第三周2018-08-22](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结)
* [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。)
### Java学习/面试开源仓库推荐 ### 数据通信
* [盘点一下Github上开源的Java面试/学习相关的仓库看完弄懂薪资至少增加10k](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/JavaInterviewGithub.md) - [数据通信(RESTful、RPC、消息队列)相关知识点总结](docs/system-design/data-communication/summary.md)
- [Dubbo 总结:关于 Dubbo 的重要知识点](docs/system-design/data-communication/dubbo.md)
- [消息队列总结](docs/system-design/data-communication/message-queue.md)
- [RabbitMQ 入门](docs/system-design/data-communication/rabbitmq.md)
- [RocketMQ的几个简单问题与答案](docs/system-design/data-communication/RocketMQ-Questions.md)
## :art: 闲谈 ### 网站架构
* [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) - [一文读懂分布式应该学什么](docs/system-design/website-architecture/分布式.md)
* [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20%E7%A7%8B%E6%8B%9B.md) - [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md)
* [这7个问题可能大部分Java程序员都比较关心吧](https://github.com/Snailclimb/JavaGuide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/java%20programmer%20need%20know.md) - [【面试精选】关于大型网站系统架构你不得不懂的10个问题](docs/system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md)
* [【2018总结】即使平凡也要热爱自己的生活](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20summary.md)
## 面试指南
### 备战面试
* [【备战面试1】程序员的简历就该这样写](docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md)
* [【备战面试2】初出茅庐的程序员该如何准备面试](docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md)
* [【备战面试3】7个大部分程序员在面试前很关心的问题](docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md)
* [【备战面试4】Github上开源的Java面试/学习相关的仓库推荐](docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md)
* [【备战面试5】如果面试官问你“你有什么问题问我吗”时你该如何回答](docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md)
* [【备战面试6】美团面试常见问题总结附详解答案](docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md)
### 常见面试题总结
* [第一周2018-8-7](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周2018-8-7.md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals)
* [第二周2018-8-13](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么String为什么是不可变的、什么是反射机制反射机制的应用场景有哪些......)
* [第三周2018-08-22](docs/java/collection/Java集合框架常见面试题.md) Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结)
* [第四周(2018-8-30).md](docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。)
### 面经
- [5面阿里,终获offer(2018年秋招)](docs/essential-content-for-interview/BATJrealInterviewExperience/5面阿里,终获offer.md)
- [蚂蚁金服2019实习生面经总结(已拿口头offer)](docs/essential-content-for-interview/BATJrealInterviewExperience/蚂蚁金服实习生面经总结(已拿口头offer).md)
- [2019年蚂蚁金服、头条、拼多多的面试总结](docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md)
## 工具
### Git
* [Git入门](docs/tools/Git.md)
### Docker
* [Docker 入门](docs/tools/Docker.md)
* [一文搞懂 Docker 镜像的常用操作!](docs/tools/Docker-Image.md)
## 资料
### 书单
- [Java程序员必备书单](docs/data/java-recommended-books.md)
### Github榜单
- [Java 项目月榜单](docs/github-trending/JavaGithubTrending.md)
*** ***
## :envelope: 说明 ## 待办
### 项目介绍 - [ ] Java 多线程类别知识重构(---正在进行中---)
- [ ] Netty 总结(---正在进行中---)
- [ ] 数据结构总结重构(---正在进行中---)
该文档主要是笔主在学习 Java 的过程中的一些学习笔记,但是为了能够涉及到大部分后端学习所需的技术知识点我也会偶尔引用一些别人的优秀文章的链接。文档大部分内容都是笔者参考书籍以及自己的原创。少部分面试题回答参考了其他人已有答案,上面都已注明。 ## 说明
该文档涉及的主要内容包括: Java、 数据结构与算法、计算机网络与数据通信、 操作系统、主流框架、数据存储、架构、面试必备知识点等等。相信不论你是前端还是后端都能在这份文档中收获到东西。 ### 介绍
* **对于 Java 初学者来说:** 本文档倾向于给你提供一个比较详细的学习路径让你对于Java整体的知识体系有一个初步认识。另外本文的一些文章
也是你学习和复习 Java 知识不错的实践;
* **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。
Markdown 格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。
利用 docsify 生成文档部署在 Github pages: [docsify 官网介绍](https://docsify.js.org/#/)
### 关于转载 ### 关于转载
**如果需要引用到本仓库的一些东西,必须注明转载地址!!!毕竟大多都是手敲的,或者引用的是我的原创文章,希望大家尊重一下作者的劳动**:smiley::smiley::smiley: 如果你需要转载本仓库的一些文章到自己的博客的话,记得注明原文地址就可以了。
### 如何对该开源文档进行贡献 ### 如何对该开源文档进行贡献
@ -217,25 +272,71 @@
### 为什么要做这个开源文档? ### 为什么要做这个开源文档?
在我们学习Java的时候很多人会面临我不知道继续学什么或者面试会问什么的尴尬情况我本人之前就很迷茫:smile:。所以我决定通过这个开源平台来帮助一些有需要的人通过下面的内容你会掌握系统的Java学习以及面试的相关知识。本来是想通过Gitbook的形式来制作的后来想了想觉得可能有点大题小做 :grin: 。另外我自己一个人的力量毕竟有限希望各位有想法的朋友可以提issue。开源的最大目的是让更多人参与进来这样文档的正确性才能得以保障 初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。
### 最后 ### 投稿
本人会利用业余时间一直更新下去目前还有很多地方不完善一些知识点我会原创总结还有一些知识点如果说网上有比较好的文章了我会把这些文章加入进去。您也可以关注我的微信公众号“Java面试通关手册”我会在这里分享一些自己的原创文章。 另外该文档格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。如果大家需要与我交流,可以扫描下方二维码添加我的微信: 由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。大家也可以对自己的文章进行自荐,对于不错的文章不仅可以成功在本仓库展示出来更可以获得作者送出的 50 元左右的任意书籍进行奖励(当然你也可以直接折现50元)。
### 我的微信 ### 联系
![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg) 添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。
### Java并发编程专栏 ![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/wechat1.jpg)
微信扫描下方二维码购买之后我会将自己得到的24元返现都还给你减轻各位的学习成本 ### Contributor
![ Java并发编程专栏](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Java并发编程实战.jpg) 下面是笔主收集的一些对本仓库提过有价值的pr或者issue的朋友人数较多如果你也对本仓库提过不错的pr或者issue的话你可以加我的微信与我联系。下面的排名不分先后
<a href="https://github.com/fanofxiaofeng">
<img src="https://avatars0.githubusercontent.com/u/3983683?s=460&v=4" width="45px"></a>
<a href="https://github.com/dongzl">
<img src="https://avatars1.githubusercontent.com/u/5917359?s=460&v=4" width="45px"></a>
<a href="https://github.com/Gene1994">
<img src="https://avatars3.githubusercontent.com/u/24930369?s=460&v=4" width="45px">
</a>
<a href="https://github.com/spikesp">
<img src="https://avatars0.githubusercontent.com/u/12581996?s=460&v=4" width="45px"></a>
<a href="https://github.com/illusorycloud">
<img src="https://avatars3.githubusercontent.com/u/31980412?s=460&v=4" width="45px">
</a>
<a href="https://github.com/LiWenGu">
<img src="https://avatars0.githubusercontent.com/u/15909210?s=460&v=4" width="45px">
</a>
<a href="https://github.com/kinglaw1204">
<img src="https://avatars1.githubusercontent.com/u/20039931?s=460&v=4" width="45px">
</a>
<a href="https://github.com/jun1st">
<img src="https://avatars2.githubusercontent.com/u/14312378?s=460&v=4" width="45px">
</a>"
<a href="https://github.com/fantasygg">
<img src="https://avatars3.githubusercontent.com/u/13445354?s=460&v=4" width="45px">
</a>
<a href="https://github.com/debugjoker">
<img src="https://avatars3.githubusercontent.com/u/26218005?s=460&v=4" width="45px">
</a>
<a href="https://github.com/zhyank">
<img src="https://avatars0.githubusercontent.com/u/17696240?s=460&v=4" width="45px">
</a>
<a href="https://github.com/Goose9527">
<img src="https://avatars2.githubusercontent.com/u/43314997?s=460&v=4" width="45px">
</a>
<a href="https://github.com/yuechuanx">
<img src="https://avatars3.githubusercontent.com/u/19339293?s=460&v=4" width="45px">
</a>
<a href="https://github.com/cnLGMing">
<img src="https://avatars2.githubusercontent.com/u/15910705?s=460&v=4" width="45px">
</a>
<a href="https://github.com/fanchenggang">
<img src="https://avatars0.githubusercontent.com/u/20358122?s=460&v=4" width="45px">
</a>
### 公众号 ### 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。我是 ThoughtWorks 准入职Java工程师。专注Java知识分享开源 Java 学习指南——JavaGuide12k+ Star的作者。公众号多篇文章被各大技术社区转载。公众号后台回复关键字“1”可以领取一份我精选的Java资源哦可以扫描下方二维码或者通过微信的搜一搜搜索ID“Java_Guide”即可。 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334) **Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

0
docs/.nojekyll Normal file
View File

242
docs/HomePage.md Normal file
View File

@ -0,0 +1,242 @@
点击订阅[Java面试进阶指南](https://xiaozhuanlan.com/javainterview?rel=javaguide)(专为Java面试方向准备)。[为什么要弄这个专栏?](https://shimo.im/./9BJjNsNg7S4dCnz3/)
<h1 align="center">Java 学习/面试指南</h1>
<p align="center">
<a href="https://github.com/Snailclimb/JavaGuide" target="_blank">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/logo - 副本.png" width=""/>
</a>
## 目录
- [Java](#java)
- [基础](#基础)
- [容器](#容器)
- [并发](#并发)
- [JVM](#jvm)
- [I/O](#io)
- [Java 8](#java-8)
- [编程规范](#编程规范)
- [网络](#网络)
- [操作系统](#操作系统)
- [Linux相关](#linux相关)
- [数据结构与算法](#数据结构与算法)
- [数据结构](#数据结构)
- [算法](#算法)
- [数据库](#数据库)
- [MySQL](#mysql)
- [Redis](#redis)
- [系统设计](#系统设计)
- [设计模式(工厂模式、单例模式 ... )](#设计模式)
- [常用框架(Spring、Zookeeper ... )](#常用框架)
- [数据通信(消息队列、Dubbo ... )](#数据通信)
- [网站架构](#网站架构)
- [面试指南](#面试指南)
- [备战面试](#备战面试)
- [常见面试题总结](#常见面试题总结)
- [面经](#面经)
- [工具](#工具)
- [Git](#git)
- [Docker](#Docker)
- [资料](#资料)
- [书单](#书单)
- [Github榜单](#Github榜单)
- [待办](#待办)
- [说明](#说明)
## Java
### 基础
* [Java 基础知识回顾](java/Java基础知识.md)
* [Java 基础知识疑难点总结](java/Java疑难点.md)
* [J2EE 基础知识回顾](java/J2EE基础知识.md)
### 容器
* [Java容器常见面试题/知识点总结](java/collection/Java集合框架常见面试题.md)
* [ArrayList 源码学习](java/collection/ArrayList.md)
* [LinkedList 源码学习](java/collection/LinkedList.md)
* [HashMap(JDK1.8)源码学习](java/collection/HashMap.md)
### 并发
* [Java 并发基础常见面试题总结](java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md)
* [Java 并发进阶常见面试题总结](java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md)
* [并发容器总结](java/Multithread/并发容器总结.md)
* [乐观锁与悲观锁](essential-content-for-interview/面试必备之乐观锁与悲观锁.md)
* [JUC 中的 Atomic 原子类总结](java/Multithread/Atomic.md)
* [AQS 原理以及 AQS 同步组件总结](java/Multithread/AQS.md)
### JVM
* [一 Java内存区域](java/jvm/Java内存区域.md)
* [二 JVM垃圾回收](java/jvm/JVM垃圾回收.md)
* [三 JDK 监控和故障处理工具](java/jvm/JDK监控和故障处理工具总结.md)
* [四 类文件结构](java/jvm/类文件结构.md)
* [五 类加载过程](java/jvm/类加载过程.md)
* [六 类加载器](java/jvm/类加载器.md)
### I/O
* [BIO,NIO,AIO 总结 ](java/BIO-NIO-AIO.md)
* [Java IO 与 NIO系列文章](java/Java%20IO与NIO.md)
### Java 8
* [Java 8 新特性总结](java/What's%20New%20in%20JDK8/Java8Tutorial.md)
* [Java 8 学习资源推荐](java/What's%20New%20in%20JDK8/Java8教程推荐.md)
### 编程规范
- [Java 编程规范](java/Java编程规范.md)
## 网络
* [计算机网络常见面试题](network/计算机网络.md)
* [计算机网络基础知识总结](network/干货:计算机网络知识总结.md)
* [HTTPS中的TLS](network/HTTPS中的TLS.md)
## 操作系统
### Linux相关
* [后端程序员必备的 Linux 基础知识](operating-system/后端程序员必备的Linux基础知识.md)
* [Shell 编程入门](operating-system/Shell.md)
## 数据结构与算法
### 数据结构
- [数据结构知识学习与面试](dataStructures-algorithms/数据结构.md)
### 算法
- [算法学习资源推荐](dataStructures-algorithms/算法学习资源推荐.md)
- [几道常见的字符串算法题总结 ](dataStructures-algorithms/几道常见的子符串算法题.md)
- [几道常见的链表算法题总结 ](dataStructures-algorithms/几道常见的链表算法题.md)
- [剑指offer部分编程题](dataStructures-algorithms/剑指offer部分编程题.md)
- [公司真题](dataStructures-algorithms/公司真题.md)
- [回溯算法经典案例之N皇后问题](dataStructures-algorithms/Backtracking-NQueens.md)
## 数据库
### MySQL
* [MySQL 学习与面试](database/MySQL.md)
* [一千行MySQL学习笔记](database/一千行MySQL命令.md)
* [MySQL高性能优化规范建议](database/MySQL高性能优化规范建议.md)
* [数据库索引总结](database/MySQL%20Index.md)
* [事务隔离级别(图文详解)](database/事务隔离级别(图文详解).md)
* [一条SQL语句在MySQL中如何执行的](database/一条sql语句在mysql中如何执行的.md)
### Redis
* [Redis 总结](database/Redis/Redis.md)
* [Redlock分布式锁](database/Redis/Redlock分布式锁.md)
* [如何做可靠的分布式锁Redlock真的可行么](database/Redis/如何做可靠的分布式锁Redlock真的可行么.md)
## 系统设计
### 设计模式
- [设计模式系列文章](system-design/设计模式.md)
### 常用框架
#### Spring
- [Spring 学习与面试](system-design/framework/spring/Spring.md)
- [Spring 常见问题总结](system-design/framework/spring/SpringInterviewQuestions.md)
- [Spring中bean的作用域与生命周期](system-design/framework/spring/SpringBean.md)
- [SpringMVC 工作原理详解](system-design/framework/spring/SpringMVC-Principle.md)
- [Spring中都用到了那些设计模式?](system-design/framework/spring/Spring-Design-Patterns.md)
#### ZooKeeper
- [ZooKeeper 相关概念总结](system-design/framework/ZooKeeper.md)
- [ZooKeeper 数据模型和常见命令](system-design/framework/ZooKeeper数据模型和常见命令.md)
### 数据通信
- [数据通信(RESTful、RPC、消息队列)相关知识点总结](system-design/data-communication/summary.md)
- [Dubbo 总结:关于 Dubbo 的重要知识点](system-design/data-communication/dubbo.md)
- [消息队列总结](system-design/data-communication/message-queue.md)
- [RabbitMQ 入门](system-design/data-communication/rabbitmq.md)
- [RocketMQ的几个简单问题与答案](system-design/data-communication/RocketMQ-Questions.md)
### 网站架构
- [一文读懂分布式应该学什么](system-design/website-architecture/分布式.md)
- [8 张图读懂大型网站技术架构](system-design/website-architecture/8%20张图读懂大型网站技术架构.md)
- [【面试精选】关于大型网站系统架构你不得不懂的10个问题](system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md)
## 面试指南
### 备战面试
* [【备战面试1】程序员的简历就该这样写](essential-content-for-interview/PreparingForInterview/程序员的简历之道.md)
* [【备战面试2】初出茅庐的程序员该如何准备面试](essential-content-for-interview/PreparingForInterview/interviewPrepare.md)
* [【备战面试3】7个大部分程序员在面试前很关心的问题](essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md)
* [【备战面试4】Github上开源的Java面试/学习相关的仓库推荐](essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md)
* [【备战面试5】如果面试官问你“你有什么问题问我吗”时你该如何回答](essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md)
* [【备战面试6】美团面试常见问题总结附详解答案](essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md)
### 常见面试题总结
* [第一周2018-8-7](essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周2018-8-7.md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals)
* [第二周2018-8-13](essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么String为什么是不可变的、什么是反射机制反射机制的应用场景有哪些......)
* [第三周2018-08-22](java/collection/Java集合框架常见面试题.md) Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结)
* [第四周(2018-8-30).md](essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。)
### 面经
- [5面阿里,终获offer(2018年秋招)](essential-content-for-interview/BATJrealInterviewExperience/5面阿里,终获offer.md)
- [蚂蚁金服2019实习生面经总结(已拿口头offer)](essential-content-for-interview/BATJrealInterviewExperience/蚂蚁金服实习生面经总结(已拿口头offer).md)
- [2019年蚂蚁金服、头条、拼多多的面试总结](essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md)
## 工具
### Git
* [Git入门](tools/Git.md)
### Docker
* [Docker 入门](tools/Docker.md)
* [一文搞懂 Docker 镜像的常用操作!](tools/Docker-Image.md)
## 资料
### 书单
- [Java程序员必备书单](data/java-recommended-books.md)
### Github榜单
- [Java 项目月榜单](github-trending/JavaGithubTrending.md)
***
## 待办
- [x] [Java 8 新特性总结](./java/What's%20New%20in%20JDK8/Java8Tutorial.md)
- [x] [Java 8 新特性详解](./java/What's%20New%20in%20JDK8/Java8教程推荐.md)
- [ ] Java 多线程类别知识重构(---正在进行中---)
- [x] [BIO,NIO,AIO 总结 ](./java/BIO-NIO-AIO.md)
- [ ] Netty 总结(---正在进行中---)
- [ ] 数据结构总结重构(---正在进行中---)
## 联系我
添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。
![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg)
## 公众号
- 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
- 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 **"Java面试突击"** 即可免费领取!
- 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
<p align="center">
<img src="https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334" width=""/>
</p>

10
docs/_coverpage.md Normal file
View File

@ -0,0 +1,10 @@
<p align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3logo-透明.png" width=""/>
</p>
<h1 align="center">Java 学习/面试指南</h1>
[常用资源](https://shimo.im/docs/MuiACIg1HlYfVxrj/)
[GitHub](<https://github.com/Snailclimb/JavaGuide>)
[开始阅读](#java)

View File

@ -0,0 +1,115 @@
<!-- TOC -->
- [Java](#java)
- [基础](#基础)
- [并发](#并发)
- [JVM](#jvm)
- [Java8 新特性](#java8-新特性)
- [代码优化](#代码优化)
- [网络](#网络)
- [操作系统](#操作系统)
- [数据结构与算法](#数据结构与算法)
- [数据库](#数据库)
- [系统设计](#系统设计)
- [设计模式](#设计模式)
- [常用框架](#常用框架)
- [网站架构](#网站架构)
- [软件底层](#软件底层)
- [其他](#其他)
<!-- /TOC -->
## Java
### 基础
- [《Head First Java》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.71.0K+人评价) 可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故 Java 知识点。
- [《Java 核心技术卷 1+卷 2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。
- [《JAVA 网络编程 第 4 版》](https://book.douban.com/subject/26259017/) 可以系统的学习一下网络的一些概念以及网络编程在 Java 中的使用。
- [《Java 编程思想 (第 4 版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.13.2K+人评价大部分人称之为Java领域的圣经但我不推荐初学者阅读有点劝退的味道。稍微有点基础后阅读更好。
### 并发
- [《Java 并发编程之美》](<https://book.douban.com/subject/30351286/>) 推荐2018 年 10 月出版的一本书,个人感觉非常不错,对每个知识点的讲解都很棒。
- [《Java 并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.20.2K+人评价): 这本书不是很适合作为 Java 并发入门书籍,需要具备一定的 JVM 基础。我感觉有些东西讲的还是挺深入的,推荐阅读。
- [《实战 Java 高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐,豆瓣评分 8.3 书的质量没的说,推荐大家好好看一下。
- [《Java 高并发编程详解》](https://book.douban.com/subject/30255689/)(豆瓣评分 7.6 2018 年 6 月出版的一本书,内容很详细,但可能又有点过于啰嗦,不过这只是我的感觉。
### JVM
- [《深入理解 Java 虚拟机(第 2 版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.91.0K+人评价):建议多刷几遍,书中的所有知识点可以通过 JAVA 运行时区域和 JAVA 的内存模型与线程两个大模块罗列完全。
- [《实战 JAVA 虚拟机》](https://book.douban.com/subject/26354292/)(推荐,豆瓣评分 8.01.0K+人评价):作为入门的了解 Java 虚拟机的知识还是不错的。
### Java8 新特性
- [《Java 8 实战》](https://book.douban.com/subject/26772632/) (推荐,豆瓣评分 9.2 ):面向 Java 8 的技能升级,包括 Lambdas、流和函数式编程特性。实战系列的一贯风格让自己快速上手应用起来。Java 8 支持的 Lambda 是精简表达在语法上提供的支持。Java 8 提供了 Stream学习和使用可以建立流式编程的认知。
- [《Java 8 编程参考官方教程》](https://book.douban.com/subject/26556574/) (推荐,豆瓣评分 9.2):也还不错吧。
### 代码优化
- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。
- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.01.4K+人评价):本书介绍了在 Java 编程中 78 条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对 Java 平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。
- [《代码整洁之道》](https://book.douban.com/subject/5442024/)(推荐,豆瓣评分 9.1):虽然是用 Java 语言作为例子,全篇都是在阐述 Java 面向对象的思想,但是其中大部分内容其它语言也能应用到。
- **阿里巴巴 Java 开发手册(详尽版)** [https://github.com/alibaba/p3c/blob/master/阿里巴巴 Java 开发手册(详尽版).pdf](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf)
- **Google Java 编程风格指南:** <http://www.hawstein.com/posts/google-java-style.html>
## 网络
- [《图解 HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲 HTTP很有意思不会觉得枯燥大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。
- [《HTTP 权威指南》](https://book.douban.com/subject/10746113/) (推荐,豆瓣评分 8.6:如果要全面了解 HTTP 非此书不可!
## 操作系统
- [《鸟哥的 Linux 私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.10.3K+人评价):本书是最具知名度的 Linux 入门书《鸟哥的 Linux 私房菜基础学习篇》的最新版,全面而详细地介绍了 Linux 操作系统。全书分为 5 个部分:第一部分着重说明 Linux 的起源及功能,如何规划和安装 Linux 主机;第二部分介绍 Linux 的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell 和管理系统的好帮手 shell 脚本,另外还介绍了文字编辑器 vi 和 vim 的使用方法;第四部分介绍了对于系统安全非常重要的 Linux 账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员 (root) 的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。
## 数据结构与算法
- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。
- [《数据结构与算法分析C 语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.91.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第 2 版的简体中译本。原书曾被评为 20 世纪顶尖的 30 部计算机著作之一,作者 Mark Allen Weiss 在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界 500 余所大学用作教材。
- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.40.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥!
- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.30.4K+人评价Java 语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是 Java 程序员的必备书籍之一了。
## 数据库
- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.30.4K+人评价mysql 领域的经典之作拥有广泛的影响力。不但适合数据库管理员dba阅读也适合开发人员参考学习。不管是数据库新手还是专家相信都能从本书有所收获。
- [《Redis 实战》](https://book.douban.com/subject/26612779/):如果你想了解 Redis 的一些概念性知识的话,这本书真的非常不错。
- [《Redis 设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.50.5K+人评价):也还行吧!
- [《MySQL 技术内幕-InnoDB 存储引擎》](<https://book.douban.com/subject/24708143/>)(推荐,豆瓣评分 8.7):了解 InnoDB 存储引擎底层原理必备的一本书,比较深入。
## 系统设计
### 设计模式
- [《设计模式 : 可复用面向对象软件的基础》 ](https://book.douban.com/subject/1052241/) (推荐,豆瓣评分 9.1):设计模式的经典!
- [《Head First 设计模式(中文版)》](https://book.douban.com/subject/2243615/) (推荐,豆瓣评分 9.2):相当赞的一本设计模式入门书籍。用实际的编程案例讲解算法设计中会遇到的各种问题和需求变更(对的,连需求变更都考虑到了!),并以此逐步推导出良好的设计模式解决办法。
### 常用框架
- [《深入分析 Java Web 技术内幕》](https://book.douban.com/subject/25953851/) 感觉还行,涉及的东西也蛮多。
- [《Netty 实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.892 人评价):内容很细,如果想学 Netty 的话,推荐阅读这本书!
- [《从 Paxos 到 Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.80.3K 人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。同时本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧旨在帮助读者全面了解 ZooKeeper并更好地使用和运维 ZooKeeper。
- [《Spring 实战(第 4 版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.30.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的新华字典,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring这才刚刚开始”。
- [《RabbitMQ 实战指南》](https://book.douban.com/subject/27591386/)《RabbitMQ 实战指南》从消息中间件的概念和 RabbitMQ 的历史切入,主要阐述 RabbitMQ 的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝 RabbitMQ 的使用,这本书是你最好的选择;如果你想深入 RabbitMQ 的原理,这本书也是你最好的选择;总之,如果你想玩转 RabbitMQ这本书一定是最值得看的书之一
- [《Spring Cloud 微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了 Spring Cloud 针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍《Spring Cloud 微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。
- [《第一本 Docker 书》](https://book.douban.com/subject/26780404/)Docker 入门书籍!
### 网站架构
- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松但是却可以学到很多东西非常推荐了。另外我写过这本书的思维导图关注我的微信公众号“Java 面试通关手册”回复“大型网站技术架构”即可领取思维导图。
- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。
### 软件底层
- [《深入剖析 Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.40.2K+人评价):本书深入剖析 Tomcat 4 和 Tomcat 5 中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发 Tomcat 组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。
- [《深入理解 Nginx第 2 版)》](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。
## 其他
- [《黑客与画家》](https://read.douban.com/ebook/387525/?dcs=subject-rec&dcm=douban&dct=2243615)这本书是硅谷创业之父Y Combinator 创始人 Paul Graham 的文集。之所以叫这个名字,是因为作者认为黑客(并非负面的那个意思)与画家有着极大的相似性,他们都是在创造,而不是完成某个任务。

View File

@ -0,0 +1,145 @@
# N皇后
[51. N皇后](https://leetcode-cn.com/problems/n-queens/)
### 题目描述
> n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
>
![ANUzjA.png](https://s2.ax1x.com/2019/03/26/ANUzjA.png)
>
上图为 8 皇后问题的一种解法。
>
给定一个整数 n返回所有不同的 n 皇后问题的解决方案。
>
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
```
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
```
### 问题分析
约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。
使用一维数组表示一种解法下标index表示行value表示该行的Q皇后在哪一列。
每行只存储一个元素,然后递归到下一行,这样就不用判断行了,只需要判断列和对角线。
### Solution1
当result[row] = column时即row行的棋子在column列。
对于[0, row-1]的任意一行i 行),若 row 行的棋子和 i 行的棋子在同一列则有result[i] == column;
若 row 行的棋子和 i 行的棋子在同一对角线,等腰直角三角形两直角边相等,即 row - i == Math.abs(result[i] - column)
布尔类型变量 isValid 的作用是剪枝,减少不必要的递归。
```
public List<List<String>> solveNQueens(int n) {
// 下标代表行值代表列。如result[0] = 3 表示第1行的Q在第3列
int[] result = new int[n];
List<List<String>> resultList = new LinkedList<>();
dfs(resultList, result, 0, n);
return resultList;
}
void dfs(List<List<String>> resultList, int[] result, int row, int n) {
// 递归终止条件
if (row == n) {
List<String> list = new LinkedList<>();
for (int x = 0; x < n; ++x) {
StringBuilder sb = new StringBuilder();
for (int y = 0; y < n; ++y)
sb.append(result[x] == y ? "Q" : ".");
list.add(sb.toString());
}
resultList.add(list);
return;
}
for (int column = 0; column < n; ++column) {
boolean isValid = true;
result[row] = column;
/*
* 逐行往下考察每一行。同列result[i] == column
* 同对角线row - i == Math.abs(result[i] - column)
*/
for (int i = row - 1; i >= 0; --i) {
if (result[i] == column || row - i == Math.abs(result[i] - column)) {
isValid = false;
break;
}
}
if (isValid) dfs(resultList, result, row + 1, n);
}
}
```
### Solution2
使用LinkedList表示一种解法下标index表示行value表示该行的Q皇后在哪一列。
解法二和解法一的不同在于,相同列以及相同对角线的校验。
将对角线抽象成【一次函数】这个简单的数学模型,根据一次函数的截距是常量这一特性进行校验。
这里,我将右上-左下对角线,简称为“\”对角线;左上-右下对角线简称为“/”对角线。
“/”对角线斜率为1对应方程为y = x + b其中b为截距。
对于线上任意一点均有y - x = b即row - i = b;
定义一个布尔类型数组anti_diag将b作为下标当anti_diag[b] = true时表示相应对角线上已经放置棋子。
但row - i有可能为负数负数不能作为数组下标row - i 的最小值为-n当row = 0i = n时可以加上n作为数组下标即将row -i + n 作为数组下标。
row - i + n 的最大值为 2n当row = ni = 0时故anti_diag的容量设置为 2n 即可。
![ANXG79.png](https://s2.ax1x.com/2019/03/26/ANXG79.png)
“\”对角线斜率为-1对应方程为y = -x + b其中b为截距。
对于线上任意一点均有y + x = b即row + i = b;
同理定义数组main_diag将b作为下标当main_diag[row + i] = true时表示相应对角线上已经放置棋子。
有了两个校验对角线的数组再来定义一个用于校验列的数组cols这个太简单啦不解释。
**解法二时间复杂度为O(n!),在校验相同列和相同对角线时,引入三个布尔类型数组进行判断。相比解法一,少了一层循环,用空间换时间。**
```
List<List<String>> resultList = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
boolean[] cols = new boolean[n];
boolean[] main_diag = new boolean[2 * n];
boolean[] anti_diag = new boolean[2 * n];
LinkedList<Integer> result = new LinkedList<>();
dfs(result, 0, cols, main_diag, anti_diag, n);
return resultList;
}
void dfs(LinkedList<Integer> result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) {
if (row == n) {
List<String> list = new LinkedList<>();
for (int x = 0; x < n; ++x) {
StringBuilder sb = new StringBuilder();
for (int y = 0; y < n; ++y)
sb.append(result.get(x) == y ? "Q" : ".");
list.add(sb.toString());
}
resultList.add(list);
return;
}
for (int i = 0; i < n; ++i) {
if (cols[i] || main_diag[row + i] || anti_diag[row - i + n])
continue;
result.add(i);
cols[i] = true;
main_diag[row + i] = true;
anti_diag[row - i + n] = true;
dfs(result, row + 1, cols, main_diag, anti_diag, n);
result.removeLast();
cols[i] = false;
main_diag[row + i] = false;
anti_diag[row - i + n] = false;
}
}
```

View File

@ -1,9 +1,10 @@
# 网易 2018
下面三道编程题来自网易2018校招编程题这三道应该来说是非常简单的编程题了这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做然后再对照这我的答案看看和你自己想的有什么区别那一种方法更好 下面三道编程题来自网易2018校招编程题这三道应该来说是非常简单的编程题了这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做然后再对照这我的答案看看和你自己想的有什么区别那一种方法更好
![问题](https://user-gold-cdn.xitu.io/2018/7/7/1647557d5a1474d7?w=1024&h=1024&f=jpeg&s=638271) ## 问题
> # 问题
## 一 获得特定数量硬币问题 ### 一 获得特定数量硬币问题
小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。
@ -15,33 +16,30 @@
**输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。 **输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。
**输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。 **输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。
**输入例子1:** 10 **输入例子1:** 10
**输出例子1:** 122 **输出例子1:** 122
## 二 求“相反数”问题 ### 二 求“相反数”问题
为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1. 为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1.
**输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5) **输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5)
**输出描述:** 输出一个整数,表示n的相反数 **输出描述:** 输出一个整数,表示n的相反数
**输入例子1:** 1325 **输入例子1:** 1325
**输出例子1:** 6556 **输出例子1:** 6556
## 三 字符串碎片的平均长度 ### 三 字符串碎片的平均长度
一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。 一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。
**输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z') **输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z')
**输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。 **输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。
**如样例所示:** s = "aaabbaaac" **如样例所示:** s = "aaabbaaac"
@ -51,19 +49,18 @@
**输出例子1:** 2.25 **输出例子1:** 2.25
![答案](https://user-gold-cdn.xitu.io/2018/7/7/16475582faddc9b2?w=1024&h=1024&f=jpeg&s=531663) ## 答案
> # 答案 ### 一 获得特定数量硬币问题
## 一 获得特定数量硬币问题 #### 分析:
### 分析:
作为该试卷的第一题,这道题应该只要思路正确就很简单了。 作为该试卷的第一题,这道题应该只要思路正确就很简单了。
解题关键明确魔法机器1只能产生奇数魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。 解题关键明确魔法机器1只能产生奇数魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
### 示例代码 #### 示例代码
注意由于用户的输入不确定性一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常后面的题目中我给的代码没有异常处理的部分参照下面两个示例代码应该很容易添加。PS企业面试中没有明确就不用添加异常处理当然你有的话也更好 注意由于用户的输入不确定性一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常后面的题目中我给的代码没有异常处理的部分参照下面两个示例代码应该很容易添加。PS企业面试中没有明确就不用添加异常处理当然你有的话也更好
**不带输入异常处理判断的版本:** **不带输入异常处理判断的版本:**
@ -141,32 +138,33 @@ public class Main {
``` ```
### 二 求“相反数”问题
## 二 求“相反数”问题 #### 分析:
### 分析:
解决本道题有几种不同的方法但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点 解决本道题有几种不同的方法但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点
**1)String转int** **1)String转int**
在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换. 在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换.
```java ```java
String str = "123"; String str = "123";
int a = Integer.parseInt(str); int a = Integer.parseInt(str);
``` ```
```java ```java
String str = "123"; String str = "123";
int a = Integer.valueOf(str).intValue() int a = Integer.valueOf(str).intValue()
``` ```
**2)next()和nextLine()的区别** **2)next()和nextLine()的区别**
在Java中输入字符串有两种方法就是next()和nextLine().两者的区别就是nextLine()的输入是碰到回车就终止输入而next()方法是碰到空格回车Tab键都会被视为终止符。所以next()不会得到带空格的字符串而nextLine()可以得到带空格的字符串。 在Java中输入字符串有两种方法就是next()和nextLine().两者的区别就是nextLine()的输入是碰到回车就终止输入而next()方法是碰到空格回车Tab键都会被视为终止符。所以next()不会得到带空格的字符串而nextLine()可以得到带空格的字符串。
### 示例代码: #### 示例代码:
```java ```java
import java.util.Scanner; import java.util.Scanner;
@ -192,15 +190,15 @@ public class Main {
} }
``` ```
## 三 字符串碎片的平均长度 ### 三 字符串碎片的平均长度
### 分析: #### 分析:
这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。 这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。
这样就很简单了就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话我们可以利用charAt(i)方法取出特定位置的字符与后一个字符比较或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。 这样就很简单了就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话我们可以利用charAt(i)方法取出特定位置的字符与后一个字符比较或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。
### 示例代码 #### 示例代码
**利用charAt(i)方法:** **利用charAt(i)方法:**

View File

@ -112,7 +112,7 @@ public class Main {
public static String replaceSpace(String[] strs) { public static String replaceSpace(String[] strs) {
// 如果检查值不合法及就返回空串 // 如果检查值不合法及就返回空串
if (!chechStrs(strs)) { if (!checkStrs(strs)) {
return ""; return "";
} }
// 数组长度 // 数组长度
@ -135,21 +135,17 @@ public class Main {
} }
private static boolean chechStrs(String[] strs) { private static boolean checkStrs(String[] strs) {
boolean flag = false; if (strs != null) {
// 注意:=是赋值,==是判断 // 遍历strs检查元素值
if (strs != null) { for (int i = 0; i < strs.length; i++) {
// 遍历strs检查元素值 if (strs[i] == null || strs[i].length() == 0) {
for (int i = 0; i < strs.length; i++) { return false;
if (strs[i] != null && strs[i].length() != 0) { }
flag = true; }
} else { }
flag = false; return true;
} }
}
}
return flag;
}
// 测试 // 测试
public static void main(String[] args) { public static void main(String[] args) {
@ -463,7 +459,7 @@ public class Main {
return 0; return 0;
} }
} }
return flag == 1 ? res : -res; return flag != 2 ? res : -res;
} }

View File

@ -225,7 +225,7 @@ public class Solution {
while (node1 != null) { while (node1 != null) {
node1 = node1.next; node1 = node1.next;
count++; count++;
if (k < 1 && node1 != null) { if (k < 1) {
node2 = node2.next; node2 = node2.next;
} }
k--; k--;

View File

@ -0,0 +1,686 @@
### 一 斐波那契数列
#### **题目描述:**
大家都知道斐波那契数列现在要求输入一个整数n请你输出斐波那契数列的第n项。
n<=39
#### **问题分析:**
可以肯定的是这一题通过递归的方式是肯定能做出来但是这样会有一个很大的问题那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法用fn1和fn2保存计算过程中的结果并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。
#### **示例代码:**
**采用迭代法:**
```java
int Fibonacci(int number) {
if (number <= 0) {
return 0;
}
if (number == 1 || number == 2) {
return 1;
}
int first = 1, second = 1, third = 0;
for (int i = 3; i <= number; i++) {
third = first + second;
first = second;
second = third;
}
return third;
}
```
**采用递归:**
```java
public int Fibonacci(int n) {
if (n <= 0) {
return 0;
}
if (n == 1||n==2) {
return 1;
}
return Fibonacci(n - 2) + Fibonacci(n - 1);
}
```
#### **运行时间对比:**
假设n为40我们分别使用迭代法和递归法计算计算结果如下
1. 迭代法
![迭代法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt5as85j308a025dfl.jpg)
2. 递归法
![递归法](https://ws1.sinaimg.cn/large/006rNwoDgy1fpydt2d1k3j30ed02kt8i.jpg)
### 二 跳台阶问题
#### **题目描述:**
一只青蛙一次可以跳上1级台阶也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
#### **问题分析:**
**正常分析法:**
a.如果两种跳法1阶或者2阶那么假定第一次跳的是一阶那么剩下的是n-1个台阶跳法是f(n-1);
b.假定第一次跳的是2阶那么剩下的是n-2个台阶跳法是f(n-2)
c.由ab假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
**找规律分析法:**
f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5 可以总结出f(n) = f(n-1) + f(n-2)的规律。
但是为什么会出现这样的规律呢假设现在6个台阶我们可以从第5跳一步到6这样的话有多少种方案跳到5就有多少种方案跳到6另外我们也可以从4跳两步跳到6跳到4有多少种方案的话就有多少种方案跳到6其他的不能从3跳到6什么的啦所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。
**所以这道题其实就是斐波那契数列的问题。**
代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。
#### **示例代码:**
```java
int jumpFloor(int number) {
if (number <= 0) {
return 0;
}
if (number == 1) {
return 1;
}
if (number == 2) {
return 2;
}
int first = 1, second = 2, third = 0;
for (int i = 3; i <= number; i++) {
third = first + second;
first = second;
second = third;
}
return third;
}
```
### 三 变态跳台阶问题
#### **题目描述:**
一只青蛙一次可以跳上1级台阶也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
#### **问题分析:**
假设n>=2第一步有n种跳法跳1级、跳2级、到跳n级
跳1级剩下n-1级则剩下跳法是f(n-1)
跳2级剩下n-2级则剩下跳法是f(n-2)
......
跳n-1级剩下1级则剩下跳法是f(1)
跳n级剩下0级则剩下跳法是f(0)
所以在n>=2的情况下
f(n)=f(n-1)+f(n-2)+...+f(1)
因为f(n-1)=f(n-2)+f(n-3)+...+f(1)
所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)**
#### **示例代码:**
```java
int JumpFloorII(int number) {
return 1 << --number;//2^(number-1)用位移操作进行更快
}
```
#### **补充:**
**java中有三种移位运算符**
1. “<<” : **左移运算符**等同于乘2的n次方
2. “>>”: **右移运算符**等同于除2的n次方
3. “>>>” **无符号右移运算符**不管移动前最高位是0还是1右移后左侧产生的空位部分都以0来填充。与>>类似。
例:
int a = 16;
int b = a << 2;//左移2等同于16 * 2的2次方也就是16 * 4
int c = a >> 2;//右移2等同于16 / 2的2次方也就是16 / 4
### 四 二维数组查找
#### **题目描述:**
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
#### **问题解析:**
这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路:
> 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增,
> 因此从左下角开始查找,当要查找数字比左下角数字大时。右移
> 要查找数字比左下角数字小时,上移。这样找的速度最快。
#### **示例代码:**
```java
public boolean Find(int target, int [][] array) {
//基本思路从左下角开始找,这样速度最快
int row = array.length-1;//行
int column = 0;//列
//当行数大于0当前列数小于总列数时循环条件成立
while((row >= 0)&& (column< array[0].length)){
if(array[row][column] > target){
row--;
}else if(array[row][column] < target){
column++;
}else{
return true;
}
}
return false;
}
```
### 五 替换空格
#### **题目描述:**
请实现一个函数,将一个字符串中的空格替换成“%20”。例如当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
#### **问题分析:**
这道题不难我们可以通过循环判断字符串的字符是否为空格是的话就利用append()方法添加追加“%20”否则还是追加原字符。
或者最简单的方法就是利用: replaceAll(String regex,String replacement)方法了,一行代码就可以解决。
#### **示例代码:**
**常规做法:**
```java
public String replaceSpace(StringBuffer str) {
StringBuffer out=new StringBuffer();
for (int i = 0; i < str.toString().length(); i++) {
char b=str.charAt(i);
if(String.valueOf(b).equals(" ")){
out.append("%20");
}else{
out.append(b);
}
}
return out.toString();
}
```
**一行代码解决:**
```java
public String replaceSpace(StringBuffer str) {
//return str.toString().replaceAll(" ", "%20");
//public String replaceAll(String regex,String replacement)
//用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
//\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示所以我这里猜测"\\s"就是代表空格的意思
return str.toString().replaceAll("\\s", "%20");
}
```
### 六 数值的整数次方
#### **题目描述:**
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
#### **问题解析:**
这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想,当然也可以采用**快速幂**。
更具剑指offer书中细节该题的解题思路如下
1.当底数为0且指数<0时会出现对0求倒数的情况需进行错误处理设置一个全局变量
2.判断底数是否等于0由于base为double型所以不能直接用==判断
3.优化求幂函数(二分幂)。
当n为偶数a^n =a^n/2*a^n/2
当n为奇数a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn)
**时间复杂度**O(logn)
#### **示例代码:**
```java
public class Solution {
boolean invalidInput=false;
public double Power(double base, int exponent) {
//如果底数等于0并且指数小于0
//由于base为double型不能直接用==判断
if(equal(base,0.0)&&exponent<0){
invalidInput=true;
return 0.0;
}
int absexponent=exponent;
//如果指数小于0将指数转正
if(exponent<0)
absexponent=-exponent;
//getPower方法求出base的exponent次方。
double res=getPower(base,absexponent);
//如果指数小于0所得结果为上面求的结果的倒数
if(exponent<0)
res=1.0/res;
return res;
}
//比较两个double型变量是否相等的方法
boolean equal(double num1,double num2){
if(num1-num2>-0.000001&&num1-num2<0.000001)
return true;
else
return false;
}
//求出b的e次方的方法
double getPower(double b,int e){
//如果指数为0返回1
if(e==0)
return 1.0;
//如果指数为1返回b
if(e==1)
return b;
//e>>1相等于e/2这里就是求a^n =a^n/2*a^n/2
double result=getPower(b,e>>1);
result*=result;
//如果指数n为奇数则要再乘一次底数base
if((e&1)==1)
result*=b;
return result;
}
}
```
当然这一题也可以采用笨方法累乘。不过这种方法的时间复杂度为On这样没有前一种方法效率高。
```java
// 使用累乘
public double powerAnother(double base, int exponent) {
double result = 1.0;
for (int i = 0; i < Math.abs(exponent); i++) {
result *= base;
}
if (exponent >= 0)
return result;
else
return 1 / result;
}
```
### 七 调整数组顺序使奇数位于偶数前面
#### **题目描述:**
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
#### **问题解析:**
这道题有挺多种解法的,给大家介绍一种我觉得挺好理解的方法:
我们首先统计奇数的个数假设为n,然后新建一个等长数组然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始把该奇数添加到新数组如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。
#### **示例代码:**
时间复杂度为On空间复杂度为On的算法
```java
public class Solution {
public void reOrderArray(int [] array) {
//如果数组长度等于0或者等于1什么都不做直接返回
if(array.length==0||array.length==1)
return;
//oddCount保存奇数个数
//oddBegin奇数从数组头部开始添加
int oddCount=0,oddBegin=0;
//新建一个数组
int[] newArray=new int[array.length];
//计算出(数组中的奇数个数)开始添加元素
for(int i=0;i<array.length;i++){
if((array[i]&1)==1) oddCount++;
}
for(int i=0;i<array.length;i++){
//如果数为基数新数组从头开始添加元素
//如果为偶数就从oddCount数组中的奇数个数开始添加元素
if((array[i]&1)==1)
newArray[oddBegin++]=array[i];
else newArray[oddCount++]=array[i];
}
for(int i=0;i<array.length;i++){
array[i]=newArray[i];
}
}
}
```
### 八 链表中倒数第k个节点
#### **题目描述:**
输入一个链表输出该链表中倒数第k个结点
#### **问题分析:**
**一句话概括:**
两个指针一个指针p1先开始跑指针p1跑到k-1个节点后另一个节点p2开始跑当p1跑到最后时p2所指的指针就是倒数第k个节点。
**思想的简单理解:**
前提假设:链表的结点个数(长度)为n。
规律一要找到倒数第k个结点需要向前走多少步呢比如倒数第一个结点需要走n步那倒数第二个结点呢很明显是向前走了n-1步所以可以找到规律是找到倒数第k个结点需要向前走n-k+1步。
**算法开始:**
1. 设两个都指向head的指针p1和p2当p1走了k-1步的时候停下来。p2之前一直不动。
2. p1的下一步是走第k步这个时候p2开始一起动了。至于为什么p2这个时候动呢看下面的分析。
3. 当p1走到链表的尾部时即p1走了n步。由于我们知道p2是在p1走了k-1步才开始动的也就是说p1和p2永远差k-1步。所以当p1走了n步时p2走的应该是在n-(k-1)步。即p2走了n-k+1步此时巧妙的是p2正好指向的是规律一的倒数第k个结点处。
这样是不是很好理解了呢?
#### **考察内容:**
链表+代码的鲁棒性
#### **示例代码:**
```java
/*
//链表类
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
//时间复杂度O(n),一次遍历即可
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode pre=null,p=null;
//两个指针都指向头结点
p=head;
pre=head;
//记录k值
int a=k;
//记录节点的个数
int count=0;
//p指针先跑并且记录节点数当p指针跑了k-1个节点后pre指针开始跑
//当p指针跑到最后时pre所指指针就是倒数第k个节点
while(p!=null){
p=p.next;
count++;
if(k<1){
pre=pre.next;
}
k--;
}
//如果节点个数小于所求的倒数第k个节点则返回空
if(count<a) return null;
return pre;
}
}
```
### 九 反转链表
#### **题目描述:**
输入一个链表,反转链表后,输出链表的所有元素。
#### **问题分析:**
链表的很常规的一道题,这一道题思路不算难,但自己实现起来真的可能会感觉无从下手,我是参考了别人的代码。
思路就是我们根据链表的特点,前一个节点指向下一个节点的特点,把后面的节点移到前面来。
就比如下图我们把1节点和2节点互换位置然后再将3节点指向2节点4节点指向3节点这样以来下面的链表就被反转了。
![链表](https://img-blog.csdn.net/20160420134000174)
#### **考察内容:**
链表+代码的鲁棒性
#### **示例代码:**
```java
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode next = null;
ListNode pre = null;
while (head != null) {
//保存要反转到头来的那个节点
next = head.next;
//要反转的那个节点指向已经反转的上一个节点
head.next = pre;
//上一个已经反转到头部的节点
pre = head;
//一直向链表尾走
head = next;
}
return pre;
}
}
```
### 十 合并两个排序的链表
#### **题目描述:**
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
#### **问题分析:**
我们可以这样分析:
1. 假设我们有两个链表 A,B
2. A的头节点A1的值与B的头结点B1的值比较假设A1小则A1为头节点
3. A2再和B1比较假设B1小,则A1指向B1
4. A2再和B2比较。。。。。。。
就这样循环往复就行了,应该还算好理解。
#### **考察内容:**
链表+代码的鲁棒性
#### **示例代码:**
**非递归版本:**
```java
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
//list1为空直接返回list2
if(list1 == null){
return list2;
}
//list2为空直接返回list1
if(list2 == null){
return list1;
}
ListNode mergeHead = null;
ListNode current = null;
//当list1和list2不为空时
while(list1!=null && list2!=null){
//取较小值作头结点
if(list1.val <= list2.val){
if(mergeHead == null){
mergeHead = current = list1;
}else{
current.next = list1;
//current节点保存list1节点的值因为下一次还要用
current = list1;
}
//list1指向下一个节点
list1 = list1.next;
}else{
if(mergeHead == null){
mergeHead = current = list2;
}else{
current.next = list2;
//current节点保存list2节点的值因为下一次还要用
current = list2;
}
//list2指向下一个节点
list2 = list2.next;
}
}
if(list1 == null){
current.next = list2;
}else{
current.next = list1;
}
return mergeHead;
}
}
```
**递归版本:**
```java
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
```
### 十一 用两个栈实现队列
#### **题目描述:**
用两个栈来实现一个队列完成队列的Push和Pop操作。 队列中的元素为int类型。
#### 问题分析:
先来回顾一下栈和队列的基本特点:
**栈:**后进先出LIFO
**队列:** 先进先出
很明显我们需要根据JDK给我们提供的栈的一些基本方法来实现。先来看一下Stack类的一些基本方法
![Stack类的一些常见方法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-4-4/5985000.jpg)
既然题目给了我们两个栈我们可以这样考虑当push的时候将元素push进stack1pop的时候我们先把stack1的元素pop到stack2然后再对stack2执行pop操作这样就可以保证是先进先出的。负[pop]负[pop]得正[先进先出]
#### 考察内容:
队列+栈
#### 示例代码:
```java
//左程云的《程序员代码面试指南》的答案
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
//当执行push操作时将元素添加到stack1
public void push(int node) {
stack1.push(node);
}
public int pop() {
//如果两个队列都为空则抛出异常,说明用户没有push进任何元素
if(stack1.empty()&&stack2.empty()){
throw new RuntimeException("Queue is empty!");
}
//如果stack2不为空直接对stack2执行pop操作
if(stack2.empty()){
while(!stack1.empty()){
//将stack1的元素按后进先出push进stack2里面
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
```
### 十二 栈的压入,弹出序列
#### **题目描述:**
输入两个整数序列第一个序列表示栈的压入顺序请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序序列45,3,2,1是该压栈序列对应的一个弹出序列但4,3,5,1,2就不可能是该压栈序列的弹出序列。注意这两个序列的长度是相等的
#### **题目分析:**
这道题想了半天没有思路参考了Alias的答案他的思路写的也很详细应该很容易看懂。
作者Alias
https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
来源:牛客网
【思路】借用一个辅助的栈遍历压栈顺序先讲第一个放入栈中这里是1然后判断栈顶元素是不是出栈顺序的第一个元素这里是4很显然1≠4所以我们继续压栈直到相等以后开始出栈出栈一个元素则将出栈顺序向后移动一位直到不相等这样循环等压栈顺序遍历完成如果辅助栈还不为空说明弹出序列不是该栈的弹出顺序。
举例:
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈此时栈顶1≠4继续入栈2
此时栈顶2≠4继续入栈3
此时栈顶3≠4继续入栈4
此时栈顶44出栈4弹出序列向后一位此时为5,辅助栈里面是1,2,3
此时栈顶3≠5继续入栈5
此时栈顶5=5出栈5,弹出序列向后一位此时为3,辅助栈里面是1,2,3
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
#### **考察内容:**
#### **示例代码:**
```java
import java.util.ArrayList;
import java.util.Stack;
//这道题没想出来参考了Alias同学的答案https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length == 0 || popA.length == 0)
return false;
Stack<Integer> s = new Stack<Integer>();
//用于标识弹出序列的位置
int popIndex = 0;
for(int i = 0; i< pushA.length;i++){
s.push(pushA[i]);
//如果栈不为空,且栈顶元素等于弹出序列
while(!s.empty() &&s.peek() == popA[popIndex]){
//出栈
s.pop();
//弹出序列向后一位
popIndex++;
}
}
return s.empty();
}
}
```

View File

@ -1,3 +1,4 @@
下面只是简单地总结,给了一些参考文章,后面会对这部分内容进行重构。
<!-- MarkdownTOC --> <!-- MarkdownTOC -->
- [Queue](#queue) - [Queue](#queue)
@ -81,8 +82,9 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
**Stack** 是栈它继承于Vector。它的特性是先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈Stack设计与实现](https://blog.csdn.net/javazejian/article/details/53362993) **Stack** 是栈它继承于Vector。它的特性是先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈Stack设计与实现](https://blog.csdn.net/javazejian/article/details/53362993)
### ArrayList 和 LinkedList 源码学习 ### ArrayList 和 LinkedList 源码学习
- [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md)
- [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) - [ArrayList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/ArrayList.md)
- [LinkedList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/LinkedList.md)
### 推荐阅读 ### 推荐阅读
@ -110,7 +112,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树
* ### 3 满二叉树 * ### 3 满二叉树
[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同)
@ -120,7 +122,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
[数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191) [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191)
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
* ### 4 二叉查找树BST * ### 4 二叉查找树BST
[浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html) [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html)
@ -129,7 +131,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值; 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值;
2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 任意节点的左、右子树也分别为二叉查找树 3. 任意节点的左、右子树也分别为二叉查找树
4. 没有键值相等的节点no duplicate nodes 4. 没有键值相等的节点no duplicate nodes
* ### 5 平衡二叉树Self-balancing binary search tree * ### 5 平衡二叉树Self-balancing binary search tree
@ -142,7 +144,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
2. 根节点总是黑色的; 2. 根节点总是黑色的;
3. 每个叶子节点都是黑色的空节点NIL节点 3. 每个叶子节点都是黑色的空节点NIL节点
4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
- 红黑树的应用: - 红黑树的应用:
@ -164,9 +166,9 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且
[《B-树B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) [《B-树B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405)
B-树或B树是一种平衡的多路查找(又称排序)在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) B-树或B树是一种平衡的多路查找(又称排序)在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance)
1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。
2. B+树支持range-query(区间查询)非常方便而B树不支持。这是数据库选用B+树的最主要原因。 2. B+树支持range-query(区间查询)非常方便而B树不支持。这是数据库选用B+树的最主要原因。
3. B\*树 是B+树的变体B\*树分配新结点的概率比B+树要低,空间使用率更高; 3. B\*树 是B+树的变体B\*树分配新结点的概率比B+树要低,空间使用率更高;
* ### 8 LSM 树 * ### 8 LSM 树

View File

@ -0,0 +1,52 @@
我比较推荐大家可以刷一下 Leetcode ,我自己平时没事也会刷一下,我觉得刷 Leetcode 不仅是为了能让你更从容地面对面试中的手撕算法问题,更可以提高你的编程思维能力、解决问题的能力以及你对某门编程语言 API 的熟练度。当然牛客网也有一些算法题,我下面也整理了一些。
## LeetCode
- [LeetCode中国官网](https://leetcode-cn.com/)
- [如何高效地使用 LeetCode](https://leetcode-cn.com/articles/%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8-leetcode/)
## 牛客网
- [牛客网官网](https://www.nowcoder.com)
- [剑指offer编程题](https://www.nowcoder.com/ta/coding-interviews)
- [2017校招真题](https://www.nowcoder.com/ta/2017test)
- [华为机试题](https://www.nowcoder.com/ta/huawei)
## 公司真题
- [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary)
- [ 网易2018校招内推编程题集合](https://www.nowcoder.com/test/6291726/summary)
- [2017年校招全国统一模拟笔试(第五场)编程题集合](https://www.nowcoder.com/test/5986669/summary)
- [2017年校招全国统一模拟笔试(第四场)编程题集合](https://www.nowcoder.com/test/5507925/summary)
- [2017年校招全国统一模拟笔试(第三场)编程题集合](https://www.nowcoder.com/test/5217106/summary)
- [2017年校招全国统一模拟笔试(第二场)编程题集合](https://www.nowcoder.com/test/4546329/summary)
- [ 2017年校招全国统一模拟笔试(第一场)编程题集合](https://www.nowcoder.com/test/4236887/summary)
- [百度2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4998655/summary)
- [网易2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4575457/summary)
- [网易2017秋招编程题集合](https://www.nowcoder.com/test/2811407/summary)
- [网易有道2017内推编程题](https://www.nowcoder.com/test/2385858/summary)
- [ 滴滴出行2017秋招笔试真题-编程题汇总](https://www.nowcoder.com/test/3701760/summary)
- [腾讯2017暑期实习生编程题](https://www.nowcoder.com/test/1725829/summary)
- [今日头条2017客户端工程师实习生笔试题](https://www.nowcoder.com/test/1649301/summary)
- [今日头条2017后端工程师实习生笔试题](https://www.nowcoder.com/test/1649268/summary)

View File

@ -28,8 +28,8 @@ MySQL的基本存储结构是页(记录都存在页里边)
所以说如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句默认会这样做 所以说如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句默认会这样做
1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** 1. **定位到记录所在的页需要遍历双向链表,找到所在的页**
2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** 2. **从所在的页内中查找相应的记录由于不是根据主键查询,只能遍历所在页的单链表了**
很明显在数据量很大的情况下这样查找会很慢这样的时间复杂度为On 很明显在数据量很大的情况下这样查找会很慢这样的时间复杂度为On
@ -55,22 +55,22 @@ MySQL的基本存储结构是页(记录都存在页里边)
### 最左前缀原则 ### 最左前缀原则
MySQL中的索引可以以一定顺序引用多列这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)o而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: MySQL中的索引可以以一定顺序引用多列这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:
``` ```
select * from user where name=xx and city=xx ; //可以命中索引 select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引 select * from user where name=xx ; // 可以命中索引
select * from user where city=xx; // 无法命中索引 select * from user where city=xx ; // 无法命中索引
``` ```
这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. 这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的
由于最左前缀原则在创建联合索引时索引字段的顺序需要考虑字段值去重之后的个数较多的放前面。ORDERBY子句也遵循此规则。 由于最左前缀原则在创建联合索引时索引字段的顺序需要考虑字段值去重之后的个数较多的放前面。ORDER BY子句也遵循此规则。
### 注意避免冗余索引 ### 注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如name,city name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如name,city name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
MySQLS.7 版本后,可以通过查询 sys 库的 `schemal_r dundant_indexes` 表来查看冗余索引 MySQLS.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引
### Mysql如何为表字段添加索引 ### Mysql如何为表字段添加索引

306
docs/database/MySQL.md Normal file
View File

@ -0,0 +1,306 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [书籍推荐](#书籍推荐)
- [文字教程推荐](#文字教程推荐)
- [视频教程推荐](#视频教程推荐)
- [常见问题总结](#常见问题总结)
- [什么是MySQL?](#什么是mysql)
- [存储引擎](#存储引擎)
- [一些常用命令](#一些常用命令)
- [MyISAM和InnoDB区别](#myisam和innodb区别)
- [字符集及校对规则](#字符集及校对规则)
- [索引](#索引)
- [查询缓存的使用](#查询缓存的使用)
- [什么是事务?](#什么是事务)
- [事物的四大特性(ACID)](#事物的四大特性acid)
- [并发事务带来哪些问题?](#并发事务带来哪些问题)
- [事务隔离级别有哪些?MySQL的默认隔离级别是?](#事务隔离级别有哪些mysql的默认隔离级别是)
- [锁机制与InnoDB锁算法](#锁机制与innodb锁算法)
- [大表优化](#大表优化)
- [1. 限定数据的范围](#1-限定数据的范围)
- [2. 读/写分离](#2-读写分离)
- [3. 垂直分区](#3-垂直分区)
- [4. 水平分区](#4-水平分区)
- [一条SQL语句在MySQL中如何执行的](#一条sql语句在mysql中如何执行的)
- [MySQL高性能优化规范建议](#mysql高性能优化规范建议)
- [一条SQL语句执行得很慢的原因有哪些](#一条sql语句执行得很慢的原因有哪些)
<!-- /TOC -->
## 书籍推荐
- 《SQL基础教程第2版》 (入门级)
- 《高性能MySQL : 第3版》 (进阶)
## 文字教程推荐
[MySQL 教程(菜鸟教程)](http://www.runoob.com/MySQL/MySQL-tutorial.html)
[MySQL教程易百教程](https://www.yiibai.com/MySQL/)
## 视频教程推荐
**基础入门:** [与MySQL的零距离接触-慕课网](https://www.imooc.com/learn/122)
**MySQL开发技巧** [MySQL开发技巧](https://www.imooc.com/learn/398)  [MySQL开发技巧](https://www.imooc.com/learn/427)  [MySQL开发技巧](https://www.imooc.com/learn/449)
**MySQL5.7新特性及相关优化技巧:** [MySQL5.7版本新特性](https://www.imooc.com/learn/533)  [性能优化之MySQL优化](https://www.imooc.com/learn/194)
[MySQL集群PXC入门](https://www.imooc.com/learn/993)  [MyCAT入门及应用](https://www.imooc.com/learn/951)
## 常见问题总结
### 什么是MySQL?
MySQL 是一种关系型数据库在Java企业级开发中非常常用因为 MySQL 是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了 MySQL因此它的稳定性是有保障的。MySQL是开放源代码的因此任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL的默认端口号是**3306**。
### 存储引擎
#### 一些常用命令
**查看MySQL提供的所有存储引擎**
```sql
mysql> show engines;
```
![查看MySQL提供的所有存储引擎](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/mysql-engines.png)
从上图我们可以查看出 MySQL 当前默认的存储引擎是InnoDB,并且在5.7版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
**查看MySQL当前默认的存储引擎**
我们也可以通过下面的命令查看默认的存储引擎。
```sql
mysql> show variables like '%storage_engine%';
```
**查看表的存储引擎**
```sql
show table status like "table_name" ;
```
![查看表的存储引擎](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/查看表的存储引擎.png)
#### MyISAM和InnoDB区别
MyISAM是MySQL的默认数据库引擎5.5版之前。虽然性能极佳而且提供了大量的特性包括全文索引、压缩、空间函数等但MyISAM不支持事务和行级锁而且最大的缺陷就是崩溃后无法安全恢复。不过5.5版本之后MySQL引入了InnoDB事务性数据库引擎MySQL 5.5版本后默认的存储引擎为InnoDB。
大多数时候我们使用的都是 InnoDB 存储引擎,但是在某些情况下使用 MyISAM 也是合适的比如读密集的情况下。(如果你不介意 MyISAM 崩溃回复问题的话)。
**两者的对比:**
1. **是否支持行级锁** : MyISAM 只有表级锁(table-level locking)而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
2. **是否支持事务和崩溃后的安全恢复: MyISAM** 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快但是不提供事务支持。但是**InnoDB** 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
3. **是否支持外键:** MyISAM不支持而InnoDB支持。
4. **是否支持MVCC** :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 `READ COMMITTED``REPEATABLE READ` 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。推荐阅读[MySQL-InnoDB-MVCC多版本并发控制](https://segmentfault.com/a/1190000012650596)
5. ......
《MySQL高性能》上面有一句话这样写到:
> 不要轻易相信“MyISAM比InnoDB快”之类的经验之谈这个结论往往不是绝对的。在很多我们已知场景中InnoDB的速度都可以让MyISAM望尘莫及尤其是用到了聚簇索引或者需要访问的数据都可以放入内存的应用。
一般情况下我们选择 InnoDB 都是没有问题的但是某事情况下你并不在乎可扩展能力和并发能力也不需要事务支持也不在乎崩溃后的安全恢复问题的话选择MyISAM也是一个不错的选择。但是一般情况下我们都是需要考虑到这些问题的。
### 字符集及校对规则
字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。MySQL中每一种字符集都会对应一系列的校对规则。
MySQL采用的是类似继承的方式指定字符集的默认值每个数据库以及每张数据表都有自己的默认值他们逐层继承。比如某个库中所有表的默认字符集将是该数据库所指定的字符集这些表在没有指定字符集的情况下才会采用默认字符集 PS整理自《Java工程师修炼之道》
详细内容可以参考: [MySQL字符集及校对规则的理解](https://www.cnblogs.com/geaozhang/p/6724393.html#MySQLyuzifuji)
### 索引
MySQL索引使用的数据结构主要有**BTree索引** 和 **哈希索引** 。对于哈希索引来说底层的数据结构就是哈希表因此在绝大多数需求为单条记录查询的时候可以选择哈希索引查询性能最快其余大部分场景建议选择BTree索引。
MySQL的BTree索引使用的是B树中的B+Tree但对于主要的两种存储引擎的实现方式是不同的。
- **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候首先按照B+Tree搜索算法搜索索引如果指定的Key存在则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
- **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM索引文件和数据文件是分离的其表数据文件本身就是按B+Tree组织的一个索引结构树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引或聚集索引”。而其余的索引都作为辅助索引辅助索引的data域存储相应记录主键的值而不是地址这也是和MyISAM不同的地方。**在根据主索引搜索时直接找到key所在的节点即可取出数据在根据辅助索引查找时则需要先取出主键的值再走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS整理自《Java工程师修炼之道》
**更多关于索引的内容可以查看文档首页MySQL目录下关于索引的详细总结。**
### 查询缓存的使用
> 执行查询语句的时候会先查询缓存。不过MySQL 8.0 版本后移除,因为这个功能不太实用
my.cnf加入以下配置重启MySQL开启查询缓存
```properties
query_cache_type=1
query_cache_size=600000
```
MySQL执行以下命令也可以开启查询缓存
```properties
set global query_cache_type=1;
set global query_cache_size=600000;
```
如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL库中的系统表其查询结果也不会被缓存。
缓存建立之后MySQL的查询缓存系统会跟踪查询中涉及的每张表如果这些表数据或结构发生变化那么和这张表相关的所有缓存数据都将失效。
**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此开启缓存查询要谨慎尤其对于写密集的应用来说更是如此。如果开启要注意合理控制缓存空间大小一般来说其大小设置为几十MB比较合适。此外**还可以通过sql_cache和sql_no_cache来控制某个查询语句是否需要缓存**
```sql
select sql_no_cache count(*) from usr;
```
### 什么是事务?
**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元这个转账会涉及到两个关键操作就是将小明的余额减少1000元将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃导致小明余额减少而小红的余额没有增加这样就不对了。事务就是保证这两个关键操作要么都成功要么都要失败。
### 事物的四大特性(ACID)
![事物的特性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/事务特性.png)
1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **一致性:** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
3. **隔离性:** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
### 并发事务带来哪些问题?
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
- **脏读Dirty read:** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- **丢失修改Lost to modify:** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如事务1读取某表中的数据A=20事务2也读取A=20事务1修改A=A-1事务2也修改A=A-1最终结果A=19事务1的修改被丢失。
- **不可重复读Unrepeatableread:** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- **幻读Phantom read:** 幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。
**不可重复读和幻读区别:**
不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
### 事务隔离级别有哪些?MySQL的默认隔离级别是?
**SQL 标准定义了四个隔离级别:**
- **READ-UNCOMMITTED(读取未提交)** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
- **READ-COMMITTED(读取已提交)** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
- **REPEATABLE-READ(可重复读)** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
- **SERIALIZABLE(可串行化)** 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说**该级别可以防止脏读、不可重复读以及幻读**。
------
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :--------------: | :--: | :--------: | :----: |
| READ-UNCOMMITTED | √ | √ | √ |
| READ-COMMITTED | × | √ | √ |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读**。我们可以通过`SELECT @@tx_isolation;`命令来查看
```sql
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
```
这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ可重读**
事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)
是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读** 已经可以完全保证事务的隔离性要求,即达到了
SQL标准的 **SERIALIZABLE(可串行化)** 隔离级别。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** 但是你要知道的是InnoDB 存储引擎默认使用 **REPEAaTABLE-READ可重读** 并不会有任何性能损失。
InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
### 锁机制与InnoDB锁算法
**MyISAM和InnoDB存储引擎使用的锁**
- MyISAM采用表级锁(table-level locking)。
- InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
**表级锁和行级锁对比:**
- **表级锁:** MySQL中锁定 **粒度最大** 的一种锁对当前操作的整张表加锁实现简单资源消耗也比较少加锁快不会出现死锁。其锁定粒度最大触发锁冲突的概率最高并发度最低MyISAM和 InnoDB引擎都支持表级锁。
- **行级锁:** MySQL中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
详细内容可以参考: MySQL锁机制简单了解一下[https://blog.csdn.net/qq_34337272/article/details/80611486](https://blog.csdn.net/qq_34337272/article/details/80611486)
**InnoDB存储引擎的锁的算法有三种**
- Record lock单个行记录上的锁
- Gap lock间隙锁锁定一个范围不包括记录本身
- Next-key lockrecord+gap 锁定一个范围,包含记录本身
**相关知识点:**
1. innodb对于行的查询使用next-key lock
2. Next-locking keying为了解决Phantom Problem幻读问题
3. 当查询的索引含有唯一属性时将next-key lock降级为record key
4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内而这会导致幻读问题的产生
5. 有两种方式显式关闭gap锁除了外键约束和唯一性检查外其余情况仅使用record lock A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1
### 大表优化
当MySQL单表记录数过大时数据库的CRUD性能会明显下降一些常见的优化措施如下
#### 1. 限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
#### 2. 读/写分离
经典的数据库拆分方案,主库负责写,从库负责读;
#### 3. 垂直分区
**根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
**简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。
![数据库垂直分区](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/数据库垂直分区.png)
- **垂直拆分的优点:** 可以使得列数据变小在查询时减少读取的Block数减少I/O次数。此外垂直分区可以简化表的结构易于维护。
- **垂直拆分的缺点:** 主键会出现冗余需要管理冗余列并会引起Join操作可以通过在应用层进行Join来解决。此外垂直分区会让事务变得更加复杂
#### 4. 水平分区
**保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。**
水平拆分是指数据表行的拆分表的行数超过200万行时就会变慢这时可以把一张的表的数据拆成多张表来存放。举个例子我们可以将用户信息表拆分成多个用户信息表这样就可以避免单一表数据量过大对性能造成影响。
![数据库水平拆分](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/数据库水平拆分.png)
水平拆分可以支持非常大的数据量。需要注意的一点是分表仅仅是解决了单一表数据过大的问题但由于表的数据还是在同一台机器上其实对于提升MySQL并发能力没有什么意义所以 **水平拆分最好分库**
水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** 跨节点Join性能较差逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** 一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片尽量选择客户端分片架构这样可以减少一次和中间件的网络I/O。
**下面补充一下数据库分片的两种常见方案:**
- **客户端代理:** **分片逻辑在应用端封装在jar包中通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。
- **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。
详细内容可以参考: MySQL大表优化方案: [https://segmentfault.com/a/1190000006158186](https://segmentfault.com/a/1190000006158186)
### 一条SQL语句在MySQL中如何执行的
[一条SQL语句在MySQL中如何执行的](<https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485097&idx=1&sn=84c89da477b1338bdf3e9fcd65514ac1&chksm=cea24962f9d5c074d8d3ff1ab04ee8f0d6486e3d015cfd783503685986485c11738ccb542ba7&token=79317275&lang=zh_CN#rd>)
### MySQL高性能优化规范建议
[MySQL高性能优化规范建议](<https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485117&idx=1&sn=92361755b7c3de488b415ec4c5f46d73&chksm=cea24976f9d5c060babe50c3747616cce63df5d50947903a262704988143c2eeb4069ae45420&token=79317275&lang=zh_CN#rd>)
### 一条SQL语句执行得很慢的原因有哪些
[腾讯面试一条SQL语句执行得很慢的原因有哪些---不看后悔系列](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485185&idx=1&sn=66ef08b4ab6af5757792223a83fc0d45&chksm=cea248caf9d5c1dc72ec8a281ec16aa3ec3e8066dbb252e27362438a26c33fbe842b0e0adf47&token=79317275&lang=zh_CN#rd)
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,441 @@
> 作者: 听风,原文地址: <https://www.cnblogs.com/huchong/p/10219318.html>。JavaGuide 已获得作者授权。
<!-- TOC -->
- [数据库命令规范](#数据库命令规范)
- [数据库基本设计规范](#数据库基本设计规范)
- [1. 所有表必须使用 Innodb 存储引擎](#1-所有表必须使用-innodb-存储引擎)
- [2. 数据库和表的字符集统一使用 UTF8](#2-数据库和表的字符集统一使用-utf8)
- [3. 所有表和字段都需要添加注释](#3-所有表和字段都需要添加注释)
- [4. 尽量控制单表数据量的大小,建议控制在 500 万以内。](#4-尽量控制单表数据量的大小建议控制在-500-万以内)
- [5. 谨慎使用 MySQL 分区表](#5-谨慎使用-mysql-分区表)
- [6.尽量做到冷热数据分离,减小表的宽度](#6尽量做到冷热数据分离减小表的宽度)
- [7. 禁止在表中建立预留字段](#7-禁止在表中建立预留字段)
- [8. 禁止在数据库中存储图片,文件等大的二进制数据](#8-禁止在数据库中存储图片文件等大的二进制数据)
- [9. 禁止在线上做数据库压力测试](#9-禁止在线上做数据库压力测试)
- [10. 禁止从开发环境,测试环境直接连接生成环境数据库](#10-禁止从开发环境测试环境直接连接生成环境数据库)
- [数据库字段设计规范](#数据库字段设计规范)
- [1. 优先选择符合存储需要的最小的数据类型](#1-优先选择符合存储需要的最小的数据类型)
- [2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据](#2-避免使用-textblob-数据类型最常见的-text-类型可以存储-64k-的数据)
- [3. 避免使用 ENUM 类型](#3-避免使用-enum-类型)
- [4. 尽可能把所有列定义为 NOT NULL](#4-尽可能把所有列定义为-not-null)
- [5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间](#5-使用-timestamp4-个字节-或-datetime-类型-8-个字节-存储时间)
- [6. 同财务相关的金额类数据必须使用 decimal 类型](#6-同财务相关的金额类数据必须使用-decimal-类型)
- [索引设计规范](#索引设计规范)
- [1. 限制每张表上的索引数量,建议单张表索引不超过 5 个](#1-限制每张表上的索引数量建议单张表索引不超过-5-个)
- [2. 禁止给表中的每一列都建立单独的索引](#2-禁止给表中的每一列都建立单独的索引)
- [3. 每个 Innodb 表必须有个主键](#3-每个-innodb-表必须有个主键)
- [4. 常见索引列建议](#4-常见索引列建议)
- [5.如何选择索引列的顺序](#5如何选择索引列的顺序)
- [6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)](#6-避免建立冗余索引和重复索引增加了查询优化器生成执行计划的时间)
- [7. 对于频繁的查询优先考虑使用覆盖索引](#7-对于频繁的查询优先考虑使用覆盖索引)
- [8.索引 SET 规范](#8索引-set-规范)
- [数据库 SQL 开发规范](#数据库-sql-开发规范)
- [1. 建议使用预编译语句进行数据库操作](#1-建议使用预编译语句进行数据库操作)
- [2. 避免数据类型的隐式转换](#2-避免数据类型的隐式转换)
- [3. 充分利用表上已经存在的索引](#3-充分利用表上已经存在的索引)
- [4. 数据库设计时,应该要对以后扩展进行考虑](#4-数据库设计时应该要对以后扩展进行考虑)
- [5. 程序连接不同的数据库使用不同的账号,进制跨库查询](#5-程序连接不同的数据库使用不同的账号进制跨库查询)
- [6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询](#6-禁止使用-select--必须使用-select-字段列表-查询)
- [7. 禁止使用不含字段列表的 INSERT 语句](#7-禁止使用不含字段列表的-insert-语句)
- [8. 避免使用子查询,可以把子查询优化为 join 操作](#8-避免使用子查询可以把子查询优化为-join-操作)
- [9. 避免使用 JOIN 关联太多的表](#9-避免使用-join-关联太多的表)
- [10. 减少同数据库的交互次数](#10-减少同数据库的交互次数)
- [11. 对应同一列进行 or 判断时,使用 in 代替 or](#11-对应同一列进行-or-判断时使用-in-代替-or)
- [12. 禁止使用 order by rand() 进行随机排序](#12-禁止使用-order-by-rand-进行随机排序)
- [13. WHERE 从句中禁止对列进行函数转换和计算](#13-where-从句中禁止对列进行函数转换和计算)
- [14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION](#14-在明显不会有重复值时使用-union-all-而不是-union)
- [15. 拆分复杂的大 SQL 为多个小 SQL](#15-拆分复杂的大-sql-为多个小-sql)
- [数据库操作行为规范](#数据库操作行为规范)
- [1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作](#1-超-100-万行的批量写-updatedeleteinsert-操作要分批多次进行操作)
- [2. 对于大表使用 pt-online-schema-change 修改表结构](#2-对于大表使用-pt-online-schema-change-修改表结构)
- [3. 禁止为程序使用的账号赋予 super 权限](#3-禁止为程序使用的账号赋予-super-权限)
- [4. 对于程序连接数据库账号,遵循权限最小原则](#4-对于程序连接数据库账号遵循权限最小原则)
<!-- /TOC -->
## 数据库命令规范
- 所有数据库对象名称必须使用小写字母并用下划线分割
- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符
- 临时库表必须以 tmp_为前缀并以日期为后缀备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀
- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
------
## 数据库基本设计规范
### 1. 所有表必须使用 Innodb 存储引擎
没有特殊要求(即 Innodb 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 Innodb 存储引擎MySQL5.5 之前默认使用 Myisam5.6 以后默认的为 Innodb
Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
### 2. 数据库和表的字符集统一使用 UTF8
兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。
### 3. 所有表和字段都需要添加注释
使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护
### 4. 尽量控制单表数据量的大小,建议控制在 500 万以内。
500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。
可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
### 5. 谨慎使用 MySQL 分区表
分区表在物理上表现为多个文件,在逻辑上表现为一个表;
谨慎选择分区键,跨分区查询效率可能更低;
建议采用物理分表的方式管理大数据。
### 6.尽量做到冷热数据分离,减小表的宽度
> MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 65535 字节。
减少磁盘 IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO
更有效的利用缓存,避免读入无用的冷数据;
经常一起使用的列放到一个表中(避免更多的关联操作)。
### 7. 禁止在表中建立预留字段
预留字段的命名很难做到见名识义。
预留字段无法确认存储的数据类型,所以无法选择合适的类型。
对预留字段类型的修改,会对表进行锁定。
### 8. 禁止在数据库中存储图片,文件等大的二进制数据
通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机 IO 操作文件很大时IO 操作很耗时。
通常存储于文件服务器,数据库只存储文件地址信息
### 9. 禁止在线上做数据库压力测试
### 10. 禁止从开发环境,测试环境直接连接生产环境数据库
------
## 数据库字段设计规范
### 1. 优先选择符合存储需要的最小的数据类型
**原因:**
列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的 IO 次数也就越多,索引的性能也就越差。
**方法:**
**a.将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据**
MySQL 提供了两个方法来处理 ip 地址
- inet_aton 把 ip 转为无符号整型 (4-8 位)
- inet_ntoa 把整型的 ip 转为地址
插入数据前,先用 inet_aton 把 ip 地址转为整型,可以节省空间,显示数据时,使用 inet_ntoa 把整型的 ip 地址转为地址显示即可。
**b.对于非负型的数据 (如自增 ID,整型 IP) 来说,要优先使用无符号整型来存储**
**原因:**
无符号相对于有符号可以多出一倍的存储空间
```
SIGNED INT -2147483648~2147483647
UNSIGNED INT 0~4294967295
```
VARCHAR(N) 中的 N 代表的是字符数,而不是字节数,使用 UTF8 存储 255 个汉字 Varchar(255)=765 个字节。**过大的长度会消耗更多的内存。**
### 2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据
**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中**
MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型如果查询中包含这样的数据在排序等操作时就不能使用内存临时表必须使用磁盘临时表进行。而且对于这种数据MySQL 还是要进行二次查询,会使 sql 性能变得很差,但是不是说一定不能使用这样的数据类型。
如果一定要使用,建议把 BLOB 或是 TEXT 列分离到单独的扩展表中,查询时一定不要使用 select * 而只需要取出必要的列,不需要 TEXT 列的数据时不要对该列进行查询。
**2、TEXT 或 BLOB 类型只能使用前缀索引**
因为[MySQL](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
### 3. 避免使用 ENUM 类型
修改 ENUM 值需要使用 ALTER 语句
ENUM 类型的 ORDER BY 操作效率低,需要额外操作
禁止使用数值作为 ENUM 的枚举值
### 4. 尽可能把所有列定义为 NOT NULL
**原因:**
索引 NULL 列需要额外的空间来保存,所以要占用更多的空间
进行比较和计算时要对 NULL 值做特别的处理
### 5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间
TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07
TIMESTAMP 占用 4 字节和 INT 相同,但比 INT 可读性高
超出 TIMESTAMP 取值范围的使用 DATETIME 类型存储
**经常会有人用字符串存储日期型的数据(不正确的做法)**
- 缺点 1无法用日期函数进行计算和比较
- 缺点 2用字符串存储日期要占用更多的空间
### 6. 同财务相关的金额类数据必须使用 decimal 类型
- 非精准浮点float,double
- 精准浮点decimal
Decimal 类型为精准浮点数,在计算时不会丢失精度
占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字,并且小数点要占用一个字节
可用于存储比 bigint 更大的整型数据
------
## 索引设计规范
### 1. 限制每张表上的索引数量,建议单张表索引不超过 5 个
索引并不是越多越好!索引可以提高效率同样可以降低效率。
索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
### 2. 禁止给表中的每一列都建立单独的索引
5.6 版本之前,一个 sql 只能使用到一个表中的一个索引5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好。
### 3. 每个 Innodb 表必须有个主键
Innodb 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。
Innodb 是按照主键索引的顺序来组织表的
- 不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引)
- 不要使用 UUID,MD5,HASH,字符串列作为主键(无法保证数据的顺序增长)
- 主键建议使用自增 ID 值
------
### 4. 常见索引列建议
- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列
- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
- 并不要将符合 1 和 2 中的字段的列都建立一个索引, 通常将 1、2 中的字段建立联合索引效果更好
- 多表 join 的关联列
------
### 5.如何选择索引列的顺序
建立索引的目的是:希望通过索引进行数据查找,减少随机 IO增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。
- 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)
- 尽量把字段长度小的列放在联合索引的最左侧因为字段长度越小一页能存储的数据量越大IO 性能也就越好)
- 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)
------
### 6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)
- 重复索引示例primary key(id)、index(id)、unique index(id)
- 冗余索引示例index(a,b,c)、index(a,b)、index(a)
------
### 7. 对于频繁的查询优先考虑使用覆盖索引
> 覆盖索引:就是包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引
**覆盖索引的好处:**
- **避免 Innodb 表进行索引的二次查询:** Innodb 是以聚集索引的顺序来存储的,对于 Innodb 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了 IO 操作,提升了查询效率。
- **可以把随机 IO 变成顺序 IO 加快查询效率:** 由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 IO 要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。
------
### 8.索引 SET 规范
**尽量避免使用外键约束**
- 不建议使用外键约束foreign key但一定要在表与表之间的关联键上建立索引
- 外键可用于保证数据的参照完整性,但建议在业务端实现
- 外键会影响父表和子表的写操作从而降低性能
------
## 数据库 SQL 开发规范
### 1. 建议使用预编译语句进行数据库操作
预编译语句可以重复使用这些计划,减少 SQL 编译所需要的时间,还可以解决动态 SQL 所带来的 SQL 注入的问题。
只传参数,比传递 SQL 语句更高效。
相同语句可以一次解析,多次使用,提高处理效率。
### 2. 避免数据类型的隐式转换
隐式转换会导致索引失效如:
```
select name,phone from customer where id = '111';
```
### 3. 充分利用表上已经存在的索引
避免使用双%号的查询条件。如:`a like '%123%'`,(如果无前置%,只有后置%,是可以用到列上的索引的)
一个 SQL 只能利用到复合索引中的一列进行范围查询。如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。
在定义联合索引时,如果 a 列要用到范围查找的话,就要把 a 列放到联合索引的右侧,使用 left join 或 not exists 来优化 not in 操作,因为 not in 也通常会使用索引失效。
### 4. 数据库设计时,应该要对以后扩展进行考虑
### 5. 程序连接不同的数据库使用不同的账号,禁止跨库查询
- 为数据库迁移和分库分表留出余地
- 降低业务耦合度
- 避免权限过大而产生的安全风险
### 6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询
**原因:**
- 消耗更多的 CPU 和 IO 以网络带宽资源
- 无法使用覆盖索引
- 可减少表结构变更带来的影响
### 7. 禁止使用不含字段列表的 INSERT 语句
如:
```
insert into values ('a','b','c');
```
应使用:
```
insert into t(c1,c2,c3) values ('a','b','c');
```
### 8. 避免使用子查询,可以把子查询优化为 join 操作
通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。
**子查询性能差的原因:**
子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。
由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
### 9. 避免使用 JOIN 关联太多的表
对于 MySQL 来说,是存在关联缓存的,缓存的大小可以由 join_buffer_size 参数进行设置。
在 MySQL 中,对于同一个 SQL 多关联join一个表就会多分配一个关联缓存如果在一个 SQL 中关联的表越多,所占用的内存也就越大。
如果程序中大量的使用了多表关联的操作,同时 join_buffer_size 设置的也不合理的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。
同时对于关联操作来说会产生临时表操作影响查询效率MySQL 最多允许关联 61 个表,建议不超过 5 个。
### 10. 减少同数据库的交互次数
数据库更适合处理批量操作,合并多个相同的操作到一起,可以提高处理效率。
### 11. 对应同一列进行 or 判断时,使用 in 代替 or
in 的值不要超过 500 个in 操作可以更有效的利用索引or 大多数情况下很少能利用到索引。
### 12. 禁止使用 order by rand() 进行随机排序
order by rand() 会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值,如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。
推荐在程序中获取一个随机值,然后从数据库中获取数据的方式。
### 13. WHERE 从句中禁止对列进行函数转换和计算
对列进行函数转换或计算时会导致无法使用索引
**不推荐:**
```
where date(create_time)='20190101'
```
**推荐:**
```
where create_time >= '20190101' and create_time < '20190102'
```
### 14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION
- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作
- UNION ALL 不会再对结果集进行去重操作
### 15. 拆分复杂的大 SQL 为多个小 SQL
- 大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL
- MySQL 中,一个 SQL 只能使用一个 CPU 进行计算
- SQL 拆分后可以通过并行执行来提高处理效率
------
## 数据库操作行为规范
### 1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作
**大批量操作可能会造成严重的主从延迟**
主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,
而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
**binlog 日志为 row 格式时会产生大量的日志**
大批量写操作会产生大量日志,特别是对于 row 格式二进制数据而言,由于在 row 格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因
**避免产生大事务操作**
大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对 MySQL 的性能产生非常大的影响。
特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批
### 2. 对于大表使用 pt-online-schema-change 修改表结构
- 避免大表修改产生的主从延迟
- 避免在对表字段进行修改时进行锁表
对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。
pt-online-schema-change 它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。把原来一个 DDL 操作,分解成多个小的批次进行。
### 3. 禁止为程序使用的账号赋予 super 权限
- 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接
- super 权限只能留给 DBA 处理问题的账号使用
### 4. 对于程序连接数据库账号,遵循权限最小原则
- 程序使用数据库账号只能在一个 DB 下使用,不准跨库
- 程序使用的账号原则上不准有 drop 权限

View File

@ -1,32 +1,32 @@
<!-- MarkdownTOC --> 点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [redis 简介](#redis-简介) - [redis 简介](#redis-简介)
- [为什么要用 redis /为什么要用缓存](#为什么要用-redis-为什么要用缓存) - [为什么要用 redis/为什么要用缓存](#为什么要用-redis为什么要用缓存)
- [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存) - [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存)
- [redis 和 memcached 的区别](#redis-和-memcached-的区别) - [redis 和 memcached 的区别](#redis-和-memcached-的区别)
- [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析) - [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析)
- [1. String](#1-string) - [1.String](#1string)
- [2.Hash](#2hash) - [2.Hash](#2hash)
- [3.List](#3list) - [3.List](#3list)
- [4.Set](#4set) - [4.Set](#4set)
- [5.Sorted Set](#5sorted-set) - [5.Sorted Set](#5sorted-set)
- [redis 设置过期时间](#redis-设置过期时间) - [redis 设置过期时间](#redis-设置过期时间)
- [redis 内存淘汰机制MySQL里有2000w数据Redis中只存20w的数据如何保证Redis中的数据都是热点数据](#redis-内存淘汰机制mysql里有2000w数据redis中只存20w的数据如何保证redis中的数据都是热点数据) - [redis 内存淘汰机制(MySQL里有2000w数据Redis中只存20w的数据如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制mysql里有2000w数据redis中只存20w的数据如何保证redis中的数据都是热点数据)
- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制(怎么保证-redis-挂掉之后再重启数据可以进行恢复) - [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复)
- [redis 事务](#redis-事务) - [redis 事务](#redis-事务)
- [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案) - [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案)
- [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题) - [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题)
- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性?) - [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性)
- [参考:](#参考:)
<!-- /MarkdownTOC -->
<!-- /TOC -->
### redis 简介 ### redis 简介
简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以写速度非常快,因此 redis 被广泛应用于缓存方向。另外redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以写速度非常快,因此 redis 被广泛应用于缓存方向。另外redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
### 为什么要用 redis /为什么要用缓存 ### 为什么要用 redis/为什么要用缓存
主要从“高性能”和“高并发”这两点来看待这个问题。 主要从“高性能”和“高并发”这两点来看待这个问题。
@ -72,7 +72,7 @@
### redis 常见数据结构以及使用场景分析 ### redis 常见数据结构以及使用场景分析
#### 1. String #### 1.String
> **常用命令:** set,get,decr,incr,mget 等。 > **常用命令:** set,get,decr,incr,mget 等。
@ -84,7 +84,7 @@ String数据结构是简单的key-value类型value其实不仅可以是String
#### 2.Hash #### 2.Hash
> **常用命令:** hget,hset,hgetall 等。 > **常用命令:** hget,hset,hgetall 等。
Hash 是一个 string 类型的 field 和 value 的映射表hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: hash 是一个 string 类型的 field 和 value 的映射表hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息:
``` ```
key=JavaUser293847 key=JavaUser293847
@ -128,7 +128,7 @@ sinterstore key1 key2 key3 将交集存在key1内
和set相比sorted set增加了一个权重参数score使得集合中的元素能够按score进行有序排列。 和set相比sorted set增加了一个权重参数score使得集合中的元素能够按score进行有序排列。
**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 **举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
### redis 设置过期时间 ### redis 设置过期时间
@ -147,11 +147,9 @@ Redis中有个设置时间过期的功能即对存储在 redis 数据库中
- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key靠定期删除没有被删除掉还停留在内存里除非你的系统去查一下那个 key才会被redis给删除掉。这就是所谓的惰性删除也是够懒的哈 - **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key靠定期删除没有被删除掉还停留在内存里除非你的系统去查一下那个 key才会被redis给删除掉。这就是所谓的惰性删除也是够懒的哈
但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key然后你也没及时去查也就没走惰性删除此时会怎么样如果大量过期key堆积在内存里导致redis内存块耗尽了。怎么解决这个问题呢 但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key然后你也没及时去查也就没走惰性删除此时会怎么样如果大量过期key堆积在内存里导致redis内存块耗尽了。怎么解决这个问题呢 **redis 内存淘汰机制。**
**redis 内存淘汰机制。** ### redis 内存淘汰机制(MySQL里有2000w数据Redis中只存20w的数据如何保证Redis中的数据都是热点数据?)
### redis 内存淘汰机制MySQL里有2000w数据Redis中只存20w的数据如何保证Redis中的数据都是热点数据
redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf) redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf)
@ -160,19 +158,23 @@ redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大
1. **volatile-lru**从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰 1. **volatile-lru**从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰
2. **volatile-ttl**从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰 2. **volatile-ttl**从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰
3. **volatile-random**从已设置过期时间的数据集server.db[i].expires中任意选择数据淘汰 3. **volatile-random**从已设置过期时间的数据集server.db[i].expires中任意选择数据淘汰
4. **allkeys-lru**当内存不足以容纳新写入数据时在键空间中移除最近最少使用的key这个是最常用的. 4. **allkeys-lru**当内存不足以容纳新写入数据时在键空间中移除最近最少使用的key这个是最常用的
5. **allkeys-random**从数据集server.db[i].dict中任意选择数据淘汰 5. **allkeys-random**从数据集server.db[i].dict中任意选择数据淘汰
6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! 6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0版本后增加以下两种:
7. **volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
8. **allkeys-lfu**当内存不足以容纳新写入数据时在键空间中移除最不经常使用的key
**备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** **备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!**
### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复) ### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis不同于Memcached的很重一点就是Redis支持持久化而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照snapshottingRDB,另一种方式是只追加文件append-only file,AOF**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 Redis不同于Memcached的很重一点就是Redis支持持久化而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照snapshottingRDB另一种方式是只追加文件append-only file,AOF**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
**快照snapshotting持久化RDB** **快照snapshotting持久化RDB**
@ -182,14 +184,13 @@ Redis可以通过创建快照来获得存储在内存里面的数据在某个时
```conf ```conf
save 900 1 #在900秒(15分钟)之后如果至少有1个key发生变化Redis就会自动触发BGSAVE命令创建快照。 save 900 1 #在900秒(15分钟)之后如果至少有1个key发生变化Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后如果至少有10个key发生变化Redis就会自动触发BGSAVE命令创建快照。 save 300 10 #在300秒(5分钟)之后如果至少有10个key发生变化Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后如果至少有10000个key发生变化Redis就会自动触发BGSAVE命令创建快照。 save 60 10000 #在60秒(1分钟)之后如果至少有10000个key发生变化Redis就会自动触发BGSAVE命令创建快照。
``` ```
**AOFappend-only file持久化** **AOFappend-only file持久化**
与快照持久化相比AOF持久化 的实时性更好因此已成为主流的持久化方案。默认情况下Redis没有开启AOFappend only file方式的持久化可以通过appendonly参数开启 与快照持久化相比AOF持久化 的实时性更好因此已成为主流的持久化方案。默认情况下Redis没有开启AOFappend only file方式的持久化可以通过appendonly参数开启
@ -203,41 +204,37 @@ appendonly yes
在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: 在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
```conf ```conf
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步 appendfsync no #让操作系统决定何时进行同步
``` ```
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 让Redis每秒同步一次AOF文件Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 让Redis每秒同步一次AOF文件Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
**Redis 4.0 对于持久化机制的优化** **Redis 4.0 对于持久化机制的优化**
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
如果把混合持久化打开AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 如果把混合持久化打开AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
**补充内容AOF 重写** **补充内容AOF 重写**
AOF重写可以产生一个新的AOF文件这个新的AOF文件和原有的AOF文件所保存的数据库状态一样但体积更小。 AOF重写可以产生一个新的AOF文件这个新的AOF文件和原有的AOF文件所保存的数据库状态一样但体积更小。
AOF重写是一个有歧义的名字该功能是通过读取数据库中的键值对来实现的程序无须对现有AOF文件进行任读入、分析或者写入操作。 AOF重写是一个有歧义的名字该功能是通过读取数据库中的键值对来实现的程序无须对现有AOF文件进行任读入、分析或者写入操作。
在执行 BGREWRITEAOF 命令时Redis 服务器会维护一个 AOF 重写缓冲区该缓冲区会在子进程创建新AOF文件期间记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾使得新旧两个AOF文件所保存的数据库状态一致。最后服务器用新的AOF文件替换旧的AOF文件以此来完成AOF文件重写操作 在执行 BGREWRITEAOF 命令时Redis 服务器会维护一个 AOF 重写缓冲区该缓冲区会在子进程创建新AOF文件期间记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾使得新旧两个AOF文件所保存的数据库状态一致。最后服务器用新的AOF文件替换旧的AOF文件以此来完成AOF文件重写操作
**更多内容可以查看我的这篇文章:** **更多内容可以查看我的这篇文章:**
- [https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md](https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md) - [Redis持久化](Redis持久化.md)
### redis 事务 ### redis 事务
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。 Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中事务总是具有原子性Atomicity)、一致性(Consistency)和隔离性Isolation并且当 Redis 运行在某种特定的持久化模式下时事务也具有持久性Durability 在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中事务总是具有原子性Atomicity、一致性Consistency和隔离性Isolation并且当 Redis 运行在某种特定的持久化模式下时事务也具有持久性Durability
### 缓存雪崩和缓存穿透问题解决方案 ### 缓存雪崩和缓存穿透问题解决方案
@ -262,7 +259,7 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
参考: 参考:
- https://blog.csdn.net/zeb_perfect/article/details/54135506[enter link description here](https://blog.csdn.net/zeb_perfect/article/details/54135506) - [https://blog.csdn.net/zeb_perfect/article/details/54135506](https://blog.csdn.net/zeb_perfect/article/details/54135506)
### 如何解决 Redis 的并发竞争 Key 问题 ### 如何解决 Redis 的并发竞争 Key 问题
@ -278,9 +275,7 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
- https://www.jianshu.com/p/8bddd381de06 - https://www.jianshu.com/p/8bddd381de06
### 如何保证缓存与数据库双写时的数据一致性?
### 如何保证缓存与数据库双写时的数据一致性?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
@ -288,13 +283,14 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
**参考:** **参考:** Java工程师面试突击第1季可能是史上最好的Java面试突击课程-中华石杉老师公众号后台回复关键字“1”即可获取该视频内容。
- Java工程师面试突击第1季可能是史上最好的Java面试突击课程-中华石杉老师。视频地址见下面! ## 公众号
- 链接: https://pan.baidu.com/s/18pp6g1xKVGCfUATf_nMrOA
- 密码5i58
### 参考: 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
- redis设计与实现(第二版) **《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -28,7 +28,7 @@ end
算法很易懂,起 5 个 master 节点分布在不同的机房尽量保证可用性。为了获得锁client 会进行如下操作: 算法很易懂,起 5 个 master 节点分布在不同的机房尽量保证可用性。为了获得锁client 会进行如下操作:
1. 得到当前的时间,微单位 1. 得到当前的时间,微单位
2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间
3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候且它会计算申请锁消耗了多少时间这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到如果锁的持续时长lock validity time比流逝的时间多的话那么锁就真正获取到了。 3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候且它会计算申请锁消耗了多少时间这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到如果锁的持续时长lock validity time比流逝的时间多的话那么锁就真正获取到了。
4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 originlock validity time - 申请锁期间流逝的时间 4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 originlock validity time - 申请锁期间流逝的时间

View File

@ -0,0 +1,973 @@
> 原文地址https://shockerli.net/post/1000-line-mysql-note/ JavaGuide 对本文进行了简答排版,新增了目录。
> 作者:格物
非常不错的总结,强烈建议保存下来,需要的时候看一看。
<!-- TOC -->
- [基本操作](#基本操作)
- [数据库操作](#数据库操作)
- [表的操作](#表的操作)
- [数据操作](#数据操作)
- [字符集编码](#字符集编码)
- [数据类型(列类型)](#数据类型列类型)
- [列属性(列约束)](#列属性列约束)
- [建表规范](#建表规范)
- [SELECT](#select)
- [UNION](#union)
- [子查询](#子查询)
- [连接查询(join)](#连接查询join)
- [TRUNCATE](#truncate)
- [备份与还原](#备份与还原)
- [视图](#视图)
- [事务(transaction)](#事务transaction)
- [锁表](#锁表)
- [触发器](#触发器)
- [SQL编程](#sql编程)
- [存储过程](#存储过程)
- [用户和权限管理](#用户和权限管理)
- [表维护](#表维护)
- [杂项](#杂项)
<!-- /TOC -->
### 基本操作
```mysql
/* Windows服务 */
-- 启动MySQL
net start mysql
-- 创建Windows服务
sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格)
/* 连接与断开服务器 */
mysql -h 地址 -P 端口 -u 用户名 -p 密码
SHOW PROCESSLIST -- 显示哪些线程正在运行
SHOW VARIABLES -- 显示系统变量信息
```
### 数据库操作
```mysql
/* 数据库操作 */ ------------------
-- 查看当前数据库
SELECT DATABASE();
-- 显示当前时间、用户名、数据库版本
SELECT now(), user(), version();
-- 创建库
CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项
数据库选项:
CHARACTER SET charset_name
COLLATE collation_name
-- 查看已有库
SHOW DATABASES[ LIKE 'PATTERN']
-- 查看当前库信息
SHOW CREATE DATABASE 数据库名
-- 修改库的选项信息
ALTER DATABASE 库名 选项信息
-- 删除库
DROP DATABASE[ IF EXISTS] 数据库名
同时删除该数据库相关的目录及其目录内容
```
### 表的操作
```mysql
-- 创建表
CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项]
每个字段必须有数据类型
最后一个字段后不能有逗号
TEMPORARY 临时表,会话结束时表自动消失
对于字段的定义:
字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string']
-- 表选项
-- 字符集
CHARSET = charset_name
如果表没有设定,则使用数据库字符集
-- 存储引擎
ENGINE = engine_name
表在管理数据时采用的不同的数据结构,结构不同会导致处理方式、提供的特性操作等不同
常见的引擎InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive
不同的引擎在保存表的结构和数据时采用不同的方式
MyISAM表文件含义.frm表定义.MYD表数据.MYI表索引
InnoDB表文件含义.frm表定义表空间数据和日志文件
SHOW ENGINES -- 显示存储引擎的状态信息
SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息
-- 自增起始数
AUTO_INCREMENT = 行数
-- 数据文件目录
DATA DIRECTORY = '目录'
-- 索引文件目录
INDEX DIRECTORY = '目录'
-- 表注释
COMMENT = 'string'
-- 分区选项
PARTITION BY ... (详细见手册)
-- 查看所有表
SHOW TABLES[ LIKE 'pattern']
SHOW TABLES FROM 库名
-- 查看表机构
SHOW CREATE TABLE 表名 (信息更详细)
DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN']
SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern']
-- 修改表
-- 修改表本身的选项
ALTER TABLE 表名 表的选项
eg: ALTER TABLE 表名 ENGINE=MYISAM;
-- 对表进行重命名
RENAME TABLE 原表名 TO 新表名
RENAME TABLE 原表名 TO 库名.表名 (可将表移动到另一个数据库)
-- RENAME可以交换两个表名
-- 修改表的字段机构13.1.2. ALTER TABLE语法
ALTER TABLE 表名 操作名
-- 操作名
ADD[ COLUMN] 字段定义 -- 增加字段
AFTER 字段名 -- 表示增加在该字段名后面
FIRST -- 表示增加在第一个
ADD PRIMARY KEY(字段名) -- 创建主键
ADD UNIQUE [索引名] (字段名)-- 创建唯一索引
ADD INDEX [索引名] (字段名) -- 创建普通索引
DROP[ COLUMN] 字段名 -- 删除字段
MODIFY[ COLUMN] 字段名 字段属性 -- 支持对字段属性进行修改,不能修改字段名(所有原有属性也需写上)
CHANGE[ COLUMN] 原字段名 新字段名 字段属性 -- 支持对字段名修改
DROP PRIMARY KEY -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性)
DROP INDEX 索引名 -- 删除索引
DROP FOREIGN KEY 外键 -- 删除外键
-- 删除表
DROP TABLE[ IF EXISTS] 表名 ...
-- 清空表数据
TRUNCATE [TABLE] 表名
-- 复制表结构
CREATE TABLE 表名 LIKE 要复制的表名
-- 复制表结构和数据
CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名
-- 检查表是否有错误
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
-- 优化表
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
-- 修复表
REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM]
-- 分析表
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
```
### 数据操作
```mysql
/* 数据操作 */ ------------------
-- 增
INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...]
-- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。
-- 可同时插入多条数据记录!
REPLACE 与 INSERT 完全一样,可互换。
INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...]
-- 查
SELECT 字段列表 FROM 表名[ 其他子句]
-- 可来自多个表的多个字段
-- 其他子句可以不使用
-- 字段列表可以用*代替,表示所有字段
-- 删
DELETE FROM 表名[ 删除条件子句]
没有条件子句,则会删除全部
-- 改
UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件]
```
### 字符集编码
```mysql
/* 字符集编码 */ ------------------
-- MySQL、数据库、表、字段均可设置编码
-- 数据编码与客户端编码不需一致
SHOW VARIABLES LIKE 'character_set_%' -- 查看所有字符集编码项
character_set_client 客户端向服务器发送数据时使用的编码
character_set_results 服务器端将结果返回给客户端所使用的编码
character_set_connection 连接层编码
SET 变量名 = 变量值
SET character_set_client = gbk;
SET character_set_results = gbk;
SET character_set_connection = gbk;
SET NAMES GBK; -- 相当于完成以上三个设置
-- 校对集
校对集用以排序
SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern'] 查看所有字符集
SHOW COLLATION [LIKE 'pattern'] 查看所有校对集
CHARSET 字符集编码 设置字符集编码
COLLATE 校对集编码 设置校对集编码
```
### 数据类型(列类型)
```mysql
/* 数据类型(列类型) */ ------------------
1. 数值类型
-- a. 整型 ----------
类型 字节 范围(有符号位)
tinyint 1字节 -128 ~ 127 无符号位0 ~ 255
smallint 2字节 -32768 ~ 32767
mediumint 3字节 -8388608 ~ 8388607
int 4字节
bigint 8字节
int(M) M表示总位数
- 默认存在符号位unsigned 属性修改
- 显示宽度如果某个数不够定义字段时设置的位数则前面以0补填zerofill 属性修改
int(5) 插入一个数'123',补填后为'00123'
- 在满足要求的情况下,越小越好。
- 1表示bool值真0表示bool值假。MySQL没有布尔类型通过整型0和1表示。常用tinyint(1)表示布尔型。
-- b. 浮点型 ----------
类型 字节 范围
float(单精度) 4字节
double(双精度) 8字节
浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。
不同于整型前后均会补填0.
定义浮点型时,需指定总位数和小数位数。
float(M, D) double(M, D)
M表示总位数D表示小数位数。
M和D的大小会决定浮点数的范围。不同于整型的固定范围。
M既表示总位数不包括小数点和正负号也表示显示宽度所有显示符号均包括
支持科学计数法表示。
浮点数表示近似值。
-- c. 定点数 ----------
decimal -- 可变长度
decimal(M, D) M也表示总位数D表示小数位数。
保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。
将浮点数转换为字符串来保存每9位数字保存为4个字节。
2. 字符串类型
-- a. char, varchar ----------
char 定长字符串,速度快,但浪费空间
varchar 变长字符串,速度慢,但节省空间
M表示能存储的最大长度此长度是字符数非字节数。
不同的编码,所占用的空间不同。
char,最多255个字符与编码无关。
varchar,最多65535字符与编码有关。
一条有效记录最大不能超过65535个字节。
utf8 最大为21844个字符gbk 最大为32766个字符latin1 最大为65532个字符
varchar 是变长的,需要利用存储空间保存 varchar 的长度如果数据小于255个字节则采用一个字节来保存长度反之需要两个字节来保存。
varchar 的最大有效长度由最大行大小和使用的字符集确定。
最大有效长度是65532字节因为在varchar存字符串时第一个字节是空的不存在任何数据然后还需两个字节来存放字符串的长度所以有效长度是64432-1-2=65532字节。
例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少 答:(65535-1-2-4-30*3)/3
-- b. blob, text ----------
blob 二进制字符串(字节字符串)
tinyblob, blob, mediumblob, longblob
text 非二进制字符串(字符字符串)
tinytext, text, mediumtext, longtext
text 在定义时,不需要定义长度,也不会计算总长度。
text 类型在定义时不可给default值
-- c. binary, varbinary ----------
类似于char和varchar用于保存二进制字符串也就是保存字节字符串而非字符字符串。
char, varchar, text 对应 binary, varbinary, blob.
3. 日期时间类型
一般用整型保存时间戳因为PHP可以很方便的将时间戳进行格式化。
datetime 8字节 日期及时间 1000-01-01 00:00:00 到 9999-12-31 23:59:59
date 3字节 日期 1000-01-01 到 9999-12-31
timestamp 4字节 时间戳 19700101000000 到 2038-01-19 03:14:07
time 3字节 时间 -838:59:59 到 838:59:59
year 1字节 年份 1901 - 2155
datetime YYYY-MM-DD hh:mm:ss
timestamp YY-MM-DD hh:mm:ss
YYYYMMDDhhmmss
YYMMDDhhmmss
YYYYMMDDhhmmss
YYMMDDhhmmss
date YYYY-MM-DD
YY-MM-DD
YYYYMMDD
YYMMDD
YYYYMMDD
YYMMDD
time hh:mm:ss
hhmmss
hhmmss
year YYYY
YY
YYYY
YY
4. 枚举和集合
-- 枚举(enum) ----------
enum(val1, val2, val3...)
在已知的值中进行单选。最大数量为65535.
枚举值在保存时以2个字节的整型(smallint)保存。每个枚举值按保存的位置顺序从1开始逐一递增。
表现为字符串类型,存储却是整型。
NULL值的索引是NULL。
空字符串错误值的索引值是0。
-- 集合set ----------
set(val1, val2, val3...)
create table tab ( gender set('男', '女', '无') );
insert into tab values ('男, 女');
最多可以有64个不同的成员。以bigint存储共8个字节。采取位运算的形式。
当创建表时SET成员值的尾部空格将自动被删除。
```
### 列属性(列约束)
```mysql
/* 列属性(列约束) */ ------------------
1. PRIMARY 主键
- 能唯一标识记录的字段,可以作为主键。
- 一个表只能有一个主键。
- 主键具有唯一性。
- 声明字段时,用 primary key 标识。
也可以在字段列表之后声明
create table tab ( id int, stu varchar(10), primary key (id));
- 主键字段的值不能为null。
- 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。
create table tab ( id int, stu varchar(10), age int, primary key (stu, age));
2. UNIQUE 唯一索引(唯一约束)
使得某字段的值也不能重复。
3. NULL 约束
null不是数据类型是列的一个属性。
表示当前列是否可以为null表示什么都没有。
null, 允许为空。默认。
not null, 不允许为空。
insert into tab values (null, 'val');
-- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null
4. DEFAULT 默认值属性
当前字段的默认值。
insert into tab values (default, 'val'); -- 此时表示强制使用默认值。
create table tab ( add_time timestamp default current_timestamp );
-- 表示将当前时间的时间戳设为默认值。
current_date, current_time
5. AUTO_INCREMENT 自动增长约束
自动增长必须为索引主键或unique
只能存在一个字段为自动增长。
默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置或 alter table tbl auto_increment = x;
6. COMMENT 注释
create table tab ( id int ) comment '注释内容';
7. FOREIGN KEY 外键约束
用于限制主表与从表数据完整性。
alter table t1 add constraint `t1_t2_fk` foreign key (t1_id) references t2(id);
-- 将表t1的t1_id外键关联到表t2的id字段。
-- 每个外键都有一个名字,可以通过 constraint 指定
存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。
作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。
MySQL中可以对InnoDB引擎使用外键约束
语法:
foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作]
此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下可以设置为null.前提是该外键列没有not null。
可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。
如果指定了 on update 或 on delete在删除或更新时有如下几个操作可以选择
1. cascade级联操作。主表数据被更新主键值更新从表也被更新外键值更新。主表记录被删除从表相关记录也被删除。
2. set null设置为null。主表数据被更新主键值更新从表的外键被设置为null。主表记录被删除从表相关记录外键被设置成null。但注意要求该外键列没有not null属性约束。
3. restrict拒绝父表删除和更新。
注意外键只被InnoDB存储引擎所支持。其他引擎是不支持的。
```
### 建表规范
```mysql
/* 建表规范 */ ------------------
-- Normal Format, NF
- 每个表保存一个实体信息
- 每个具有一个ID字段作为主键
- ID主键 + 原子表
-- 1NF, 第一范式
字段不能再分,就满足第一范式。
-- 2NF, 第二范式
满足第一范式的前提下,不能出现部分依赖。
消除符合主键就可以避免部分依赖。增加单列关键字。
-- 3NF, 第三范式
满足第二范式的前提下,不能出现传递依赖。
某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。
将一个实体信息的数据放在一个表内实现。
```
### SELECT
```mysql
/* SELECT */ ------------------
SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMIT
a. select_expr
-- 可以用 * 表示所有字段。
select * from tb;
-- 可以使用表达式(计算公式、函数调用、字段也是个表达式)
select stu, 29+25, now() from tb;
-- 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。
- 使用 as 关键字,也可省略 as.
select stu+10 as add10 from tb;
b. FROM 子句
用于标识查询来源。
-- 可以为表起别名。使用as关键字。
SELECT * FROM tb1 AS tt, tb2 AS bb;
-- from子句后可以同时出现多个表。
-- 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。
SELECT * FROM tb1, tb2;
-- 向优化符提示如何选择索引
USE INDEX、IGNORE INDEX、FORCE INDEX
SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3;
SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3;
c. WHERE 子句
-- 从from获得的数据源中进行筛选。
-- 整型1表示真0表示假。
-- 表达式由运算符和运算数组成。
-- 运算数:变量(字段)、值、函数返回值
-- 运算符:
=, <=>, <>, !=, <=, <, >=, >, !, &&, ||,
in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor
is/is not 加上ture/false/unknown检验某个值的真假
<=>与<>功能相同,<=>可用于null比较
d. GROUP BY 子句, 分组子句
GROUP BY 字段/别名 [排序方式]
分组后会进行排序。升序ASC降序DESC
以下[合计函数]需配合 GROUP BY 使用:
count 返回不同的非NULL值数目 count(*)、count(字段)
sum 求和
max 求最大值
min 求最小值
avg 求平均值
group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。
e. HAVING 子句,条件子句
与 where 功能、用法相同,执行时机不同。
where 在开始时执行检测数据,对原数据进行过滤。
having 对筛选出的结果再次进行过滤。
having 字段必须是查询出来的where 字段必须是数据表存在的。
where 不可以使用字段的别名having 可以。因为执行WHERE代码时可能尚未确定列值。
where 不可以使用合计函数。一般需用合计函数才会用 having
SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。
f. ORDER BY 子句,排序子句
order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]...
升序ASC降序DESC
支持多个字段的排序。
g. LIMIT 子句,限制结果数量子句
仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合按照记录出现的顺序索引从0开始。
limit 起始位置, 获取条数
省略第一个参数表示从索引0开始。limit 获取条数
h. DISTINCT, ALL 选项
distinct 去除重复记录
默认为 all, 全部记录
```
### UNION
```mysql
/* UNION */ ------------------
将多个select查询的结果组合成一个结果集合。
SELECT ... UNION [ALL|DISTINCT] SELECT ...
默认 DISTINCT 方式,即所有返回的行都是唯一的
建议对每个SELECT查询加上小括号包裹。
ORDER BY 排序时,需加上 LIMIT 进行结合。
需要各select查询的字段数量一样。
每个select查询的字段列表(数量、类型)应一致因为结果中的字段名以第一条select语句为准。
```
### 子查询
```mysql
/* 子查询 */ ------------------
- 子查询需用括号包裹。
-- from型
from后要求是一个表必须给子查询结果取个别名。
- 简化每个查询内的条件。
- from型需将结果生成一个临时表格可用以原表的锁定的释放。
- 子查询返回一个表,表型子查询。
select * from (select * from tb where id>0) as subfrom where id>1;
-- where型
- 子查询返回一个值,标量子查询。
- 不需要给子查询取别名。
- where子查询内的表不能直接用以更新。
select * from tb where money = (select max(money) from tb);
-- 列子查询
如果子查询结果返回的是一列。
使用 in 或 not in 完成查询
exists 和 not exists 条件
如果子查询返回数据则返回1或0。常用于判断条件。
select column1 from t1 where exists (select * from t2);
-- 行子查询
查询条件是一个行。
select * from t1 where (id, gender) in (select id, gender from t2);
行构造符:(col1, col2, ...) 或 ROW(col1, col2, ...)
行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。
-- 特殊运算符
!= all() 相当于 not in
= some() 相当于 in。any 是 some 的别名
!= some() 不等同于 not in不等于其中某一个。
all, some 可以配合其他运算符一起使用。
```
### 连接查询(join)
```mysql
/* 连接查询(join) */ ------------------
将多个表的字段进行连接,可以指定连接条件。
-- 内连接(inner join)
- 默认就是内连接可省略inner。
- 只有数据存在时才能发送连接。即连接结果不能出现空行。
on 表示连接条件。其条件表达式与where类似。也可以省略条件表示条件永远为真
也可用where表示连接条件。
还有 using, 但需字段名相同。 using(字段名)
-- 交叉连接 cross join
即,没有条件的内连接。
select * from tb1 cross join tb2;
-- 外连接(outer join)
- 如果数据不存在,也会出现在连接结果中。
-- 左外连接 left join
如果数据不存在左表记录会出现而右表为null填充
-- 右外连接 right join
如果数据不存在右表记录会出现而左表为null填充
-- 自然连接(natural join)
自动判断连接条件完成连接。
相当于省略了using会自动查找相同字段名。
natural join
natural left join
natural right join
select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id;
```
### TRUNCATE
```mysql
/* TRUNCATE */ ------------------
TRUNCATE [TABLE] tbl_name
清空数据
删除重建表
区别:
1truncate 是删除表再创建delete 是逐条删除
2truncate 重置auto_increment的值。而delete不会
3truncate 不知道删除了几条而delete知道。
4当被用于带分区的表时truncate 会保留分区
```
### 备份与还原
```mysql
/* 备份与还原 */ ------------------
备份,将数据的结构与表内数据保存起来。
利用 mysqldump 指令完成。
-- 导出
mysqldump [options] db_name [tables]
mysqldump [options] ---database DB1 [DB2 DB3...]
mysqldump [options] --all--database
1. 导出一张表
  mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)
2. 导出多张表
  mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql)
3. 导出所有表
  mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)
4. 导出一个库
  mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql)
可以-w携带WHERE条件
-- 导入
1. 在登录mysql的情况下
  source 备份文件
2. 在不登录的情况下
  mysql -u用户名 -p密码 库名 < 备份文件
```
### 视图
```mysql
什么是视图:
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。
视图具有表结构文件,但不存在数据文件。
对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。
视图是存储在数据库中的查询的sql语句它主要出于两种原因安全原因视图可以隐藏一些数据社会保险基金表可以用视图只显示姓名地址而不显示社会保险号和工资数等另一原因是可使复杂的查询易于理解和使用。
-- 创建视图
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement
- 视图名必须唯一,同时不能与表重名。
- 视图可以使用select语句查询到的列名也可以自己指定相应的列名。
- 可以指定视图执行的算法通过ALGORITHM指定。
- column_list如果存在则数目必须等于SELECT语句检索的列数
-- 查看结构
SHOW CREATE VIEW view_name
-- 删除视图
- 删除视图后,数据依然存在。
- 可同时删除多个视图。
DROP VIEW [IF EXISTS] view_name ...
-- 修改视图结构
- 一般不修改视图,因为不是所有的更新视图都会映射到表上。
ALTER VIEW view_name [(column_list)] AS select_statement
-- 视图作用
1. 简化业务逻辑
2. 对客户端隐藏真实的表结构
-- 视图算法(ALGORITHM)
MERGE 合并
将视图的查询语句,与外部查询需要先合并再执行!
TEMPTABLE 临时表
将视图执行完毕后,形成临时表,再做外层查询!
UNDEFINED 未定义(默认)指的是MySQL自主去选择相应的算法。
```
### 事务(transaction)
```mysql
事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。
- 支持连续SQL的集体成功或集体撤销。
- 事务是数据库在数据晚自习方面的一个功能。
- 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。
- InnoDB被称为事务安全型引擎。
-- 事务开启
START TRANSACTION; 或者 BEGIN;
开启事务后所有被执行的SQL语句均被认作当前事务内的SQL语句。
-- 事务提交
COMMIT;
-- 事务回滚
ROLLBACK;
如果部分操作发生问题,映射到事务开启前。
-- 事务的特性
1. 原子性Atomicity
事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2. 一致性Consistency
事务前后数据的完整性必须保持一致。
- 事务开始和结束时,外部数据一致
- 在整个事务过程中,操作是连续的
3. 隔离性Isolation
多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。
4. 持久性Durability
一个事务一旦被提交,它对数据库中的数据改变就是永久性的。
-- 事务的实现
1. 要求是事务支持的表类型
2. 执行一组相关的操作前开启事务
3. 整组操作完成后,都成功,则提交;如果存在失败,选择回滚,则会回到事务开始的备份点。
-- 事务的原理
利用InnoDB的自动提交(autocommit)特性完成。
普通的MySQL执行语句后当前的数据提交操作均可被其他客户端可见。
而事务是暂时关闭“自动提交”机制需要commit提交持久化数据操作。
-- 注意
1. 数据定义语言DDL语句不能被回滚比如创建或取消数据库的语句和创建、取消或更改表或存储的子程序的语句。
2. 事务不能被嵌套
-- 保存点
SAVEPOINT 保存点名称 -- 设置一个事务保存点
ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点
RELEASE SAVEPOINT 保存点名称 -- 删除保存点
-- InnoDB自动提交特性设置
SET autocommit = 0|1; 0表示关闭自动提交1表示开启自动提交。
- 如果关闭了那普通操作的结果对其他客户端也不可见需要commit提交后才能持久化数据操作。
- 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是
SET autocommit是永久改变服务器的设置直到下次再次修改该设置。(针对当前连接)
而START TRANSACTION记录开启前的状态而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务)
```
### 锁表
```mysql
/* 锁表 */
表锁定只用于防止其它客户端进行不正当地读取和写入
MyISAM 支持表锁InnoDB 支持行锁
-- 锁定
LOCK TABLES tbl_name [AS alias]
-- 解锁
UNLOCK TABLES
```
### 触发器
```mysql
/* 触发器 */ ------------------
触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象
监听:记录的增加、修改、删除。
-- 创建触发器
CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt
参数:
trigger_time是触发程序的动作时间。它可以是 before 或 after以指明触发程序是在激活它的语句之前或之后触发。
trigger_event指明了激活触发程序的语句的类型
INSERT将新行插入表时激活触发程序
UPDATE更改某一行时激活触发程序
DELETE从表中删除某一行时激活触发程序
tbl_name监听的表必须是永久性的表不能将触发程序与TEMPORARY表或视图关联起来。
trigger_stmt当触发程序激活时执行的语句。执行多个语句可使用BEGIN...END复合语句结构
-- 删除
DROP TRIGGER [schema_name.]trigger_name
可以使用old和new代替旧的和新的数据
更新操作更新前是old更新后是new.
删除操作只有old.
增加操作只有new.
-- 注意
1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。
-- 字符连接函数
concat(str1,str2,...])
concat_ws(separator,str1,str2,...)
-- 分支语句
if 条件 then
执行语句
elseif 条件 then
执行语句
else
执行语句
end if;
-- 修改最外层语句结束符
delimiter 自定义结束符号
SQL语句
自定义结束符号
delimiter ; -- 修改回原来的分号
-- 语句块包裹
begin
语句块
end
-- 特殊的执行
1. 只要添加记录,就会触发程序。
2. Insert into on duplicate key update 语法会触发:
如果没有重复记录,会触发 before insert, after insert;
如果有重复记录并更新,会触发 before insert, before update, after update;
如果有重复记录但是没有发生更新,则触发 before insert, before update
3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert
```
### SQL编程
```mysql
/* SQL编程 */ ------------------
--// 局部变量 ----------
-- 变量声明
declare var_name[,...] type [default value]
这个语句被用来声明局部变量。要给变量提供一个默认值请包含一个default子句。值可以被指定为一个表达式不需要为一个常数。如果没有default子句初始值为null。
-- 赋值
使用 set 和 select into 语句为变量赋值。
- 注意:在函数内是可以使用全局变量(用户自定义的变量)
--// 全局变量 ----------
-- 定义、赋值
set 语句可以定义并为变量赋值。
set @var = value;
也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行但是可以是多个字段就意味着同时为多个变量进行赋值变量的数量需要与查询的列数一致。
还可以把赋值语句看作一个表达式通过select执行完成。此时为了避免=被当作关系运算符看待,使用:=代替。set语句可以使用= 和 :=)。
select @var:=20;
select @v1:=id, @v2=name from t1 limit 1;
select * from tbl_name where @var:=30;
select into 可以将表中查询获得的数据赋给变量。
-| select max(height) into @max_height from tb;
-- 自定义变量名
为了避免select语句中用户自定义的变量与系统标识符通常是字段名冲突用户自定义变量在变量名前使用@作为开始符号
@var=10;
- 变量被定义后,在整个会话周期都有效(登录到退出)
--// 控制结构 ----------
-- if语句
if search_condition then
statement_list
[elseif search_condition then
statement_list]
...
[else
statement_list]
end if;
-- case语句
CASE value WHEN [compare-value] THEN result
[WHEN [compare-value] THEN result ...]
[ELSE result]
END
-- while循环
[begin_label:] while search_condition do
statement_list
end while [end_label];
- 如果需要在循环内提前终止 while循环则需要使用标签标签需要成对出现。
-- 退出循环
退出整个循环 leave
退出当前循环 iterate
通过退出的标签决定退出哪个循环
--// 内置函数 ----------
-- 数值函数
abs(x) -- 绝对值 abs(-10.9) = 10
format(x, d) -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46
ceil(x) -- 向上取整 ceil(10.1) = 11
floor(x) -- 向下取整 floor (10.1) = 10
round(x) -- 四舍五入去整
mod(m, n) -- m%n m mod n 求余 10%3=1
pi() -- 获得圆周率
pow(m, n) -- m^n
sqrt(x) -- 算术平方根
rand() -- 随机数
truncate(x, d) -- 截取d位小数
-- 时间日期函数
now(), current_timestamp(); -- 当前日期时间
current_date(); -- 当前日期
current_time(); -- 当前时间
date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分
time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分
date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间
unix_timestamp(); -- 获得unix时间戳
from_unixtime(); -- 从时间戳获得时间
-- 字符串函数
length(string) -- string长度字节
char_length(string) -- string的字符个数
substring(str, position [,length]) -- 从str的position开始,取length个字符
replace(str ,search_str ,replace_str) -- 在str中用replace_str替换search_str
instr(string ,substring) -- 返回substring首次在string中出现的位置
concat(string [,...]) -- 连接字串
charset(str) -- 返回字串字符集
lcase(string) -- 转换成小写
left(string, length) -- 从string2中的左边起取length个字符
load_file(file_name) -- 从文件读取内容
locate(substring, string [,start_position]) -- 同instr,但可指定开始位置
lpad(string, length, pad) -- 重复用pad加在string开头,直到字串长度为length
ltrim(string) -- 去除前端空格
repeat(string, count) -- 重复count次
rpad(string, length, pad) --在str后用pad补充,直到长度为length
rtrim(string) -- 去除后端空格
strcmp(string1 ,string2) -- 逐字符比较两字串大小
-- 流程函数
case when [condition] then result [when [condition] then result ...] [else result] end 多分支
if(expr1,expr2,expr3) 双分支。
-- 聚合函数
count()
sum();
max();
min();
avg();
group_concat()
-- 其他常用函数
md5();
default();
--// 存储函数,自定义函数 ----------
-- 新建
CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型
函数体
- 函数名,应该合法的标识符,并且不应该与已有的关键字冲突。
- 一个函数应该属于某个数据库可以使用db_name.funciton_name的形式执行当前函数所属数据库否则为当前数据库。
- 参数部分,由"参数名"和"参数类型"组成。多个参数用逗号隔开。
- 函数体由多条可用的mysql语句流程控制变量声明等语句构成。
- 多条语句应该使用 begin...end 语句块包含。
- 一定要有 return 返回值语句。
-- 删除
DROP FUNCTION [IF EXISTS] function_name;
-- 查看
SHOW FUNCTION STATUS LIKE 'partten'
SHOW CREATE FUNCTION function_name;
-- 修改
ALTER FUNCTION function_name 函数选项
--// 存储过程,自定义功能 ----------
-- 定义
存储存储过程 是一段代码过程存储在数据库中的sql组成。
一个存储过程通常用于完成一段业务逻辑,例如报名,交班费,订单入库等。
而一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行 通过call执行。
-- 创建
CREATE PROCEDURE sp_name (参数列表)
过程体
参数列表:不同于函数的参数列表,需要指明参数类型
IN表示输入型
OUT表示输出型
INOUT表示混合型
注意,没有返回值。
```
### 存储过程
```mysql
/* 存储过程 */ ------------------
存储过程是一段可执行性代码的集合。相比函数,更偏向于业务逻辑。
调用CALL 过程名
-- 注意
- 没有返回值。
- 只能单独调用,不可夹杂在其他语句中
-- 参数
IN|OUT|INOUT 参数名 数据类型
IN 输入:在调用过程中,将数据输入到过程体内部的参数
OUT 输出:在调用过程中,将过程体处理完的结果返回到客户端
INOUT 输入输出:既可输入,也可输出
-- 语法
CREATE PROCEDURE 过程名 (参数列表)
BEGIN
过程体
END
```
### 用户和权限管理
```mysql
/* 用户和权限管理 */ ------------------
-- root密码重置
1. 停止MySQL服务
2. [Linux] /usr/local/mysql/bin/safe_mysqld --skip-grant-tables &
[Windows] mysqld --skip-grant-tables
3. use mysql;
4. UPDATE `user` SET PASSWORD=PASSWORD("密码") WHERE `user` = "root";
5. FLUSH PRIVILEGES;
用户信息表mysql.user
-- 刷新权限
FLUSH PRIVILEGES;
-- 增加用户
CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串)
- 必须拥有mysql数据库的全局CREATE USER权限或拥有INSERT权限。
- 只能创建用户,不能赋予权限。
- 用户名,注意引号:如 'user_name'@'192.168.1.1'
- 密码也需引号,纯数字密码也要加引号
- 要在纯文本中指定密码需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值需包含关键字PASSWORD
-- 重命名用户
RENAME USER old_user TO new_user
-- 设置密码
SET PASSWORD = PASSWORD('密码') -- 为当前用户设置密码
SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码
-- 删除用户
DROP USER 用户名
-- 分配权限/添加用户
GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password']
- all privileges 表示所有权限
- *.* 表示所有库的所有表
- 库名.表名 表示某库下面的某表
GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817';
-- 查看权限
SHOW GRANTS FOR 用户名
-- 查看当前用户权限
SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 SHOW GRANTS FOR CURRENT_USER();
-- 撤消权限
REVOKE 权限列表 ON 表名 FROM 用户名
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名 -- 撤销所有权限
-- 权限层级
-- 要使用GRANT或REVOKE您必须拥有GRANT OPTION权限并且您必须用于您正在授予或撤销的权限。
全局层级全局权限适用于一个给定服务器中的所有数据库mysql.user
GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。
数据库层级数据库权限适用于一个给定数据库中的所有目标mysql.db, mysql.host
GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。
表层级表权限适用于一个给定表中的所有列mysql.talbes_priv
GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。
列层级列权限适用于一个给定表中的单一列mysql.columns_priv
当使用REVOKE时您必须指定与被授权列相同的列。
-- 权限列表
ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限
ALTER -- 允许使用ALTER TABLE
ALTER ROUTINE -- 更改或取消已存储的子程序
CREATE -- 允许使用CREATE TABLE
CREATE ROUTINE -- 创建已存储的子程序
CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLE
CREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。
CREATE VIEW -- 允许使用CREATE VIEW
DELETE -- 允许使用DELETE
DROP -- 允许使用DROP TABLE
EXECUTE -- 允许用户运行已存储的子程序
FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE
INDEX -- 允许使用CREATE INDEX和DROP INDEX
INSERT -- 允许使用INSERT
LOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLES
PROCESS -- 允许使用SHOW FULL PROCESSLIST
REFERENCES -- 未被实施
RELOAD -- 允许使用FLUSH
REPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址
REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件)
SELECT -- 允许使用SELECT
SHOW DATABASES -- 显示所有数据库
SHOW VIEW -- 允许使用SHOW CREATE VIEW
SHUTDOWN -- 允许使用mysqladmin shutdown
SUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句mysqladmin debug命令允许您连接一次即使已达到max_connections。
UPDATE -- 允许使用UPDATE
USAGE -- “无权限”的同义词
GRANT OPTION -- 允许授予权限
```
### 表维护
```mysql
/* 表维护 */
-- 分析和存储表的关键字分布
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...
-- 检查一个或多个表是否有错误
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}
-- 整理数据文件的碎片
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
```
### 杂项
```mysql
/* 杂项 */ ------------------
1. 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符!
2. 每个库目录存在一个保存当前数据库的选项文件db.opt。
3. 注释:
单行注释 # 注释内容
多行注释 /* 注释内容 */
单行注释 -- 注释内容 (标准SQL注释风格要求双破折号后加一空格符空格、TAB、换行等)
4. 模式通配符:
_ 任意单个字符
% 任意多个字符,甚至包括零字符
单引号需要进行转义 \'
5. CMD命令行内的语句结束符可以为 ";", "\G", "\g"仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。
6. SQL对大小写不敏感
7. 清除已有语句:\c
```

View File

@ -0,0 +1,149 @@
本文来自[木木匠](https://github.com/kinglaw1204)投稿。
<!-- TOC -->
- [一 MySQL 基础架构分析](#一-mysql-基础架构分析)
- [1.1 MySQL 基本架构概览](#11-mysql-基本架构概览)
- [1.2 Server 层基本组件介绍](#12-server-层基本组件介绍)
- [1) 连接器](#1-连接器)
- [2) 查询缓存(MySQL 8.0 版本后移除)](#2-查询缓存mysql-80-版本后移除)
- [3) 分析器](#3-分析器)
- [4) 优化器](#4-优化器)
- [5) 执行器](#5-执行器)
- [二 语句分析](#二-语句分析)
- [2.1 查询语句](#21-查询语句)
- [2.2 更新语句](#22-更新语句)
- [三 总结](#三-总结)
- [四 参考](#四-参考)
<!-- /TOC -->
本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转sql 语句的更新是怎么完成的。
在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。
## 一 MySQL 基础架构分析
### 1.1 MySQL 基本架构概览
下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。
先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。
- **连接器:** 身份认证和权限相关(登录 MySQL 的时候)。
- **查询缓存:** 执行查询语句的时候会先查询缓存MySQL 8.0 版本后移除,因为这个功能不太实用)。
- **分析器:** 没有命中缓存的话SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
- **优化器:** 按照 MySQL 认为最优的方案去执行。
- **执行器:** 执行语句,然后从存储引擎返回数据。
![](https://user-gold-cdn.xitu.io/2019/3/23/169a8bc60a083849?w=950&h=1062&f=jpeg&s=38189)
简单来说 MySQL 主要分为 Server 层和存储引擎层:
- **Server 层**:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
- **存储引擎** 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。**
### 1.2 Server 层基本组件介绍
#### 1) 连接器
连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。
主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。
#### 2) 查询缓存(MySQL 8.0 版本后移除)
查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。
连接建立后执行查询语句的时候会先查询缓存MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中Key 是查询预计Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。
所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。
MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。
#### 3) 分析器
MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:
**第一步,词法分析**,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select提出查询的表提出字段名提出查询条件等等。做完这些操作后就会进入第二步。
**第二步,语法分析**,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。
完成这 2 步之后MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。
#### 4) 优化器
优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。
可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。
#### 5) 执行器
当选择了执行方案后MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。
## 二 语句分析
### 2.1 查询语句
说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下:
```sql
select * from tb_student A where A.age='18' and A.name=' 张三 ';
```
结合上面的说明,我们分析下这个语句的执行流程:
* 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
* 接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:
a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。
b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。
那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。
* 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。
### 2.2 更新语句
以上就是一条查询 sql 的执行流程那么接下来我们看看一条更新语句如何执行的呢sql 语句如下:
```
update tb_student A set A.age='19' where A.name=' 张三 ';
```
我们来给张三修改下年龄在实际数据库肯定不会设置年龄这个字段的不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走只不过执行更新的时候肯定要记录日志啦这就会引入日志模块了MySQL 自带的日志模块式 **binlog归档日志** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log重做日志**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
* 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
* 然后拿到查询的语句,把 age 改为 19然后调用引擎 API 接口写入这一行数据InnoDB 引擎把数据保存在内存中,同时记录 redo log此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
* 执行器收到通知后记录 binlog然后调用引擎接口提交 redo log 为提交状态。
* 更新完成。
**这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?**
这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) MySQL 自带的引擎是 MyISAM但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失)binlog 日志只能用来归档。
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
* **先写 redo log 直接提交,然后写 binlog**,假设写完 redo log 后机器挂了binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
* **先写 binlog然后写 redo log**,假设写完了 binlog机器异常重启了由于没有 redo log本机是无法恢复这一条记录的但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态binglog 也已经写完了,这个时候发生了异常重启会怎么样呢?
这个就要依赖于 MySQL 的处理机制了MySQL 的处理过程如下:
* 判断 redo log 是否完整,如果判断是完整的,就立即提交。
* 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。
## 三 总结
* MySQL 主要分为 Server 层和引擎层Server 层主要包括连接器、查询缓存、分析器、优化器、执行器同时还有一个日志模块binlog这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
* 引擎层是插件式的目前主要包括MyISAM,InnoDB,Memory 等。
* 查询语句的执行流程如下:权限校验(如果命中缓存)---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
* 更新语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态)
## 四 参考
* 《MySQL 实战45讲》
* MySQL 5.6参考手册:<https://dev.MySQL.com/doc/refman/5.6/en/>

View File

@ -0,0 +1,148 @@
> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [BugSpeak](https://github.com/BugSpeak) 共同完成。
<!-- TOC -->
- [事务隔离级别(图文详解)](#事务隔离级别图文详解)
- [什么是事务?](#什么是事务)
- [事务的特性(ACID)](#事务的特性acid)
- [并发事务带来的问题](#并发事务带来的问题)
- [事务隔离级别](#事务隔离级别)
- [实际情况演示](#实际情况演示)
- [脏读(读未提交)](#脏读读未提交)
- [避免脏读(读已提交)](#避免脏读读已提交)
- [不可重复读](#不可重复读)
- [可重复读](#可重复读)
- [防止幻读(可重复读)](#防止幻读可重复读)
- [参考](#参考)
<!-- /TOC -->
## 事务隔离级别(图文详解)
### 什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元这个转账会涉及到两个关键操作就是将小明的余额减少1000元将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃导致小明余额减少而小红的余额没有增加这样就不对了。事务就是保证这两个关键操作要么都成功要么都要失败。
### 事务的特性(ACID)
![事务的特性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/事务特性.png)
1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **一致性:** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
3. **隔离性:** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
### 并发事务带来的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
- **脏读Dirty read:** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- **丢失修改Lost to modify:** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如事务1读取某表中的数据A=20事务2也读取A=20事务1修改A=A-1事务2也修改A=A-1最终结果A=19事务1的修改被丢失。
- **不可重复读Unrepeatableread:** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- **幻读Phantom read:** 幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。
**不可重复度和幻读区别:**
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 事务1中的A先生读取自己的工资为 1000的操作还没完成事务2中的B先生就修改了A的工资为2000导 致A再读自己的工资时工资变为 2000这就是不可重复读。
例2同样的条件, 第1次和第2次读出来的记录数不一样 假某工资单表中工资大于3000的有4人事务1读取了所有工资大于3000的人共查到4条记录这时事务2 又插入了一条工资大于3000的记录事务1再次读取时查到的记录就变为了5条这样就导致了幻读。
### 事务隔离级别
**SQL 标准定义了四个隔离级别:**
- **READ-UNCOMMITTED(读取未提交)** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
- **READ-COMMITTED(读取已提交)** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
- **REPEATABLE-READ(可重复读)** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
- **SERIALIZABLE(可串行化)** 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说**该级别可以防止脏读、不可重复读以及幻读**。
----
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :---: | :---: | :---:| :---: |
| READ-UNCOMMITTED | √ | √ | √ |
| READ-COMMITTED | × | √ | √ |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
```sql
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
```
这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ可重读**事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读** 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的**SERIALIZABLE(可串行化)**隔离级别。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ可重读**并不会有任何性能损失。
InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。
### 实际情况演示
在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。
MySQL 命令行的默认配置中事务都是自动提交的即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TARNSACTION`
我们可以通过下面的命令来设置隔离级别。
```sql
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
```
我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:
- `START TARNSACTION` |`BEGIN`:显式地开启一个事务。
- `COMMIT`:提交事务,使得对数据库做的所有修改成为永久性。
- `ROLLBACK`:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
#### 脏读(读未提交)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-1脏读(读未提交)实例.jpg" width="800px"/>
</div>
#### 避免脏读(读已提交)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-2读已提交实例.jpg" width="800px"/>
</div>
#### 不可重复读
还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-32-1不可重复读实例.jpg"/>
</div>
#### 可重复读
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33-2可重复读.jpg"/>
</div>
#### 防止幻读(可重复读)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33防止幻读(使用可重复读).jpg"/>
</div>
一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?
幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。
### 参考
- 《MySQL技术内幕InnoDB存储引擎》
- <https://dev.mysql.com/doc/refman/5.7/en/>
- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/)
- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)

View File

@ -0,0 +1,294 @@
作者: rhwayfun,原文地址https://mp.weixin.qq.com/s/msYty4vjjC0PvrwasRH5Bw ,JavaGuide 已经获得作者授权并对原文进行了重新排版。
<!-- TOC -->
- [写在2019年后的蚂蚁、头条、拼多多的面试总结](#写在2019年后的蚂蚁头条拼多多的面试总结)
- [准备过程](#准备过程)
- [蚂蚁金服](#蚂蚁金服)
- [一面](#一面)
- [二面](#二面)
- [三面](#三面)
- [四面](#四面)
- [五面](#五面)
- [小结](#小结)
- [拼多多](#拼多多)
- [面试前](#面试前)
- [一面](#一面-1)
- [二面](#二面-1)
- [三面](#三面-1)
- [小结](#小结-1)
- [字节跳动](#字节跳动)
- [面试前](#面试前-1)
- [一面](#一面-2)
- [二面](#二面-2)
- [小结](#小结-2)
- [总结](#总结)
<!-- /TOC -->
# 2019年蚂蚁金服、头条、拼多多的面试总结
文章有点长请耐心看完绝对有收获不想听我BB直接进入面试分享
- 准备过程
- 蚂蚁金服面试分享
- 拼多多面试分享
- 字节跳动面试分享
- 总结
说起来开始进行面试是年前倒数第二周上午9点我还在去公司的公交上突然收到蚂蚁的面试电话其实算不上真正的面试。面试官只是和我聊了下他们在做的事情主要是做双十一这里大促的稳定性保障偏中间件吧说的很详细然后和我沟通了下是否有兴趣我表示有兴趣后面就收到正式面试的通知最后没选择去蚂蚁表示抱歉。
当时我自己也准备出去看看机会,顺便看看自己的实力。当时我其实挺纠结的,一方面现在部门也正需要我,还是可以有一番作为的,另一方面觉得近一年来进步缓慢,没有以前飞速进步的成就感了,而且业务和技术偏于稳定,加上自己也属于那种比较懒散的人,骨子里还是希望能够突破现状,持续在技术上有所精进。
在开始正式的总结之前,还是希望各位同仁能否听我继续发泄一会,抱拳!
我翻开自己2018年初立的flag觉得甚是惭愧。其中就有一条是保持一周写一篇博客奈何中间因为各种原因没能坚持下去。细细想来主要是自己没能真正静下来心认真投入到技术的研究和学习那么为什么会这样说白了还是因为没有确定目标或者目标不明确没有目标或者目标不明确都可能导致行动的失败。
那么问题来了目标是啥就我而言短期目标是深入研究某一项技术比如最近在研究mysql那么深入研究一定要动手实践并且有所产出这就够了么还需要我们能够举一反三结合实际开发场景想一想日常开发要注意什么这中间有没有什么坑可以看出要进步真的不是一件简单的事这种反人类的行为需要我们克服自我的弱点逐渐形成习惯。真正牛逼的人从不觉得认真学习是一件多么难的事因为这已经形成了他的习惯就喝早上起床刷牙洗脸那么自然简单。
扯了那么多,开始进入正题,先后进行了蚂蚁、拼多多和字节跳动的面试。
## 准备过程
先说说我自己的情况我2016先在蚂蚁实习了将近三个月然后去了我现在的老东家2.5年工作经验,可以说毕业后就一直老老实实在老东家打怪升级,虽说有蚂蚁的实习经历,但是因为时间太短,还是有点虚的。所以面试官看到我简历第一个问题绝对是这样的。
“哇,你在蚂蚁待过,不错啊”,面试官笑嘻嘻地问到。“是的,还好”,我说。“为啥才三个月?”,面试官脸色一沉问到。“哗啦啦解释一通。。。”,我解释道。“哦,原来如此,那我们开始面试吧”,面试官一本正经说到。
尼玛,早知道不写蚂蚁的实习经历了,后面仔细一想,当初写上蚂蚁不就给简历加点料嘛。
言归正传准备过程其实很早开始了当然这不是说我工作时老想着跳槽因为我明白现在的老东家并不是终点我还需要不断提升具体可追溯到从蚂蚁离职的时候当时出来也面了很多公司没啥大公司面了大概5家公司都拿到offer了。
工作之余常常会去额外研究自己感兴趣的技术以及工作用到的技术力求把原理搞明白并且会自己实践一把。此外买了N多书基本有时间就会去看补补基础什么操作系统、数据结构与算法、MySQL、JDK之类的源码基本都好好温习了文末会列一下自己看过的书和一些好的资料。**我深知基础就像“木桶效应”的短板,决定了能装多少水。**
此外在正式决定看机会之前我给自己列了一个提纲主要包括Java要掌握的核心要点有不懂的就查资料搞懂。我给自己定位还是Java工程师所以Java体系是一定要做到心中有数的很多东西没有常年的积累面试的时候很容易露馅学习要对得起自己不要骗人。
剩下的就是找平台和内推了,除了蚂蚁,头条和拼多多都是找人内推的,感谢蚂蚁面试官对我的欣赏,以后说不定会去蚂蚁咯😄。
平台脉脉、GitHub、v2
## 蚂蚁金服
![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvt9qal7lJSgfGJ8mq00yE1J4UQ9H1oo9t6RAL4T3whhx17TYlj1mjlXA/?wx_fmt=jpeg)
- 一面
- 二面
- 三面
- 四面
- 五面
- 小结
### 一面
一面就做了一道算法题要求两小时内完成给了长度为N的有重复元素的数组要求输出第10大的数。典型的TopK问题快排算法搞定。
算法题要注意的是合法性校验、边界条件以及异常的处理。另外,如果要写测试用例,一定要保证测试覆盖场景尽可能全。加上平时刷刷算法题,这种考核应该没问题的。
### 二面
- 自我介绍下呗
- 开源项目贡献过代码么Dubbo提过一个打印accesslog的bug算么
- 目前在部门做什么,业务简单介绍下,内部有哪些系统,作用和交互过程说下
- Dubbo踩过哪些坑分别是怎么解决的说了异常处理时业务异常捕获的问题自定义了一个异常拦截器
- 开始进入正题,说下你对线程安全的理解(多线程访问同一个对象,如果不需要考虑额外的同步,调用对象的行为就可以获得正确的结果就是线程安全)
- 事务有哪些特性ACID
- 怎么理解原子性?(同一个事务下,多个操作要么成功要么失败,不存在部分成功或者部分失败的情况)
- 乐观锁和悲观锁的区别悲观锁假定会发生冲突访问的时候都要先获得锁保证同一个时刻只有线程获得锁读读也会阻塞乐观锁假设不会发生冲突只有在提交操作的时候检查是否有冲突这两种锁在Java和MySQL分别是怎么实现的Java乐观锁通过CAS实现悲观锁通过synchronize实现。mysql乐观锁通过MVCC也就是版本实现悲观锁可以通过select... for update加上排它锁
- HashMap为什么不是线程安全的多线程操作无并发控制顺便说了在扩容的时候多线程访问时会造成死锁会形成一个环不过扩容时多线程操作形成环的问题再JDK1.8已经解决但多线程下使用HashMap还会有一些其他问题比如数据丢失所以多线程下不应该使用HashMap而应该使用ConcurrentHashMap怎么让HashMap变得线程安全(Collections的synchronize方法包装一个线程安全的Map或者直接用ConcurrentHashMap)两者的区别是什么前者直接在put和get方法加了synchronize同步后者采用了分段锁以及CAS支持更高的并发
- jdk1.8对ConcurrentHashMap做了哪些优化插入的时候如果数组元素使用了红黑树取消了分段锁设计synchronize替代了Lock锁为什么这样优化避免冲突严重时链表多长提高查询效率时间复杂度从O(N)提高到O(logN)
- redis主从机制了解么怎么实现的
- 有过GC调优的经历么有点虚答得不是很好
- 有什么想问的么?
### 三面
- 简单自我介绍下
- 监控系统怎么做的分为哪些模块模块之间怎么交互的用的什么数据库MySQL使用什么存储引擎为什么使用InnnoDB(支持事务、聚簇索引、MVCC)
- 订单表有做拆分么,怎么拆的?(垂直拆分和水平拆分)
- 水平拆分后查询过程描述下
- 如果落到某个分片的数据很大怎么办?(按照某种规则比如哈希取模、range将单张表拆分为多张表)
- 哈希取模会有什么问题么?(有的,数据分布不均,扩容缩容相对复杂 )
- 分库分表后怎么解决读写压力?(一主多从、多主多从)
- 拆分后主键怎么保证惟一?(UUID、Snowflake算法)
- Snowflake生成的ID是全局递增唯一么(不是,只是全局唯一,单机递增)
- 怎么实现全局递增的唯一ID(讲了TDDL的一次取一批ID然后再本地慢慢分配的做法)
- Mysql的索引结构说下(说了B+树B+树可以对叶子结点顺序查找,因为叶子结点存放了数据结点且有序)
- 主键索引和普通索引的区别(主键索引的叶子结点存放了整行记录普通索引的叶子结点存放了主键ID查询的时候需要做一次回表查询)一定要回表查询么?(不一定,当查询的字段刚好是索引的字段或者索引的一部分,就可以不用回表,这也是索引覆盖的原理)
- 你们系统目前的瓶颈在哪里?
- 你打算怎么优化?简要说下你的优化思路
- 有什么想问我么?
### 四面
- 介绍下自己
- 为什么要做逆向?
- 怎么理解微服务?
- 服务治理怎么实现的?(说了限流、压测、监控等模块的实现)
- 这个不是中间件做的事么,为什么你们部门做?(当时没有单独的中间件团队,微服务刚搞不久,需要进行监控和性能优化)
- 说说Spring的生命周期吧
- 说说GC的过程(说了young gc和full gc的触发条件和回收过程以及对象创建的过程)
- CMS GC有什么问题(并发清除算法,浮动垃圾,短暂停顿)
- 怎么避免产生浮动垃圾?(记得有个VM参数设置可以让扫描新生代之前进行一次young gc但是因为gc是虚拟机自动调度的所以不保证一定执行。但是还有参数可以让虚拟机强制执行一次young gc)
- 强制young gc会有什么问题(STW停顿时间变长)
- 知道G1么(了解一点 )
- 回收过程是怎么样的?(young gc、并发阶段、混合阶段、full gc说了Remember Set)
- 你提到的Remember Set底层是怎么实现的
- 有什么想问的么?
### 五面
五面是HRBP面的和我提前预约了时间主要聊了之前在蚂蚁的实习经历、部门在做的事情、职业发展、福利待遇等。阿里面试官确实是具有一票否决权的很看重你的价值观是否match一般都比较喜欢皮实的候选人。HR面一定要诚实不要说谎只要你说谎HR都会去证实直接cut了。
- 之前蚂蚁实习三个月怎么不留下来?
- 实习的时候主管是谁?
- 实习做了哪些事情?(尼玛这种也问?)
- 你对技术怎么看平时使用什么技术栈阿里HR真的是既当爹又当妈😂
- 最近有在研究什么东西么
- 你对SRE怎么看
- 对待遇有什么预期么
最后HR还对我说目前稳定性保障部挺缺人的希望我尽快回复。
### 小结
蚂蚁面试比较重视基础所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的因为我面的是稳定性保障部门还有许多单独的小组什么三年1班很有青春的感觉。面试官基本水平都比较高基本都P7以上除了基础还问了不少架构设计方面的问题收获还是挺大的。
## 拼多多
![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvtsmoh9TdJcV0hwnrjtbWPdOacyj2uYe2qaI5jvlGIQHwYtknwnGTibbQ/?wx_fmt=jpeg)
- 面试前
- 一面
- 二面
- 三面
- 小结
### 面试前
面完蚂蚁后早就听闻拼多多这个独角兽决定也去面一把。首先我在脉脉找了一个拼多多的HR加了微信聊了下发了简历便开始我的拼多多面试之旅。这里要非常感谢拼多多HR小姐姐从面试内推到offer确认一直都在帮我人真的很nice。
### 一面
- 为啥蚂蚁只待了三个月?没转正?(转正了,解释了一通。。。)
- Java中的HashMap、TreeMap解释下(TreeMap红黑树有序HashMap无序数组+链表)
- TreeMap查询写入的时间复杂度多少(O(logN))
- HashMap多线程有什么问题(线程安全,死锁)怎么解决?( jdk1.8用了synchronize + CAS扩容的时候通过CAS检查是否有修改是则重试)重试会有什么问题么?(CASCompare And Swap是比较和交换不会导致线程阻塞但是因为重试是通过自旋实现的所以仍然会占用CPU时间还有ABA的问题)怎么解决?(超时限定自旋的次数ABA可以通过原理变量AtomicStampedReference解决原理利用版本号进行比较)超过重试次数如果仍然失败怎么办?(synchronize互斥锁)
- CAS和synchronize有什么区别都用synchronize不行么(CAS是乐观锁不需要阻塞硬件级别实现的原子性synchronize会阻塞JVM级别实现的原子性。使用场景不同线程冲突严重时CAS会造成CPU压力过大导致吞吐量下降synchronize的原理是先自旋然后阻塞线程冲突严重仍然有较高的吞吐量因为线程都被阻塞了不会占用CPU
)
- 如果要保证线程安全怎么办?(ConcurrentHashMap)
- ConcurrentHashMap怎么实现线程安全的(分段锁)
- get需要加锁么为什么(不用volatile关键字)
- volatile的作用是什么(保证内存可见性)
- 底层怎么实现的?(说了主内存和工作内存读写内存屏障happen-before并在纸上画了线程交互图)
- 在多核CPU下可见性怎么保证(思考了一会,总线嗅探技术)
- 聊项目,系统之间是怎么交互的?
- 系统并发多少,怎么优化?
- 给我一张纸画了一个九方格都填了数字给一个M*N矩阵从1开始逆时针打印这M*N个数要求时间复杂度尽可能低内心OS之前貌似碰到过这题最优解是怎么实现来着思考中。。。
- 可以先说下你的思路(想起来了,说了什么时候要变换方向的条件,向右、向下、向左、向上,依此循环)
- 有什么想问我的?
### 二面
- 自我介绍下
- 手上还有其他offer么(拿了蚂蚁的offer)
- 部门组织结构是怎样的?(这轮不是技术面么,不过还是老老实实说了)
- 系统有哪些模块,每个模块用了哪些技术,数据怎么流转的?(面试官有点秃顶,一看级别就很高)给了我一张纸,我在上面简单画了下系统之间的流转情况
- 链路追踪的信息是怎么传递的?(RpcContext的attachment说了Span的结构:parentSpanId + curSpanId)
- SpanId怎么保证唯一性(UUID说了下内部的定制改动)
- RpcContext是在什么维度传递的(线程)
- Dubbo的远程调用怎么实现的(讲了读取配置、拼装url、创建Invoker、服务导出、服务注册以及消费者通过动态代理、filter、获取Invoker列表、负载均衡等过程哗啦啦讲了10多分钟我可以喝口水么)
- Spring的单例是怎么实现的(单例注册表)
- 为什么要单独实现一个服务治理框架?(说了下内部刚搞微服务不久,主要对服务进行一些监控和性能优化)
- 谁主导的?内部还在使用么?
- 逆向有想过怎么做成通用么?
- 有什么想问的么?
### 三面
二面老大面完后就直接HR面了主要问了些职业发展、是否有其他offer、以及入职意向等问题顺便说了下公司的福利待遇等都比较常规啦。不过要说的是手上有其他offer或者大厂经历会有一定加分。
### 小结
拼多多的面试流程就简单许多毕竟是一个成立三年多的公司。面试难度中规中矩只要基础扎实应该不是问题。但不得不说工作强度很大开始面试前HR就提前和我确认能否接受这样强度的工作想来的老铁还是要做好准备
## 字节跳动
![img](https://mmbiz.qpic.cn/mmbiz_jpg/zsXjkGNcic53JMPc0FUw1lBXl5iaibrEXvtRoTSCMeUWramk7M4CekxE9ssH5DFGBxmDcw0x9hjzmbIGHVWenDK8w/?wx_fmt=jpeg)
- 面试前
- 一面
- 二面
- 小结
### 面试前
头条的面试是三家里最专业的每次面试前有专门的HR和你约时间确定OK后再进行面试。每次都是通过视频面试因为都是之前都是电话面或现场面所以视频面试还是有点不自然。也有人觉得视频面试体验很赞当然萝卜青菜各有所爱。最坑的二面的时候对方面试官的网络老是掉线最后很冤枉的挂了当然有一些点答得不好也是原因之一。所以还是有点遗憾的。
### 一面
- 先自我介绍下
- 聊项目,逆向系统是什么意思
- 聊项目,逆向系统用了哪些技术
- 线程池的线程数怎么确定?
- 如果是IO操作为主怎么确定
- 如果计算型操作又怎么确定?
- Redis熟悉么了解哪些数据结构?(说了zset) zset底层怎么实现的?(跳表)
- 跳表的查询过程是怎么样的,查询和插入的时间复杂度?(说了先从第一层查找不满足就下沉到第二层找因为每一层都是有序的写入和插入的时间复杂度都是O(logN))
- 红黑树了解么,时间复杂度?(说了是N叉平衡树O(logN))
- 既然两个数据结构时间复杂度都是O(logN)zset为什么不用红黑树(跳表实现简单,踩坑成本低,红黑树每次插入都要通过旋转以维持平衡,实现复杂)
- 点了点头说下Dubbo的原理?(说了服务注册与发布以及消费者调用的过程)踩过什么坑没有说了dubbo异常处理的和打印accesslog的问题
- CAS了解么说了CAS的实现还了解其他同步机制么说了synchronize以及两者的区别一个乐观锁一个悲观锁
- 那我们做一道题吧数组A2*n个元素n个奇数、n个偶数设计一个算法使得数组奇数下标位置放置的都是奇数偶数下标位置放置的都是偶数
- 先说下你的思路从0下标开始遍历如果是奇数下标判断该元素是否奇数是则跳过否则从该位置寻找下一个奇数
- 下一个奇数?怎么找?(有点懵逼,思考中。。)
- 有思路么?(仍然是先遍历一次数组,并对下标进行判断,如果下标属性和该位置元素不匹配从当前下标的下一个遍历数组元素,然后替换)
- 你这样时间复杂度有点高如果要求O(N)要怎么做思考一会答道“定义两个指针分别从下标0和1开始遍历遇见奇数位是是偶数和偶数位是奇数就停下交换内容”
- 时间差不多了,先到这吧。你有什么想问我的?
### 二面
- 面试官和蔼很多,你先介绍下自己吧
- 你对服务治理怎么理解的?
- 项目中的限流怎么实现的Guava ratelimiter令牌桶算法
- 具体怎么实现的?(要点是固定速率且令牌数有限)
- 如果突然很多线程同时请求令牌,有什么问题?(导致很多请求积压,线程阻塞)
- 怎么解决呢?(可以把积压的请求放到消息队列,然后异步处理)
- 如果不用消息队列怎么解决说了RateLimiter预消费的策略
- 分布式追踪的上下文是怎么存储和传递的ThreadLocal + spanId当前节点的spanId作为下个节点的父spanId
- Dubbo的RpcContext是怎么传递的ThreadLocal主线程的ThreadLocal怎么传递到线程池说了先在主线程通过ThreadLocal的get方法拿到上下文信息在线程池创建新的ThreadLocal并把之前获取的上下文信息设置到ThreadLocal中。这里要注意的线程池创建的ThreadLocal要在finally中手动remove不然会有内存泄漏的问题
- 你说的内存泄漏具体是怎么产生的说了ThreadLocal的结构主要分两种场景主线程仍然对ThreadLocal有引用和主线程不存在对ThreadLocal的引用。第一种场景因为主线程仍然在运行所以还是有对ThreadLocal的引用那么ThreadLocal变量的引用和value是不会被回收的。第二种场景虽然主线程不存在对ThreadLocal的引用且该引用是弱引用所以会在gc的时候被回收但是对用的value不是弱引用不会被内存回收仍然会造成内存泄漏
- 线程池的线程是不是必须手动remove才可以回收value是的因为线程池的核心线程是一直存在的如果不清理那么核心线程的threadLocals变量会一直持有ThreadLocal变量
- 那你说的内存泄漏是指主线程还是线程池?(主线程
- 可是主线程不是都退出了,引用的对象不应该会主动回收么?(面试官和内存泄漏杠上了),沉默了一会。。。
- 那你说下SpringMVC不同用户登录的信息怎么保证线程安全的刚才解释的有点懵逼一下没反应过来居然回答成锁了。大脑有点晕了此时已经一个小时过去了感觉情况不妙。。。
- 这个直接用ThreadLocal不就可以么你见过SpringMVC有锁实现的代码么有点晕菜。。。
- 我们聊聊mysql吧说下索引结构说了B+树)
- 为什么使用B+树?( 说了查询效率高O(logN),可以充分利用磁盘预读的特性,多叉树,深度小,叶子结点有序且存储数据)
- 什么是索引覆盖?(忘记了。。。
- Java为什么要设计双亲委派模型
- 什么时候需要自定义类加载器?
- 我们做一道题吧,手写一个对象池
- 有什么想问我的么?(感觉我很多点都没答好,是不是挂了(结果真的是)
### 小结
头条的面试确实很专业,每次面试官会提前给你发一个视频链接,然后准点开始面试,而且考察的点都比较全。
面试官都有一个特点,会抓住一个值得深入的点或者你没说清楚的点深入下去直到你把这个点讲清楚,不然面试官会觉得你并没有真正理解。二面面试官给了我一点建议,研究技术的时候一定要去研究产生的背景,弄明白在什么场景解决什么特定的问题,其实很多技术内部都是相通的。很诚恳,还是很感谢这位面试官大大。
## 总结
从年前开始面试到头条面完大概一个多月的时间真的有点身心俱疲的感觉。最后拿到了拼多多、蚂蚁的offer还是蛮幸运的。头条的面试对我帮助很大再次感谢面试官对我的诚恳建议以及拼多多的HR对我的啰嗦的问题详细解答。
这里要说的是面试前要做好两件事:简历和自我介绍,简历要好好回顾下自己做的一些项目,然后挑几个亮点项目。自我介绍基本每轮面试都有,所以最好提前自己练习下,想好要讲哪些东西,分别怎么讲。此外,简历提到的技术一定是自己深入研究过的,没有深入研究也最好找点资料预热下,不打无准备的仗。
**这些年看过的书**
《Effective Java》、《现代操作系统》、《TCP/IP详解卷一》、《代码整洁之道》、《重构》、《Java程序性能优化》、《Spring实战》、《Zookeeper》、《高性能MySQL》、《亿级网站架构核心技术》、《可伸缩服务架构》、《Java编程思想》
说实话这些书很多只看了一部分,我通常会带着问题看书,不然看着看着就睡着了,简直是催眠良药😅。
最后,附一张自己面试前准备的脑图:
链接:https://pan.baidu.com/s/1o2l1tuRakBEP0InKEh4Hzw 密码:300d
全文完。

View File

@ -0,0 +1,96 @@
> 作者ppxyn。本文来自读者投稿同时也欢迎各位投稿**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。
**目录**
<!-- MarkdownTOC -->
- [前言](#前言)
- [一面\(技术面\)](#一面技术面)
- [二面\(技术面\)](#二面技术面)
- [三面\(技术面\)](#三面技术面)
- [四面\(半个技术面\)](#四面半个技术面)
- [五面\(HR面\)](#五面hr面)
- [总结](#总结)
<!-- /MarkdownTOC -->
### 前言
在接触 Java 之前我接触的比较多的是硬件方面用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向到目前为止使用Java到现在大概有一年多的时间所以Java算不上很好。刚开始投递的时候实习刚辞职也没准备笔试面试很多东西都忘记了。所以刚开始我并没有直接就投递阿里毕竟心里还是有一点点小害怕的。于是我就先投递了几个不算大的公司来练手就是想着刷刷经验而已或者说是练练手ps还是挺对不起那些公司的。面了一个月其他公司后我找了我实验室的学长内推我后面就有了这5次面试。
下面简单的说一下我的这5次面试4次技术面+1次HR面希望我的经历能对你有所帮助。
### 一面(技术面)
1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
2. 聊聊项目(就是一个很普通的分布式商城,自己做了一些改进),让我画了整个项目的架构图,然后针对项目抛了一系列的提高性能的问题,还问了我做项目的过程中遇到了那些问题,如何解决的,差不读就这些吧。
3. 可能是我前面说了我会数据库优化然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。
4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性?
5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事?非递归且不用额外空间(不用栈),如何遍历二叉树
6. 后面又问了很多JVM方面的问题比如Java内存模型、常见的垃圾回收器、双亲委派模型这些
7. 你有什么问题要问吗?
### 二面(技术面)
1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
2. 操作系统的内存管理机制
3. 进程和线程的区别
4. 说下你对线程安全的理解
5. volatile 有什么作用 sychronized和lock有什么区别
6. ReentrantLock实现原理
7. 用过CountDownLatch么什么场景下用的
8. AQS底层原理。
9. 造成死锁的原因有哪些,如何预防?
10. 加锁会带来哪些性能问题。如何解决?
11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗Hashtable呢ConcurrentHashMap有了解吗
12. 是否可以实习?
13. 你有什么问题要问吗?
### 三面(技术面)
1. 有没有参加过 ACM 或者他竞赛,有没有拿过什么奖?( 我说我没参加过ACM本科参加过数学建模竞赛名次并不好没拿过什么奖。面试官好像有点失望然后我又赶紧补充说我和老师一起做过一个项目目前已经投入使用。面试官还比较感兴趣后面又和他聊了一下这个项目。
2. 研究生期间,做过什么项目,发过论文吗?有什么成果吗?
3. 你觉得你有什么优点和缺点?你觉得你相比于那些比你更优秀的人欠缺什么?
4. 有读过什么源码吗?(我说我读过 Java 集合框架和 Netty 的,面试官说 Java 集合前几面一定问的差不多,就不问了,然后就问我 Netty的我当时很慌啊
5. 介绍一下自己对 Netty 的认识为什么要用。说说业务中Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题讲讲Netty的零拷贝巴拉巴拉问了好多我记得有好几个我都没回答上来心里想着凉凉了啊。
6. 用到了那些开源技术、在开源领域做过贡献吗?
7. 常见的排序算法及其复杂度,现场写了快排。
8. 红黑树B树的一些问题。
9. 讲讲算法及数据结构在实习项目中的用处。
10. 自己的未来规划(就简单描述了一下自己未来的设想啊,说的还挺诚恳,面试官好像还挺满意的)
11. 你有什么问题要问吗?
### 四面(半个技术面)
三面面完当天晚上9点接到面试电话感觉像是部门或者项目主管。 这个和之前的面试不大相同,感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。
1. 让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊,我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。
2. 出现 OOM 后你会怎么排查问题?
3. 自己平时是如何学习新技术的?除了 Java 还回去了解其他技术吗?
4. 上一段实习经历的收获。
5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的
6. 你有什么问题问我吗?
7. 还有一些其他的,想不起来了,感觉这一面不是偏向技术来问。
## 五面(HR面)
1. 自我介绍(主要讲能突出自己的经历,会的编程技术一语带过)。
2. 你觉得你有什么优点和缺点?如何克服这些缺点?
3. 说一件大学里你自己比较有成就感的一件事情,为此付出了那些努力。
4. 你前面跟其他面试官讲过一些你做的项目吧?可以给我讲讲吗?你要考虑到我不是一个做技术的人,怎么让我也听得懂。项目中有什么问题,你怎么解决的?你最大的收获是什么?
5. 你目前有面试过其他公司吗?如果让你选,这些公司和阿里,你选哪个?(送分题,回答不好可能送命)
6. 你期望的工作地点是哪里?
7. 你有什么问题吗?
### 总结
1. 可以看出面试官问我的很多问题都是比较常见的问题,所以记得一定要提前准备,还要深入准备,不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题,遇到不会的问题不要慌,冷静分析,如果你真的回答不上来,也不要担心自己是不是就要挂了,很可能这个问题本身就比较难。
2. 表达能力和沟通能力太重要了,一定要提前练一下,我自身就是一个不太会说话的人,所以,面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久,确保面试的时候能够很清晰和简洁的说出来。
3. 等待面试的过程和面试的过程真的好熬人,那段时间我压力也比较大,好在我私下找到学长聊了很多,心情也好了很多。
4. 面试之后及时总结,面的好的话,不要得意,尽快准备下一场面试吧!
我觉得我还算是比较幸运的最后也祝大家都能获得心仪的Offer。

View File

@ -0,0 +1,251 @@
本文来自 Anonymous 的投稿 JavaGuide 对原文进行了重新排版和一点完善。
<!-- TOC -->
- [一面 (37 分钟左右)](#一面-37-分钟左右)
- [二面 (33 分钟左右)](#二面-33-分钟左右)
- [三面 (46 分钟)](#三面-46-分钟)
- [HR 面](#hr-面)
<!-- /TOC -->
### 一面 (37 分钟左右)
一面是上海的小哥打来的3.12 号中午确认的内推,下午就打来约时间了,也是唯一一个约时间的面试官。约的晚上八点。紧张的一比,人生第一次面试就献给了阿里。
幸运的是一面的小哥特温柔。好像是个海归?口语中夹杂着英文。废话不多说,上干货:
**面试官:** 先自我介绍下吧!
**我:** 巴拉巴拉...。
> 关于自我介绍:从 HR 面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对 HR 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
**面试官:** 我看你简历上写你做了个秒杀系统?我们就从这个项目开始吧,先介绍下你的项目。
> 关于项目介绍:如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
>
> 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
> 2. 在这个项目中你负责了什么、做了什么、担任了什么角色
> 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
> 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
**我:** 我说了我是如何考虑它的需求(秒杀地址隐藏,记录订单,减库存),一开始简单的用 synchronized 锁住方法,出现了问题,后来乐观锁改进,又有瓶颈,再上缓存,出现了缓存雪崩,于是缓存预热,错开缓存失效时间。最后,发现先记录订单再减库存会减少行级锁等待时间。
> 一面面试官很耐心地听,并给了我一些指导,问了我乐观锁是怎么实现的,我说是基于 sql 语句,在减库存操作的 where 条件里加剩余库存数>0他说这应该不算是一种乐观锁应该先查库存在减库存的时候判断当前库存是否与读到的库存一样可这样不是多一次查询操作吗不是很理解不过我没有反驳只是说理解您的意思。事实证明千万别怼面试官即使你觉得他说的不对
**面试官:** 我缓存雪崩什么情况下会发生?如何避免?
**我:** 当多个商品缓存同时失效时会雪崩,导致大量查询数据库。还有就是秒杀刚开始的时候缓存里没有数据。解决方案:缓存预热,错开缓存失效时间
**面试官:** 问我更新数据库的同时为什么不马上更新缓存,而是删除缓存?
**我:** 因为考虑到更新数据库后更新缓存可能会因为多线程下导致写入脏数据(比如线程 A 先更新数据库成功,接下来要取更新缓存,接着线程 B 更新数据库,但 B 又更新了缓存,接着 B 的时间片用完了,线程 A 更新了缓存)
逼逼了将近 30 分钟,面试官居然用周杰伦的语气对我说:
![not bad](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3not-bad.jpg)
我突然受宠若惊,连忙说谢谢,也正是因为第一次面试得到了面试官的肯定,才让我信心大增,二三面稳定发挥。
**面试官又曰:** 我看你还懂数据库是吧,答:略懂略懂。。。那我问个简单的吧!
**我:** 因为这个问题太简单了,所以我忘记它是什么了。
**面试官:** 你还会啥数据库知识?
**我:** 我一听,问的这么随意的吗。。。都让我选题了,我就说我了解索引,慢查询优化,巴拉巴拉
**面试官:** 等等,你说索引是吧,那你能说下索引的存储数据结构吗?
**我:** 我心想这简单啊,我就说 B+树,还说了为什么用 B+树
**面试官:** 你简历上写的这个 J.U.C 包是什么啊?(他居然不知道 JUC
**我:** 就是 java 多线程的那个包啊。。。
**面试官:** 那你都了解里面的哪些东西呢?
**我:** 哈哈哈!这可是我的强项,从 ConcurrentHashMapConcurrentLinkedQueue 说到 CountDownLatchCyclicBarrier又说到线程池分别说了底层实现和项目中的应用。
**面试官:** 我觉得差不多了,那我再问个与技术无关的问题哈,虽然这个问题可能不应该我问,就是你是如何考虑你的项目架构的呢?
**我:** 先用最简单的方式实现它,再去发掘系统的问题和瓶颈,于是查资料改进架构。。。
**面试官:** 好,那我给你介绍下我这边的情况吧
![chat-end](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3chat-end.jpg)
**总结:** 一面可能是简历面吧,问的比较简单,我在讲项目中说出了我做项目时的学习历程和思考,赢得了面试官的好感,感觉他应该给我的评价很好。
### 二面 (33 分钟左右)
然而开心了没一会,内推人问我面的怎么样啊?看我流程已经到大大 boss 那了。我一听二面不是主管吗???怎么直接跳了一面。于是瞬间慌了,赶紧(下床)学习准备二面。
隔了一天3.14 的早上 1056 分,杭州的大大 boss 给我打来了电话,卧槽我当时在上毛概课,万恶的毛概课每节课都点名,我还在最后一排不敢跑出去。于是接起电话来怂怂地说不好意思我在上课,晚上可以面试吗?大大 boss 看来很忙啊,跟我说晚上没时间啊,再说吧!
于是又隔了一天3.16 中午我收到了北京的电话,当时心里小失望,我的大大 boss 呢???接起电话来,就是一番狂轰乱炸。。。
第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
**面试官:** 我们还是从你的项目开始吧,说说你的秒杀系统。
**我:** 一面时的套路。。。我考虑到秒杀地址在开始前不应暴露给用户。。。
**面试官:** 等下啊,为什么要这样呢?暴露给用户会怎么样?
**我:** 用户提前知道秒杀地址就可以写脚本来抢购了,这样不公平
**面试官:** 那比如说啊,我现在是个黑客,我在秒杀开始时写好了脚本,运行一万个线程获取秒杀地址,这样是不是也不公平呢?
**我:** 我考虑到了这方面,于是我自己写了个 LRU 缓存(划重点,这么多好用的缓存我为啥不用偏要自己写?就是为了让面试官上钩问我是怎么写的,这样我就可以逼逼准备好的内容了!),用这个缓存存储请求的 ip 和用户名,一个 ip 和用户名只能同时透过 3 个请求。
**面试官:** 那我可不可以创建一个 ip 代理池和很多用户来抢购呢?假设我有很多手机号的账户。
**我:** 这就是在为难我胖虎啊,我说这种情况跟真实用户操作太像了。。。我没法区别,不过我觉得可以通过地理位置信息或者机器学习算法来做吧。。。
**面试官:** 好的这个问题就到这吧,你接着说
**我:** 我把生成订单和减库存两条 sql 语句放在一个事务里,都操作成功了则认为秒杀成功。
**面试官:** 等等,你这个订单表和商品库存表是在一个数据库的吧,那如果在不同的数据库中呢?
**我:** 这面试官好变态啊,我只是个本科生?!?!我觉得应该要用分布式锁来实现吧。。。
**面试官:** 有没有更轻量级的做法?
**我:** 不知道了。后来查资料发现可以用消息队列来实现。使用消息队列主要能带来两个好处:(1) 通过异步处理提高系统性能(削峰、减少响应所需时间);(2) 降低系统耦合性。关于消息队列的更多内容可以查看这篇文章:<https://snailclimb.gitee.io/javaguide/#/./system-design/data-communication/message-queue>
后来发现消息队列作用好大,于是现在在学手写一个消息队列。
**面试官:** 好的你接着说项目吧。
**我:** 我考虑到了缓存雪崩问题,于是。。。
**面试官:** 等等,你有没有考虑到一种情况,假如说你的缓存刚刚失效,大量流量就来查缓存,你的数据库会不会炸?
**我:** 我不知道数据库会不会炸,反正我快炸了。当时说没考虑这么高的并发量,后来发现也是可以用消息队列来解决,对流量削峰填谷。
**面试官:** 好项目聊(怼)完了,我们来说说别的,操作系统了解吧,你能说说 NIO 吗?
**我:** NIO 是。。。
**面试官:** 那你知道 NIO 的系统调用有哪些吗,具体是怎么实现的?
**我:** 当时复习 NIO 的时候就知道是咋回事,不知道咋实现。最近在补这方面的知识,可见 NIO 还是很重要的!
**面试官:** 说说进程切换时操作系统都会发生什么?
**我:** 不如杀了我,我最讨厌操作系统了。简单说了下,可能不对,需要答案自行百度。
**面试官:** 说说线程池?
**答:** 卧槽这我熟啊,把 Java 并发编程的艺术里讲的都说出来了,说了得有十分钟,自夸一波,毕竟这本书我看了五遍😂
**面试官:** 好问问计网吧如果设计一个聊天系统,应该用 TCP 还是 UDP为什么
**我:** 当然是 TCP原因如下
![TCP VS UDP](https://user-gold-cdn.xitu.io/2018/4/19/162db5e97e9a9e01?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
**面试官:** 好的,你有什么要问我的吗?
**我:** 我还有下一次面试吗?
**面试官:** 应该。应该有的一周内吧。还告诉我居然转正前要实习三个月wtf一个大三满课的本科生让我如何在八月底前实习三个月
**我:** 面试官再见
![saygoodbye-smile](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3saygoodbye-smile.jpg)
### 三面 (46 分钟)
3.18 号,三面来了,这次又是那个大大 boss
第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
**面试官:** 聊聊你的项目?
**我:** 经过二面的教训,我迅速学习了一下分布式的理论知识,并应用到了我的项目(吹牛逼)中。
**面试官:** 看你用到了 Spring 的事务机制,你能说下 Spring 的事务传播吗?
**我:** 完了这个问题好像没准备,虽然之前刷知乎看到过。。。我就只说出来一条,面试官说其实这个有很多机制的,比如事务嵌套,内事务回滚外事务回滚都会有不同情况,你可以回去看看。
**面试官:** 说说你的分布式事务解决方案?
**我:** 我叭叭的照着资料查到的解决方案说了一通,面试官怎么好像没大听懂???
> 阿里巴巴之前开源了一个分布式 Fescar一种易于使用高性能基于 Java 的开源分布式事务解决方案后来Ant Financial 加入 Fescar使其成为一个更加中立和开放的分布式交易社区Fescar 重命名为 Seata。Github 地址:<https://github.com/seata/seata>
**面试官:** 好,我们聊聊其他项目,说说你这个 MapReduce 项目MapReduce 原理了解过吗?
**我:** 我叭叭地说了一通,面试官好像觉得这个项目太简单了。要不是没项目,我会把我的实验写上吗???
**面试官:** 你这个手写 BP 神经网络是干了啥?
**我:** 这是我选修机器学习课程时的一个作业,我又对它进行了扩展。
**面试官:** 你能说说为什么调整权值时要沿着梯度下降的方向?
**我:** 老大,你太厉害了,怎么什么都懂。我压根没准备这个项目。。。没想到会问,做过去好几个月了,加上当时一紧张就忘了,后来想起来大概是....。
**面试官:** 好我们问问基础知识吧,说说什么叫 xisuo
**我:**xisuo您说什么不好意思我没听清。这面试官有点口音。。。就是 xisuo 啊xisuo 你不知道吗?。。。尴尬了十几秒后我终于意识到,他在说死锁!!!
**面试官:** 假如 A 账户给 B 账户转钱,会发生 xisuo 吗?能具体说说吗?
**我:** 当时答的不好,后来发现面试官又是想问分布式,具体答案参考这个:<https://blog.csdn.net/taylorchan2016/article/details/51039362>
**面试官:** 为什么不考研?
**我:** 不喜欢学术氛围,巴拉巴拉。
**面试官:** 你有什么问题吗?
**我:** 我还有下一面吗。。。面试官说让我等,一周内答复。
------
等了十天,一度以为我凉了,内推人说我流程到 HR 了,让我等着吧可能 HR 太忙了3.28 号 HR 打来了电话,当时在教室,我直接飞了出去。
### HR 面
**面试官:** 你好啊,先自我介绍下吧
**我:** 巴拉巴拉....HR 面的技术面试和技术面的还是有所区别的!
面试官人特别好,一听就是很会说话的小姐姐!说我这里给你悄悄透露下,你的评级是 A 哦!
![panghu-knowledge](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3panghu-knowledge.jpg)
接下来就是几个经典 HR 面挂人的问题,什么难给我来什么,我看别人的 HR 面怎么都是聊聊天。。。
**面试官:** 你为什么选择支付宝呢,你怎么看待支付宝?
**我:** 我从个人情怀,公司理念,环境氛围,市场价值,趋势导向分析了一波(说白了就是疯狂夸支付宝,不过说实话我说的那些一点都没撒谎,阿里确实做到了。比如我举了个雷军和格力打赌 5 年 2000 亿销售额,大部分企业家关注的是利益,而马云更关注的是真的为人类为世界做一些事情,利益不是第一位的。)
**面试官:** 明白了解,那你的优点我们都很明了了,你能说说你的缺点吗?
> 缺点肯定不能是目标岗位需要的关键能力!!!
>
> 总之,记住一点,面试官问你这个问题的话,你可以说一些不影响你这个职位工作需要的一些缺点。比如你面试后端工程师,面试官问你的缺点是什么的话,你可以这样说:自己比较内向,平时不太爱与人交流,但是考虑到以后可能要和客户沟通,自己正在努力改。
**我:** 据说这是 HR 面最难的一个问题。。。我当时翻了好几天的知乎才找到一个合适的,也符合我的答案:我有时候会表现的不太自信,比如阿里的内推二月份就开始了,其实我当时已经复习了很久了,但是老是觉得自己还不行,不敢投简历,于是又把书看了一遍才投的,当时也是舍友怂恿一波才投的,面了之后发现其实自己也没有很差。(划重点,一定要把自己的缺点圆回来)。
**面试官:** HR 好像不太满意我的答案,继续问我还有缺点吗?
**我:** 我说比较容易紧张吧,举了自己大一面实验室因为紧张没进去的例子,后来不断调整心态,现在已经好很多了。
接下来又是个好难的问题。
**面试官:** BAT 都给你 offer 了,你怎么选?
其实我当时好想说BT 是什么?不好意思我只知道阿里。
**我 :** 哈哈哈哈开玩笑,就说了阿里的文化,支付宝给我们带来很多便利,想加入支付宝为人类做贡献!
最后 HR 问了我实习时间,现在大几之类的问题,说肯定会给我发 offer 的,让我等着就好了,希望过两天能收到好的结果。
![mengbi](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3mengbi.jpg)

View File

@ -139,7 +139,7 @@ Java程序设计语言对对象采用的不是引用调用实际上对象
下面再总结一下Java中方法参数的使用情况 下面再总结一下Java中方法参数的使用情况
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型 - 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。 - 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。 - 一个方法不能让对象参数引用一个新的对象。
@ -247,7 +247,5 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返
[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html) [https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html) [https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html)

View File

@ -168,10 +168,10 @@ Java语言通过字节码的方式在一定程度上解决了传统解释型
### 接口和抽象类的区别是什么? ### 接口和抽象类的区别是什么?
1. 接口的方法默认是public所有方法在接口中不能有实现抽象类可以有非抽象的方法 1. 接口的方法默认是public所有方法在接口中不能有实现抽象类可以有非抽象的方法
2. 接口中的实例变量默认是final类型的而抽象类中则不一定 2. 接口中的实例变量默认是final类型的而抽象类中则不一定
3. 一个类可以实现多个接口,但最多只能实现一个抽象类 3. 一个类可以实现多个接口,但最多只能实现一个抽象类
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5. 接口不能用new实例化但可以声明但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 5. 接口不能用new实例化但可以声明但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
注意Java8 后接口可以有默认实现( default )。 注意Java8 后接口可以有默认实现( default )。

View File

@ -16,39 +16,40 @@
## 2. 线程有哪些基本状态?这些状态是如何定义的? ## 2. 线程有哪些基本状态?这些状态是如何定义的?
1. **新建(new)**:新创建了一个线程对象。 Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态图源《Java 并发编程艺术》4.1.4 节)。
2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。
3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片timeslice执行程序代码。
4. **阻塞(block)**阻塞状态是指线程因为某种原因放弃了cpu使用权也即让出了cpu timeslice暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:
- **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法JVM会把该线程放 入等待队列(waiting queue)中。
- **(二). 同步阻塞**:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用则JVM会把该线程放入锁池(lock pool)中。
- **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法或者发出了I/O请求时JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时线程重新转入可运行(runnable)状态。
5. **死亡(dead)**线程run()、main()方法执行结束或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
![](https://user-gold-cdn.xitu.io/2018/8/9/1651f19d7c4e93a3?w=876&h=492&f=png&s=128092) ![Java 线程的状态 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)
备注: 可以用早起坐地铁来比喻这个过程(下面参考自牛客网某位同学的回答 线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示图源《Java 并发编程艺术》4.1.4 节):
1. 还没起床sleeping ![Java 线程状态变迁 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)
2. 起床收拾好了随时可以坐地铁出发Runnable
3. 等地铁来Waiting
4. 地铁来了但要排队上地铁I/O阻塞
5. 上了地铁发现暂时没座位synchronized阻塞
6. 地铁上找到座位Running
7. 到达目的地Dead
由上图可以看出:线程创建之后它将处于 **NEW新建** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY可运行** 状态。可运行状态的线程获得了 CPU 时间片timeslice后就处于 **RUNNING运行** 状态。
> 操作系统隐藏 Java 虚拟机JVM中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/)[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE运行中** 状态 。
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)
当线程执行 `wait()`方法之后,线程进入 **WAITING等待**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED阻塞** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED终止** 状态。
## 3. 何为多线程? ## 3. 何为多线程?
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行也就是交替运行。多核CPU的话因为每个CPU有自己的运算器所以在多个CPU中可以同时运行。 多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行也就是交替运行。多核CPU的话因为每个CPU有自己的运算器所以在多个CPU中可以同时运行。
## 4. 为什么多线程是必要的? ## 4. 为什么要使用多线程?
1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。 先从总体上来说:
2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
3. 程序的运行速度可能加快。
- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
再深入到计算机底层来探讨:
- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时IO 设备空闲;进行 IO 操作时CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子假如我们要计算一个复杂的任务我们只用一个线程的话CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
## 5 使用多线程常见的三种方式 ## 5 使用多线程常见的三种方式
### ①继承Thread类 ### ①继承Thread类
@ -186,7 +187,7 @@ Thread类中包含的成员变量代表了线程的某些优先级。如**Thread
这是另一个非常经典的java多线程面试问题而且在面试中会经常被问到。很简单但是很多人都会答不上来 这是另一个非常经典的java多线程面试问题而且在面试中会经常被问到。很简单但是很多人都会答不上来
new一个Thread线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 new一个Thread线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。
start()会执行线程的相应准备工作然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法会把run方法当成一个mian线程下的普通方法去执行并不会在某个线程中执行它所以这并不是多线程工作。 start()会执行线程的相应准备工作然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法会把run方法当成一个main线程下的普通方法去执行并不会在某个线程中执行它所以这并不是多线程工作。
**总结: 调用start方法方可启动线程并使线程进入就绪状态而run方法只是thread的一个普通方法调用还是在主线程里执行。** **总结: 调用start方法方可启动线程并使线程进入就绪状态而run方法只是thread的一个普通方法调用还是在主线程里执行。**

View File

@ -0,0 +1,89 @@
昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章:[整理了一些有助于你拿Offer的文章](<https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485434&idx=1&sn=f6bdf19d2594bf719e149e48d1384340&chksm=cea24831f9d5c1278617d347238f65f0481f36291675f05fabb382b69ea0ff3adae7ee6e6524&token=1452779379&lang=zh_CN#rd>) 。今天分享一下最近逛Github看到了一些我觉得对于Java面试以及学习有帮助的仓库这些仓库涉及Java核心知识点整理、Java常见面试题、算法、基础知识点比如网络和操作系统等等。
## 知识点相关
### 1.JavaGuide
- Github地址 [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- star: 64.0k
- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
### 2.CS-Notes
- Github 地址:<https://github.com/CyC2018/CS-Notes>
- Star: 68.3k
- 介绍: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
### 3. advanced-java
- Github地址[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- star: 23.4k
- 介绍: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
### 4.JCSprout
- Github地址[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
- star: 21.2k
- 介绍: Java Core Sprout处于萌芽阶段的 Java 核心知识库。
### 5.toBeTopJavaer
- Github地址[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
- star: 4.0 k
- 介绍: Java工程师成神之路。
### 6.architect-awesome
- Github地址[https://github.com/xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)
- star: 34.4 k
- 介绍:后端架构师技术图谱。
### 7.technology-talk
- Github地址 [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)
- star: 6.1k
- 介绍: 汇总java生态圈常用技术框架、开源中间件系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。
### 8.fullstack-tutorial
- Github地址 [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)
- star: 4.0k
- 介绍: fullstack tutorial 2019后台技术栈/架构师之路/全栈开发社区,春招/秋招/校招/面试。
### 9.3y
- Github地址[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)
- star: 1.9 k
- 介绍: Java 知识整合。
### 10.java-bible
- Github地址[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)
- star: 2.3k
- 介绍: 这里记录了一些技术摘要部分文章来自网络本项目的目的力求分享精品技术干货以Java为主。
### 11.interviews
- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)
- star: 35.3k
- 介绍: 软件工程技术面试个人指南(国外的一个项目,虽然有翻译版,但是不太推荐,因为很多内容并不适用于国内)。
## 算法相关
### 1.LeetCodeAnimation
- Github 地址: <https://github.com/MisterBooo/LeetCodeAnimation>
- Star: 33.4k
- 介绍: Demonstrate all the questions on LeetCode in the form of animation.用动画的形式呈现解LeetCode题目的思路
### 2.awesome-java-leetcode
- Github地址[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)
- star: 6.1k
- 介绍: LeetCode 上 Facebook 的面试题目。
### 3.leetcode
- Github地址[https://github.com/azl397985856/leetcode](https://github.com/azl397985856/leetcode)
- star: 12.0k
- 介绍: LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)

View File

@ -1,4 +1,4 @@
  身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的我有机会进入大厂吗”、“非计算机专业的学生能学好吗”、“如何学习Java”、“Java学习该学些东西”、“我该如何准备Java面试”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。   身边的朋友或者公众号的粉丝很多人都向我询问过“我是双非/三本/专科学校的我有机会进入大厂吗”、“非计算机专业的学生能学好吗”、“如何学习Java”、“Java学习该学些东西”、“我该如何准备Java面试”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。
### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗? ### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗?
@ -6,7 +6,7 @@
  首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。   首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。
  企业HR肯定是更喜欢高学历的人毕竟985211优秀人才比例肯定比普通学校高很多HR团队肯定会优先在这些学校里选。这就好比相亲你是愿意在很多优秀的人中选一个优秀的还是愿意在很多普通的人中选一个优秀的呢   企业HR肯定是更喜欢高学历的人毕竟985211优秀人才比例肯定比普通学校高很多HR团队肯定会优先在这些学校里选。这就好比相亲你是愿意在很多优秀的人中选一个优秀的还是愿意在很多普通的人中选一个优秀的呢
     
  双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的不过比率相比于名校的低很多而已。从大厂招聘的结果上看高学历人才的数量占据大头那些成功进入BAT、美团京东网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**   双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的不过比率相比于名校的低很多而已。从大厂招聘的结果上看高学历人才的数量占据大头那些成功进入BAT、美团京东网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**
@ -17,7 +17,7 @@
  我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。   我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。
  建议非科班的同学首先要打好计算机基础知识基础①计算机网络、②操作系统、③数据机构与算法我个人觉得这3个对你最重要。这些东西就像是内功对你以后的长远发展非常有用。当然如果你想要进大厂的话这些知识也是一定会被问到的。另外“一定学好数据机构与算法!一定学好数据机构与算法!一定学好数据机构与算法重要的东西说3遍。   建议非科班的同学首先要打好计算机基础知识基础①计算机网络、②操作系统、③数据机构与算法我个人觉得这3个对你最重要。这些东西就像是内功对你以后的长远发展非常有用。当然如果你想要进大厂的话这些知识也是一定会被问到的。另外“一定学好数据结构与算法!一定学好数据结构与算法!一定学好数据结构与算法重要的东西说3遍。
@ -31,12 +31,12 @@
下面是我总结的一些准备面试的Tips以及面试必备的注意事项 下面是我总结的一些准备面试的Tips以及面试必备的注意事项
1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账); 1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账);
2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。) 2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。)
3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**平时空闲时间多的可以刷一下笔试题目牛客网上有很多但是不要只刷面试题不动手code程序员不是为了考试而存在的。另外注意抓重点因为题目太多了但是有很多题目几乎次次遇到像这样的题目一定要搞定。 3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**平时空闲时间多的可以刷一下笔试题目牛客网上有很多但是不要只刷面试题不动手code程序员不是为了考试而存在的。另外注意抓重点因为题目太多了但是有很多题目几乎次次遇到像这样的题目一定要搞定。
4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) 4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。 7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。

View File

@ -1,8 +1,22 @@
这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。
不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。 不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。
### 1 如何获取大厂面试机会? <!-- TOC -->
- [1 如何获取大厂面试机会?](#1-如何获取大厂面试机会)
- [2 面试前的准备](#2--面试前的准备)
- [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)
- [2.2 关于着装](#22-关于着装)
- [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)
- [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)
- [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)
- [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)
- [2.7 提前准备技术面试](#27-提前准备技术面试)
- [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)
- [3 面试之后复盘](#3-面试之后复盘)
<!-- /TOC -->
## 1 如何获取大厂面试机会?
**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。** **在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**
@ -24,7 +38,7 @@
除了这些方法我也遇到过这样的经历有些大公司的一些部门可能暂时没招够人然后如果你的亲戚或者朋友刚好在这个公司而你正好又在寻求offer那么面试机会基本上是有了而且这种面试的难度好像一般还普遍比其他正规面试低很多。 除了这些方法我也遇到过这样的经历有些大公司的一些部门可能暂时没招够人然后如果你的亲戚或者朋友刚好在这个公司而你正好又在寻求offer那么面试机会基本上是有了而且这种面试的难度好像一般还普遍比其他正规面试低很多。
### 2 面试前的准备 ## 2 面试前的准备
### 2.1 准备自己的自我介绍 ### 2.1 准备自己的自我介绍
@ -57,11 +71,11 @@
1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图) 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
2. 在这个项目中你负责了什么、做了什么、担任了什么角色 2. 在这个项目中你负责了什么、做了什么、担任了什么角色
3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
### 2.7 提前准备技术面试 ### 2.7 提前准备技术面试
搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
### 2.7 面试之前做好定向复习 ### 2.7 面试之前做好定向复习
@ -69,6 +83,6 @@
举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。
# 3 面试之后复盘 ## 3 面试之后复盘
如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! 如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

View File

@ -0,0 +1,64 @@
我还记得当时我去参加面试的时候几乎每一场面试特别是HR面和高管面的时候面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊?
![无奈](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/无奈.jpg)
### 这个问题对最终面试结果的影响到底大不大?
就技术面试而言回答这个问题的时候只要你不是触碰到你所面试的公司的雷区那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说“我没问题了。”笔主当时面试的时候确实也说过挺多次“没问题要问了。”最终也没有导致笔主被pass掉可能是前面表现比较好哈哈自恋一下。我现在回想起来觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程你对这个问题的回答也会侧面反映出你对这次面试的上心程度你的问题是否有价值也影响了你最终的选择与公司是否选择你。
面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要很多公司还需要你认同它的文化我觉得你只要不是太笨应该不会栽在这里。除非你和另外一个人在能力上相同但是只能在你们两个人中选一个那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好给面试官留一个好的影响总归是没错的。
但是,就非技术面试来说,我觉得好好回答这个问题对你最终的结果还是比较重要的。
总的来说不管是技术面试还是非技术面试,如果你想赢得公司的青睐和尊重,我觉得我们都应该重视这个问题。
### 真诚一点,不要问太 Low 的问题
回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太 Low 的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不上心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题:
- 贵公司的主要业务是什么?(面试之前自己不知道提前网上查一下吗?)
- 贵公司的男女比例如何?(考虑脱单?记住你是来工作的!)
- 贵公司一年搞几次外出旅游?(你是来工作的,这些娱乐活动先别放在心上!)
- ......
### 有哪些有价值的问题值得问?
针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答然后结合自己的实际经历我概括了下面几个比较适合问的问题。
#### 面对HR或者其他Level比较低的面试官时
1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答,不会让面试官陷入无话可说的尴尬境地。另外,从面试官的回答中你可以加深对这个公司的了解,让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外,这样的问题在某种程度上还可以拉进你与面试官的距离。)
2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法。)
3. **我觉得我这次表现的不是太好,你有什么建议或者评价给我吗?**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说,这样可以显的你比较谦虚好学上进。)
4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。)
5. **这个岗位为什么还在招人?** (岗位真实性和价值咨询)
6. **大概什么时候能给我回复呢?** (终面的时候,如果面试官没有说的话,可以问一下)
7. ......
#### 面对部门领导
1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗?**
2. **未来如果我要加入这个团队,你对我的期望是什么?** (部门领导一般情况下是你的直属上级了,你以后和他打交道的机会应该是最多的。你问这个问题,会让他感觉你是一个对他的部门比较上心,比较有团体意识,并且愿意倾听的候选人。)
3. **公司对新入职的员工的培养机制是什么样的呢?** (正规的公司一般都有培养机制,提前问一下是对你自己的负责也会显的你比较上心)
4. **以您来看,这个岗位未来在公司内部的发展如何?** (在我看来,问这个问题也是对你自己的负责吧,谁不想发展前景更好的岗位呢?)
5. **团队现在面临的最大挑战是什么?** (这样的问题不会暴露你对公司的不了解,并且也能让你对未来工作的挑战或困难有一个提前的预期。)
#### 面对Level比较高的(比如总裁,老板)
1. **贵公司的发展目标和方向是什么?** (看下公司的发展是否满足自己的期望)
2. **与同行业的竞争者相比,贵公司的核心竞争优势在什么地方?** (充分了解自己的优势和劣势)
3. **公司现在面临的最大挑战是什么?**
### 来个补充,顺便送个祝福给大家
薪酬待遇和相关福利问题一般在终面的时候(最好不要在前面几面的时候就问到这个问题),面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说,如果面试官很愿意为你回答问题,对你的问题也比较上心的话,那他肯定是觉得你就是他们要招的人。
大家在面试的时候,可以根据自己对于公司或者岗位的了解程度,对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考,如果你还有其他比较好的问题的话,那当然也更好啦!
金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁,愿各位能始终不忘初心!每个人都有每个人的难处。引用一句《阿甘正传》里面的台词:“生活就像一盒巧克力,你永远不知道下一块是什么味道“。
![加油!彩虹就要来了](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/生活就像一盒巧克力你永远不知道下一块是什么味道.JPEG)

View File

@ -1,21 +1,43 @@
# 程序员的简历就该这样写 <!-- TOC -->
### 1 前言 - [程序员简历就该这样写](#程序员简历就该这样写)
<font color="red">一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。</font> 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。 - [为什么说简历很重要?](#为什么说简历很重要)
- [先从面试前来说](#先从面试前来说)
- [再从面试中来说](#再从面试中来说)
- [下面这几点你必须知道](#下面这几点你必须知道)
- [必须了解的两大法则](#必须了解的两大法则)
- [STAR法则Situation Task Action Result](#star法则situation-task-action-result)
- [FAB 法则Feature Advantage Benefit](#fab-法则feature-advantage-benefit)
- [项目经历怎么写?](#项目经历怎么写)
- [专业技能该怎么写?](#专业技能该怎么写)
- [排版注意事项](#排版注意事项)
- [其他的一些小tips](#其他的一些小tips)
- [推荐的工具/网站](#推荐的工具网站)
### 2 为什么说简历很重要? <!-- /TOC -->
#### 2.1 先从面试前来说 # 程序员简历就该这样写
假如你是网申你的简历必然会经过HR的筛选一张简历HR可能也就花费10秒钟看一下然后HR就会决定你这一关是Fail还是Pass。 本篇文章除了教大家用Markdown如何写一份程序员专属的简历后面还会给大家推荐一些不错的用来写Markdown简历的软件或者网站以及如何优雅的将Markdown格式转变为PDF格式或者其他格式
假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 推荐大家使用Markdown语法写简历然后再将Markdown格式转换为PDF格式后进行简历投递。
如果你对Markdown语法不太了解的话可以花半个小时简单看一下Markdown语法说明: http://www.markdown.cn 。
## 为什么说简历很重要?
一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。为什么说简历很重要呢?
### 先从面试前来说
- 假如你是网申你的简历必然会经过HR的筛选一张简历HR可能也就花费10秒钟看一下然后HR就会决定你这一关是Fail还是Pass。
- 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。 所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。
#### 2.2 再从面试中来说 ### 再从面试中来说
我发现大家比较喜欢看面经 这点无可厚非但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子一般情况下你的简历上注明你会的东西才会被问到Java、数据结构、网络、算法这些基础是每个人必问的比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。 我发现大家比较喜欢看面经 这点无可厚非但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子一般情况下你的简历上注明你会的东西才会被问到Java、数据结构、网络、算法这些基础是每个人必问的比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。
@ -23,17 +45,16 @@
面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。 面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
### 3 下面这几点你必须知道 ## 下面这几点你必须知道
1. 大部分公司的HR都说我们不看重学历骗你的但是如果你的学校不出众的话很难在一堆简历中脱颖而出除非你的简历上有特别的亮点比如某某大厂的实习经历、获得了某某大赛的奖等等。 1. 大部分公司的HR都说我们不看重学历骗你的但是如果你的学校不出众的话很难在一堆简历中脱颖而出除非你的简历上有特别的亮点比如某某大厂的实习经历、获得了某某大赛的奖等等。
2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作** 2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作**
3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;** 3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;**
4. **将自己的项目经历完美的展示出来非常重要。** 4. **将自己的项目经历完美的展示出来非常重要。**
### 4 必须了解的两大法则 ## 必须了解的两大法则
### STAR法则Situation Task Action Result
**①STAR法则Situation Task Action Result**
- **Situation** 事情是在什么情况下发生; - **Situation** 事情是在什么情况下发生;
- **Task:** 你是如何明确你的任务的; - **Task:** 你是如何明确你的任务的;
@ -42,14 +63,7 @@
简而言之STAR法则就是一种讲述自己故事的方式或者说是一个清晰、条理的作文模板。不管是什么合理熟练运用此法则可以轻松的对面试官描述事物的逻辑方式表现出自己分析阐述问题的清晰性、条理性和逻辑性。 简而言之STAR法则就是一种讲述自己故事的方式或者说是一个清晰、条理的作文模板。不管是什么合理熟练运用此法则可以轻松的对面试官描述事物的逻辑方式表现出自己分析阐述问题的清晰性、条理性和逻辑性。
下面这段内容摘自百度百科,我觉得写的非常不错: ### FAB 法则Feature Advantage Benefit
> STAR法则500强面试题回答时的技巧法则备受面试者成功者和500强HR的推崇。
由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容)
在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。
但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗?
**②FAB 法则Feature Advantage Benefit**
- **Feature** 是什么; - **Feature** 是什么;
- **Advantage** 比别人好在哪些地方; - **Advantage** 比别人好在哪些地方;
@ -57,7 +71,7 @@
简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。 简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。
### 5 项目经历怎么写? ## 项目经历怎么写?
简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
@ -66,7 +80,8 @@
3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
### 6 专业技能该怎么写? ## 专业技能该怎么写?
先问一下你自己会什么然后看看你意向的公司需要什么。一般HR可能并不太懂技术所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能你可以花几天时间学习一下然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善) 先问一下你自己会什么然后看看你意向的公司需要什么。一般HR可能并不太懂技术所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能你可以花几天时间学习一下然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善)
- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握 - 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握
@ -84,23 +99,23 @@
- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解 - Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解
- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib熟悉 - Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib熟悉
### 7 开源程序员Markdown格式简历模板分享 ## 排版注意事项
分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 1. 尽量简洁,不要太花里胡哨;
Github地址[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) 2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysqlJava不要写成Java。这个在我看来还是比较忌讳的所以一定要注意这个细节
3. 中文和数字英文之间加上空格的话看起来会舒服一点;
## 其他的一些小tips
我的下面这篇文章讲了如何写一份Markdown格式的简历另外文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。
[手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd)
### 8 其他的一些小tips
1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
2. 注意排版不需要花花绿绿的尽量使用Markdown语法。 2. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 3. 如果自己的Github比较活跃的话写上去也会为你加分很多。
4. 如果自己的Github比较活跃的话写上去也会为你加分很多。 4. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 5. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 6. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 7. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
## 推荐的工具/网站
- 冷熊简历(MarkDown在线简历工具可在线预览、编辑和生成PDF):<http://cv.ftqq.com/>
- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md)

View File

@ -0,0 +1,950 @@
<!-- MarkdownTOC -->
- [一 基础篇](#一-基础篇)
- [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)
- [2. 说一下转发\(Forward\)和重定向\(Redirect\)的区别](#2-说一下转发forward和重定向redirect的区别)
- [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)
- [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)
- [为什么要三次握手](#为什么要三次握手)
- [为什么要传回 SYN](#为什么要传回-syn)
- [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- [为什么要四次挥手](#为什么要四次挥手)
- [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)
- [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)
- [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)
- [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)
- [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)
- [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)
- [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)
- [事务传播行为](#事务传播行为)
- [隔离级别](#隔离级别)
- [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)
- [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)
- [二 进阶篇](#二-进阶篇)
- [1 消息队列MQ的套路](#1-消息队列mq的套路)
- [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)
- [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)
- [2)降低系统耦合性](#2降低系统耦合性)
- [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)
- [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)
- [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)
- [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)
- [2.1 两者的对比](#21-两者的对比)
- [2.2 关于两者的总结](#22-关于两者的总结)
- [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)
- [3.1 Arraylist 与 LinkedList 有什么不同?\(注意加上从数据结构分析的内容\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)
- [3.2 HashMap的底层实现](#32-hashmap的底层实现)
- [1)JDK1.8之前](#1jdk18之前)
- [2)JDK1.8之后](#2jdk18之后)
- [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)
- [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)
- [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)
- [三 终结篇](#三-终结篇)
- [1. Object类有哪些方法?](#1-object类有哪些方法)
- [1.1 Object类的常见方法总结](#11-object类的常见方法总结)
- [1.2 hashCode与equals](#12-hashcode与equals)
- [1.2.1 hashCode\(\)介绍](#121-hashcode介绍)
- [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)
- [1.2.3 hashCode\(\)与equals\(\)的相关规定](#123-hashcode与equals的相关规定)
- [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)
- [1.3 ==与equals](#13-与equals)
- [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)
- [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)
- [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)
- [JDK1.7\(上面有示意图\)](#jdk17上面有示意图)
- [JDK1.8\(上面有示意图\)](#jdk18上面有示意图)
- [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)
- [4 线程池了解吗?](#4-线程池了解吗)
- [4.1 为什么要用线程池?](#41-为什么要用线程池)
- [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)
- [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)
- [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)
- [4.3 创建的线程池的方式](#43-创建的线程池的方式)
- [5 Nginx](#5-nginx)
- [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)
- [反向代理](#反向代理)
- [负载均衡](#负载均衡)
- [动静分离](#动静分离)
- [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)
- [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)
<!-- /MarkdownTOC -->
这些问题是2018年去美团面试的同学被问到的一些常见的问题希望对你有帮助
# 一 基础篇
## 1. `System.out.println(3|9)`输出什么?
正确答案11。
**考察知识点:&&&|和||**
**&&&**
共同点两者都可做逻辑运算符。它们都表示运算符的两边都是true时结果为true
不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西然后判断是否为true是true就继续运算右边的然后判断并输出是false就停下来直接输出不会再运行后面的东西。
**|和||**
共同点两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true结果为true两边都不是true结果就为false
不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西然后判断是否为true是true就停下来直接输出不会再运行后面的东西是false就继续运算右边的然后判断并输出。
**回到本题:**
3 | 9=0011二进制 | 1001二进制=1011二进制=11十进制
## 2. 说一下转发(Forward)和重定向(Redirect)的区别
**转发是服务器行为,重定向是客户端行为。**
**转发Forword** 通过RequestDispatcher对象的`forwardHttpServletRequest request,HttpServletResponse response`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest``getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
```java
request.getRequestDispatcher("login_success.jsp").forward(request, response);
```
**重定向Redirect** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302则浏览器会到新的网址重新请求该资源。
1. **从地址栏显示来说**forward是服务器请求资源服务器直接访问目标地址的URL把那个URL的响应内容读取过来然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的所以它的地址栏还是原来的地址。redirect是服务端根据逻辑发送一个状态码告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。
2. **从数据共享来说**forward转发页面和转发到的页面可以共享request里面的数据。redirect不能共享数据。
3. **从运用地方来说**forward一般用于用户登陆的时候根据角色转发到相应的模块。redirect一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
4. **从效率来说**forward高。redirect低。
## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议
图片来源《图解HTTP》
![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
总体来说分为以下几个过程:
1. DNS解析
2. TCP连接
3. 发送HTTP请求
4. 服务器处理请求并返回HTTP报文
5. 浏览器解析渲染页面
6. 连接结束
具体可以参考下面这篇文章:
- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
## 4. TCP 三次握手和四次挥手
为了准确无误地把数据送达目标处TCP协议采用了三次握手策略。
**漫画图解:**
图片来源《图解HTTP》
![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095)
**简单示意图:**
![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088)
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
#### 为什么要三次握手
**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
第一次握手Client 什么都不能确认Server 确认了对方发送正常,自己接收正常。
第二次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:自己接收正常,对方发送正常
第三次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
#### 为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接数据才可以在客户机和服务器之间传递。
#### 传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN证明发送方主动关闭方到接收方被动关闭方的通道没有问题但是接收方到发送方的通道还需要 ACK 信号来进行验证。
![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406)
断开一个 TCP 连接则需要“四次挥手”:
- 客户端-发送一个 FIN用来关闭客户端到服务器的数据传送
- 服务器-收到这个 FIN它发回一 个 ACK确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
- 服务器-关闭与客户端的连接发送一个FIN给客户端
- 客户端-发回 ACK 报文确认并将确认序号设置为收到序号加1
#### 为什么要四次挥手
任何一方都可以在数据传送结束后发出连接释放的通知待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候则发出连接释放通知对方确认后就完全关闭了TCP连接。
举个例子A 和 B 打电话通话即将结束后A 说“我没啥要说的了”B回答“我知道了”但是 B 可能还会有要说的话A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”A 回答“知道了”,这样通话才算结束。
上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
## 5. IP地址与MAC地址的区别
参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597)
IP地址是指互联网协议地址Internet Protocol AddressIP Address的缩写。IP地址是IP协议提供的一种统一的地址格式它为互联网上的每一个网络和每一台主机分配一个逻辑地址以此来屏蔽物理地址的差异。
MAC 地址又称为物理地址、硬件地址用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡一台电脑会有一或多个网卡每个网卡都需要有一个唯一的MAC地址。
## 6. HTTP请求,响应报文格式
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273)
## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?
**为什么要使用索引?**
1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
3. 帮助服务器避免排序和临时表
4. 将随机IO变为顺序IO
5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?**
1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
**索引是如何提高查询速度的?**
将无序的数据变成相对有序的数据(就像查目录一样)
**说一下使用索引的注意事项**
1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。
2. 在使用InnoDB时使用与业务无关的自增主键作为主键即使用逻辑主键而不要使用业务主键。
3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描
4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
**Mysql索引主要使用的哪两种数据结构**
- 哈希索引对于哈希索引来说底层的数据结构就是哈希表因此在绝大多数需求为单条记录查询的时候可以选择哈希索引查询性能最快其余大部分场景建议选择BTree索引。
- BTree索引Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎MyISAM和InnoDB的实现方式是不同的。
更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)
**什么是覆盖索引?**
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称
之为“覆盖索引”。我们知道在InnoDB存储引擎中如果不是主键索引叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?
**进程与线程的区别是什么?**
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
**进程间的几种通信方式说一下?**
1. **管道pipe**管道是一种半双工的通信方式数据只能单向流动而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe无名管道和fifo命名管道两种有名管道也是半双工的通信方式但是它允许无亲缘关系进程间通信。
2. **信号量semophore**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
3. **消息队列message queue**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
4. **信号signal**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
5. **共享内存shared memory**共享内存就是映射一段能被其他进程所访问的内存这段共享内存由一个进程创建但多个进程都可以访问共享内存是最快的IPC方式它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制如信号量配合使用来实现进程间的同步和通信。
6. **套接字socket**socket即套接字是一种通信机制凭借这种机制客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
**线程间的几种通信方式知道不?**
1、锁机制
- 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。
- 读写锁:允许多个线程同时读共享数据,而对写操作互斥。
- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
2、信号量机制包括无名线程信号量与有名线程信号量
3、信号机制类似于进程间的信号处理。
线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。
## 9. 为什么要用单例模式?手写几种线程安全的单例模式?
**简单来说使用单例模式可以带来下面几个好处:**
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
**懒汉式(双重检查加锁版本)**
```java
public class Singleton {
//volatile保证当uniqueInstance变量被初始化成Singleton实例时多个线程可以正确处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (uniqueInstance == null) {
//只有第一次才彻底执行这里的代码
synchronized(Singleton.class) {
//进入同步代码块后再检查一次如果仍是null才创建实例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
```
**静态内部类方式**
静态内部实现的单例是懒加载的且线程安全。
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance只有第一次使用这个单例的实例的时候才加载同时不会有线程安全问题
```java
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
```
## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?
在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲bean 就是由 IOC 容器初始化、装配及管理的对象除此之外bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
Spring中的bean默认都是单例的这些单例Bean在多线程程序下如何保证线程安全呢 例如对于Web应用来说Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求引入Spring框架之后每个Action都是单例的那么对于Spring托管的单例Service Bean如何保证其安全呢 Spring的单例是基于BeanFactory也就是Spring容器的单例Bean在此容器内只有一个Java的单例是基于 JVM每个 JVM 内只有一个实例。
![pring的bean的作用域](https://user-gold-cdn.xitu.io/2018/11/10/166fd45773d5dd2e?w=563&h=299&f=webp&s=27930)
Spring的bean的生命周期以及更多内容可以查看[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)
## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?
#### 事务传播行为
事务传播行为(为了解决业务层方法之间互相调用的事务问题):
当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量
**支持当前事务的情况:**
- TransactionDefinition.PROPAGATION_REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY 如果当前存在事务则加入该事务如果当前没有事务则抛出异常。mandatory强制性
**不支持当前事务的情况:**
- TransactionDefinition.PROPAGATION_REQUIRES_NEW 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。
**其他情况:**
- TransactionDefinition.PROPAGATION_NESTED 如果当前存在事务则创建一个事务作为当前事务的嵌套事务来运行如果当前没有事务则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
#### 隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
## 12. SpringMVC 原理了解吗?
![SpringMVC 原理](https://user-gold-cdn.xitu.io/2018/11/10/166fd45787394192?w=1015&h=466&f=webp&s=35352)
客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据Model->将得到视图对象返回给用户
关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)
## 13. Spring AOP IOC 实现原理
过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。
**IOC** 控制反转也叫依赖注入。IOC利用java反射机制AOP利用代理模式。IOC 概念看似很抽象但是很容易理解。说简单点就是将对象交给容器管理你只需要在spring配置文件中配置对应的bean以及设置相关的属性让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候spring会把你在配置文件中配置的bean都初始化好然后在你需要调用的时候就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
**AOP** 面向切面编程。Aspect-Oriented Programming 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构用以模拟公共行为的一个集合。实现AOP的技术主要分为两大类一是采用动态代理技术利用截取消息的方式对该消息进行装饰以取代原有对象行为的执行二是采用静态织入的方式引入特定的语法创建“方面”从而使得编译器可以在编译期间织入有关“方面”的代码属于静态代理。
# 二 进阶篇
## 1 消息队列MQ的套路
消息队列/消息中间件应该是Java程序员必备的一个技能了如果你之前没接触过消息队列的话建议先去百度一下某某消息队列入门然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》如果大家没有资源的话可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可
### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处
面试官一般会先问你这个问题预热一下看你知道消息队列不一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**
#### 1)通过异步处理提高系统性能
![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123)
如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。**
通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550)
因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
#### 2)降低系统耦合性
我们知道模块分布式部署以后聚合方式通常有两种1.**分布式消息队列**和2.**分布式服务**。
> **先来简单说一下分布式服务:**
目前使用比较多的用来构建**SOAService Oriented Architecture面向服务体系结构**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章**《高性能优秀的服务框架-dubbo介绍》**[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)
> **再来谈我们的分布式消息队列:**
我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946)
**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**
**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!
### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?
- **系统可用性降低:** 系统可用性在某种程度上降低为什么这样说呢在加入MQ之前你不用考虑消息丢失或者说MQ挂掉等等的情况但是引入MQ之后你就需要去考虑了
- **系统复杂性提高:** 加入MQ之后你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题
- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
> 了解下面这个问题是为了我们更好的进行技术选型该部分摘自《Java工程师面试突击第1季-中华石杉老师》如果大家没有资源的话可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可
### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?
| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafaka |
| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |
| 单机吞吐量 | 万级吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别这是kafka最大的优点就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
| topic数量对吞吐量的影响 | | | topic可以达到几百几千个的级别吞吐量会有较小幅度的下降这是RocketMQ的一大优势在同等机器下可以支撑大量的topic | topic从几十个到几百个的时候吞吐量会大幅度下降。所以在同等机器下kafka尽量保证topic数量不要过多。如果要支撑大规模topic需要增加更多的机器资源 |
| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高kafka是分布式的一个数据多个副本少数机器宕机不会丢失数据不会导致不可用 |
| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置可以做到0丢失 | 经过参数优化配置消息可以做到0丢失 |
| 时效性 | ms级 | 微秒级这是rabbitmq的一大特点延迟是最低的 | ms级 | 延迟在ms级以内 |
| 功能支持 | MQ领域的功能极其完备 | 基于erlang开发所以并发能力很强性能极其好延时很低 | MQ功能较为完善还是分布式的扩展性好 | 功能较为简单主要支持简单的MQ功能在大数据领域的实时计算以及日志采集被大规模使用是事实上的标准 |
| 优劣势总结 | 非常成熟功能强大在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息而且现在社区以及国内应用都越来越少官方社区现在对ActiveMQ 5.x维护越来越少几个月才发布一个版本而且确实主要是基于解耦和异步来用的较少在大规模吞吐的场景中使用 | erlang语言开发性能极其好延时很低吞吐量到万级MQ功能比较完备而且开源提供的管理界面非常棒用起来很好用。社区相对比较活跃几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的RabbitMQ确实吞吐量会低一些这是因为他做的实现机制比较重。而且erlang开发国内有几个公司有实力做erlang源码级别的研究和定制如果说你没这个实力的话确实偶尔会有一些问题你很难去看懂源码你公司对这个东西的掌控很弱基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码很难定制和掌控。 | 接口简单易用而且毕竟在阿里大规模应用过有阿里品牌保障。日处理消息上百亿之多可以做到大规模吞吐性能也非常好分布式扩展也很方便社区维护还可以可靠性和可用性都是ok的还可以支撑大规模的topic数量支持复杂MQ业务场景。而且一个很大的优势在于阿里出品都是java系的我们可以自己阅读源码定制自己公司的MQ可以掌控。社区活跃度相对较为一般不过也还可以文档相对来说简单一些然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术你得做好这个技术万一被抛弃社区黄掉的风险那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显就是仅仅提供较少的核心功能但是提供超高的吞吐量ms级的延迟极高的可用性以及可靠性而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费那么对数据准确性会造成极其轻微的影响在大数据领域中以及日志采集中这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
> 这部分内容我这里不给出答案大家可以自行根据自己学习的消息队列查阅相关内容我可能会在后面的文章中介绍到这部分内容。另外下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到如果大家没有资源的话可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可
### 1.4 关于消息队列其他一些常见的问题展望
1. 引入消息队列之后如何保证高可用性?
2. 如何保证消息不被重复消费呢?
3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
4. 我该怎么保证从消息队列里拿到的数据按顺序执行?
5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
6. 如果让你来开发一个消息队列中间件,你会怎么设计架构?
## 2 谈谈 InnoDB 和 MyIsam 两者的区别
### 2.1 两者的对比
1. **count运算上的区别** 因为MyISAM缓存有表meta-data行数等因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说则没有这种缓存
2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能每次查询具有原子性其执行速度比InnoDB类型更快但是不提供事务支持。但是 InnoDB 提供事务支持,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
3. **是否支持外键:** MyISAM不支持而InnoDB支持。
### 2.2 关于两者的总结
MyISAM更适合读密集的表而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下经常选择MyISAM作为主库的存储引擎。
一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大所以当该表写并发量较高时要等待的查询就会很多了)InnoDB是不错的选择。如果你的数据量很大MyISAM支持压缩特性可以减少磁盘的空间占用而且不需要支持事务时MyISAM是最好的选择。
## 3 聊聊 Java 中的集合吧!
### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)
- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- **2. 底层数据结构:** Arraylist 底层使用的是Object数组LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);
- **3. 插入和删除是否受元素位置的影响:****ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1) 而数组为近似 O(n) 。**
- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间因为要存放直接后继和直接前驱以及数据
**补充内容:RandomAccess接口**
```java
public interface RandomAccess {
}
```
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
在 binarySearch() 方法中,它要判断传入的 list 是否RamdomAccess的实例如果是调用 indexedBinarySearch() 方法,如果不是,那么调用 iteratorBinarySearch() 方法
```java
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
```
ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢我觉得还是和底层数据结构有关ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1) ,所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n) 所以不支持快速随机访问。ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!
**下面再总结一下 list 的遍历方式选择:**
- 实现了RandomAccess接口的list优先选择普通for循环 其次foreach,
- 未实现RandomAccess接口的ist 优先选择iterator遍历foreach遍历底层也是通过iterator实现的大size的数据千万不要使用普通for循环
> Java 中的集合这类问题几乎是面试必问的问到这类问题的时候HashMap 又是几乎必问的问题,所以大家一定要引起重视!
### 3.2 HashMap的底层实现
#### 1)JDK1.8之前
JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
**JDK 1.8 HashMap 的 hash 方法源码:**
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
```java
static final int hash(Object key) {
int h;
// key.hashCode()返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移忽略符号位空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```
对比一下 JDK1.7的 HashMap 的 hash 方法源码.
```java
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
```
相比于 JDK1.8 的 hash 方法 JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991)
#### 2)JDK1.8之后
相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。
![JDK1.8之后的HashMap底层数据结构](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c351da9?w=720&h=545&f=jpeg&s=23933)
TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷因为二叉查找树在某些情况下会退化成一个线性结构。
> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题!
### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解
![红黑树](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c138cba?w=851&h=614&f=jpeg&s=34458)
**红黑树特点:**
1. 每个节点非红即黑;
2. 根节点总是黑色的;
3. 每个叶子节点都是黑色的空节点NIL节点
4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
**红黑树的应用:**
TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
**为什么要用红黑树**
简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
### 3.4 红黑树这么优秀,为何不直接使用红黑树得了?
说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。
### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别
**HashMap 和 Hashtable 的区别**
1. **线程是否安全:** HashMap 是非线程安全的Hashtable 是线程安全的Hashtable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
2. **效率:** 因为线程安全的问题HashMap 要比 Hashtable 效率高一点。另外Hashtable 基本被淘汰,不要在代码中使用它;
3. **对Null key 和Null value的支持** HashMap 中null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null直接抛出 NullPointerException。
4. **初始容量大小和每次扩充容量大小的不同 ** ①创建时如果不指定容量初始值Hashtable 默认的初始大小为11之后每次扩充容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充容量变为原来的2倍。②创建时如果给定了容量初始值那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
**HashSet 和 HashMap 区别**
如果你看过 HashSet 源码的话就应该知道HashSet 底层就是基于 HashMap 实现的。HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536)
# 三 终结篇
## 1. Object类有哪些方法?
这个问题面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言大家都要掌握
### 1.1 Object类的常见方法总结
Object类是一个特殊的类是所有类的父类。它主要提供了以下11个方法
```java
public final native Class<?> getClass()//native方法用于返回当前运行时对象的Class对象使用了final关键字修饰故不允许子类重写。
public native int hashCode() //native方法用于返回对象的哈希码主要使用在哈希表中比如JDK中的HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等String类对该方法进行了重写用户比较字符串的值是否相等。
protected native Object clone() throws CloneNotSupportedException//naitive方法用于创建并返回当前对象的一份拷贝。一般情况下对于任何对象 x表达式 x.clone() != x 为truex.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
public final native void notify()//native方法并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notifyAll()//native方法并且不能重写。跟notify一样唯一的区别就是会唤醒在此对象监视器上等待的所有线程而不是一个线程。
public final native void wait(long timeout) throws InterruptedException//native方法并且不能重写。暂停线程的执行。注意sleep方法没有释放锁而wait方法释放了锁 。timeout是等待时间。
public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数这个参数表示额外时间以毫微秒为单位范围是 0-999999。 所以超时的时间还需要加上nanos毫秒。
public final void wait() throws InterruptedException//跟之前的2个wait方法一样只不过该方法一直等待没有超时时间这个概念
protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
```
> 问完上面这个问题之后面试官很可能紧接着就会问你“hashCode与equals”相关的问题。
### 1.2 hashCode与equals
面试官可能会问你:“你重写过 hashcode 和 equals 么为什么重写equals时必须重写hashCode方法
#### 1.2.1 hashCode()介绍
hashCode() 的作用是获取哈希码也称为散列码它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
```java
public native int hashCode();
```
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
#### 1.2.2 为什么要有hashCode
**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode**
当你把对象加入HashSet时HashSet会先计算对象的hashcode值来判断对象加入的位置同时也会与其他已经加入的对象的hashcode值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象这时会调用equals方法来检查hashcode相等的对象是否真的相同。如果两者相同HashSet就不会让其加入操作成功。如果不同的话就会重新散列到其他位置。摘自我的Java启蒙书《Head fist java》第二版。这样我们就大大减少了equals的次数相应就大大提高了执行速度。
#### 1.2.3 hashCode()与equals()的相关规定
1. 如果两个对象相等则hashcode一定也是相同的
2. 两个对象相等对两个对象分别调用equals方法都返回true
3. 两个对象有相同的hashcode值它们也不一定是相等的
4. **因此equals方法被覆盖过则hashCode方法也必须被覆盖**
5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()则该class的两个对象无论如何都不会相等即使这两个对象指向相同的数据
#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode
我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
> ==与equals 的对比也是比较常问的基础问题之一!
### 1.3 ==与equals
**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
- 情况1类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2类覆盖了equals()方法。一般我们都覆盖equals()方法来两个对象的内容相等若它们的内容相等则返回true(即,认为这两个对象相等)。
**举个例子:**
```java
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
```
**说明:**
- String中的equals()方法是被重写过的因为Object的equals()方法是比较的对象的内存地址而String的equals()方法比较的是对象的值。
- 当创建String类型的对象时虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
> 在[【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视!
## 2 ConcurrentHashMap 相关问题
### 2.1 ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在JDK1.7的时候ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment)每一把锁只锁容器其中一部分数据多线程访问容器里不同数据段的数据就不会存在锁竞争提高并发访问率。默认分配16个Segment比Hashtable效率提高16倍。 **到了 JDK1.8 的时候已经摒弃了Segment的概念而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6以后 对 synchronized锁做了很多优化** 整个看起来就像是优化过且线程安全的 HashMap虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)**:使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
**两者的对比图:**
图片来源http://www.cnblogs.com/chengxiao/p/6842045.html
Hashtable
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg)
JDK1.7的ConcurrentHashMap
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg)
JDK1.8的ConcurrentHashMapTreeBin: 红黑二叉树节点
Node: 链表节点):
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg)
### 2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现
#### JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
Segment 实现了 ReentrantLock所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。
```java
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
```
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似是一种数组和链表结构一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
#### JDK1.8(上面有示意图)
ConcurrentHashMap取消了Segment分段锁采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点这样只要hash不冲突就不会产生并发效率又提升N倍。
## 3 谈谈 synchronized 和 ReentrantLock 的区别
**① 两者都是可重入锁**
两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。
**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
**③ ReentrantLock 比 synchronized 增加了一些高级功能**
相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
- **ReentrantLock提供了一种能够中断等待锁的线程的机制**,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视器**线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的用ReentrantLock类结合Condition实例可以实现“选择性通知”** 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能那么选择ReentrantLock是一个不错的选择。
**④ 两者的性能已经相差无几**
在JDK1.6之前synchronized 的性能是比 ReentrantLock 差很多。具体表示为synchronized 关键字吞吐量岁线程数的增加下降得非常严重。而ReentrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后synchronized 和 ReentrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReentrantLock 的文章都是错的JDK1.6之后性能已经不是选择synchronized和ReentrantLock的影响因素了而且虚拟机在未来的性能改进中会更偏向于原生的synchronized所以还是提倡在synchronized能满足你的需求的情况下优先考虑使用synchronized关键字来进行同步优化后的synchronized和ReentrantLock一样在很多地方都是用到了CAS操作。
## 4 线程池了解吗?
### 4.1 为什么要用线程池?
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处
- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?
#### Java 主要提供了下面4种线程池
- **FixedThreadPool** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- **SingleThreadExecutor** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
- **CachedThreadPool** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
- **ScheduledThreadPoolExecutor** 主要用来在给定的延迟后运行任务或者定期执行任务。ScheduledThreadPoolExecutor又分为ScheduledThreadPoolExecutor包含多个线程和SingleThreadScheduledExecutor (只包含一个线程)两种。
#### 各种线程池的适用场景介绍
- **FixedThreadPool** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
- **SingleThreadExecutor** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景;
- **CachedThreadPool** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
- **ScheduledThreadPoolExecutor** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
- **SingleThreadScheduledExecutor** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。
### 4.3 创建的线程池的方式
**1 使用 Executors 创建**
我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
```java
Executors 返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadExecutor 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致OOM。
```
**2 ThreadPoolExecutor的构造函数创建**
我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下:
```java
private static ExecutorService executor = new ThreadPoolExecutor(13, 13,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(13));
```
这种情况下一旦提交的线程数超过当前可用线程数时就会抛出java.util.concurrent.RejectedExecutionException这是因为当前线程池使用的队列是有边界队列队列已经满了便无法继续处理新的请求。但是异常Exception总比发生错误Error要好。
**3 使用开源类库**
Hollis 大佬之前在他的文章中也提到了“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例
```java
public class ExecutorsDemo {
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
pool.execute(new SubThread());
}
}
}
```
通过上述方式创建线程时不仅可以避免OOM的问题还可以自定义线程名称更加方便的出错的时候溯源。
## 5 Nginx
### 5.1 简单介绍一下Nginx
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件IMAP/POP3代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
#### 反向代理
谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
- **正向代理:**某些情况下代理我们用户去访问服务器需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN 了。
- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
通过下面两幅图大家应该更好理解图源http://blog.720ui.com/2016/nginx_action_05_proxy/
![正向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/60925795.jpg)
![反向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/62563930.jpg)
所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
#### 负载均衡
在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
Nginx支持的weight轮询默认、ip_hash、fair、url_hash这四种负载均衡调度算法感兴趣的可以自行查阅。
负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
#### 动静分离
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
### 5.2 为什么要用 Nginx?
> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。
Nginx 有以下5个优点
1. 高并发、高性能这是其他web服务器不具有的
2. 可扩展性好(模块化设计,第三方插件生态圈丰富)
3. 高可靠性(可以在服务器行持续不间断的运行数年)
4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx
5. BSD许可证意味着我们可以将源代码下载下来进行修改然后使用自己的版本
### 5.3 Nginx 的四个主要组成部分了解吗?
> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
- Nginx 二进制可执行文件:由各模块源码编译出一个文件
- nginx.conf 配置文件控制Nginx 行为
- acess.log 访问日志: 记录每一条HTTP请求信息
- error.log 错误日志:定位问题

View File

@ -1,3 +1,22 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [何谓悲观锁与乐观锁](#何谓悲观锁与乐观锁)
- [悲观锁](#悲观锁)
- [乐观锁](#乐观锁)
- [两种锁的使用场景](#两种锁的使用场景)
- [乐观锁常见的两种实现方式](#乐观锁常见的两种实现方式)
- [1. 版本号机制](#1-版本号机制)
- [2. CAS算法](#2-cas算法)
- [乐观锁的缺点](#乐观锁的缺点)
- [1 ABA 问题](#1-aba-问题)
- [2 循环时间长开销大](#2-循环时间长开销大)
- [3 只能保证一个共享变量的原子操作](#3-只能保证一个共享变量的原子操作)
- [CAS与synchronized的使用情景](#cas与synchronized的使用情景)
<!-- /TOC -->
### 何谓悲观锁与乐观锁 ### 何谓悲观锁与乐观锁
> 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 > 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
@ -64,8 +83,6 @@ JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。 CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。
### CAS与synchronized的使用情景 ### CAS与synchronized的使用情景
> **简单的来说CAS适用于写比较少的情况下多读场景冲突一般较少synchronized适用于写比较多的情况下多写场景冲突一般较多** > **简单的来说CAS适用于写比较少的情况下多读场景冲突一般较少synchronized适用于写比较多的情况下多写场景冲突一般较多**
@ -73,10 +90,17 @@ CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS
1. 对于资源竞争较少线程冲突较轻的情况使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源而CAS基于硬件实现不需要进入内核不需要切换线程操作自旋几率较少因此可以获得更高的性能。 1. 对于资源竞争较少线程冲突较轻的情况使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源而CAS基于硬件实现不需要进入内核不需要切换线程操作自旋几率较少因此可以获得更高的性能。
2. 对于资源竞争严重线程冲突严重的情况CAS自旋的概率会比较大从而浪费更多的CPU资源效率低于synchronized。 2. 对于资源竞争严重线程冲突严重的情况CAS自旋的概率会比较大从而浪费更多的CPU资源效率低于synchronized。
补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色很久之前很多人都会称它为 **“重量级锁”** 。但是在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁****轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞****竞争切换后继续竞争锁****稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下可以获得和CAS类似的性能而线程冲突严重的情况下性能远高于CAS。 补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色很久之前很多人都会称它为 **“重量级锁”** 。但是在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁****轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞****竞争切换后继续竞争锁****稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下可以获得和CAS类似的性能而线程冲突严重的情况下性能远高于CAS。
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,78 @@
本文数据统计于 1.1 号凌晨,由 SnailClimb 整理。
### 1. JavaGuide
- **Github地址** [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- **star**: 18.2k
- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
### 2. mall
- **Github地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 3.3k
- **介绍**: mall项目是一套电商系统包括前台商城系统及后台管理系统基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 3. advanced-java
- **Github地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **star**: 3.3k
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲
### 4. matrix
- **Github地址**[https://github.com/Tencent/matrix](https://github.com/Tencent/matrix)
- **star**: 2.5k
- **介绍**: Matrix 是一款微信研发并日常使用的 APMApplication Performance Manage当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。
### 5. miaosha
- **Github地址**[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)
- **star**: 2.4k
- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。
### 6. arthas
- **Github地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 8.2k
- **介绍**: Arthas 是Alibaba开源的Java诊断工具深受开发者喜爱。
### 7 spring-boot
- **Github地址** [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
- **star:** 32.6k
- **介绍** 虽然Spring的组件代码是轻量级的但它的配置却是重量级的需要大量XML配置,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的我个人非常有必要学习一下。
**关于Spring Boot官方的介绍**
> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”可能是run Application或java -jar 或 tomcat 或 maven插件run 或 shell脚本便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
### 8. tutorials
- **Github地址**[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
- **star**: 10k
- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然它们的重点是Spring Framework - SpringSpring Boot和Spring Securiyt。 除了Spring之外还有以下技术核心JavaJacksonHttpClientGuava。
### 9. qmq
- **Github地址**[https://github.com/qunarcorp/qmq](https://github.com/qunarcorp/qmq)
- **star**: 1.1k
- **介绍**: QMQ是去哪儿网内部广泛使用的消息中间件自2012年诞生以来在去哪儿网所有业务场景中广泛的应用包括跟交易息息相关的订单场景 也包括报价搜索等高吞吐量场景。
### 10. symphony
- **Github地址**[https://github.com/b3log/symphony](https://github.com/b3log/symphony)
- **star**: 9k
- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。
### 11. incubator-dubbo
- **Github地址**[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo)
- **star**: 23.6k
- **介绍**: 阿里开源的一个基于Java的高性能开源RPC框架。
### 12. apollo
- **Github地址**[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)
- **star**: 10k
- **介绍**: Apollo阿波罗是携程框架部门研发的分布式配置中心能够集中化管理应用不同环境、不同集群的配置配置修改后能够实时推送到应用端并且具备规范的权限、流程治理等特性适用于微服务配置管理场景。

View File

@ -0,0 +1,76 @@
### 1. JavaGuide
- **Github地址** [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- **star**: 22.8k
- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
### 2. advanced-java
- **Github地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **star**: 7.9k
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲
### 3. fescar
- **Github地址**[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)
- **star**: 4.6k
- **介绍**: 具有 **高性能****易用性****微服务架构****分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。
### 4. mall
- **Github地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 5.6 k
- **介绍**: mall项目是一套电商系统包括前台商城系统及后台管理系统基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 5. miaosha
- **Github地址**[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)
- **star**: 4.4k
- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。
### 6. flink
- **Github地址**[https://github.com/apache/flink](https://github.com/apache/flink)
- **star**: 7.1 k
- **介绍**: Apache Flink是一个开源流处理框架具有强大的流和批处理功能。
### 7. cim
- **Github地址**[https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim)
- **star**: 1.8 k
- **介绍**: cim(cross IM) 适用于开发者的即时通讯系统。
### 8. symphony
- **Github地址**[https://github.com/b3log/symphony](https://github.com/b3log/symphony)
- **star**: 10k
- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。
### 9. spring-boot
- **Github地址** [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
- **star:** 32.6k
- **介绍** 虽然Spring的组件代码是轻量级的但它的配置却是重量级的需要大量XML配置,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的我个人非常有必要学习一下。
**关于Spring Boot官方的介绍**
> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”可能是run Application或java -jar 或 tomcat 或 maven插件run 或 shell脚本便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
### 10. arthas
- **Github地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 9.5k
- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
**概览:**
当你遇到以下类似问题而束手无策时,`Arthas`可以帮助你解决:
0. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception
1. 我改的代码为什么没有执行到?难道是我没 commit分支搞错了
2. 遇到问题无法在线上 debug难道只能通过加日志再重新发布吗
3. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug线下无法重现
4. 是否有一个全局视角来查看系统的运行状况?
5. 有什么办法可以监控到JVM的实时运行状态
`Arthas`支持JDK 6+支持Linux/Mac/Winodws采用命令行交互模式同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。

View File

@ -0,0 +1,64 @@
### 1. JavaGuide
- **Github地址** [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- **Star**: 27.2k (4,437 stars this month)
- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
### 2.DoraemonKit
- **Github地址** <https://github.com/didi/DoraemonKit>
- **Star**: 5.2k (3,786 stars this month)
- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
### 3.advanced-java
- **Github地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **Star**:11.2k (3,042 stars this month)
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
### 4. spring-boot-examples
- **Github地址**<https://github.com/ityouknow/spring-boot-examples>
- **star**: 9.6 k (1,764 stars this month)
- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
### 5. mall
- **Github地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 7.4 k (1,736 stars this month)
- **介绍**: mall项目是一套电商系统包括前台商城系统及后台管理系统基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 6. fescar
- **Github地址**[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar)
- **star**: 6.0 k (1,308 stars this month)
- **介绍**: 具有 **高性能****易用性****微服务架构****分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。)
### 7. h4cker
- **Github地址**<https://github.com/The-Art-of-Hacking/h4cker>
- **star**: 2.1 k (1,303 stars this month)
- **介绍**: 该仓库主要由Omar Santos维护包括与道德黑客/渗透测试数字取证和事件响应DFIR漏洞研究漏洞利用开发逆向工程等相关的资源。
### 8. spring-boot
- **Github地址** [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
- **star:** 34.8k (1,073 stars this month)
- **介绍** 虽然Spring的组件代码是轻量级的但它的配置却是重量级的需要大量XML配置,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的我个人非常有必要学习一下。
**关于Spring Boot官方的介绍**
> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”可能是run Application或java -jar 或 tomcat 或 maven插件run 或 shell脚本便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
### 9. arthas
- **Github地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 10.5 k (970 stars this month)
- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
### 10. tutorials
- **Github地址**[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
- **star**: 12.1 k (789 stars this month)
- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然它们的重点是Spring Framework - SpringSpring Boot和Spring Securiyt。 除了Spring之外还有以下技术核心JavaJacksonHttpClientGuava。

View File

@ -0,0 +1,60 @@
### 1. JavaGuide
- **Github 地址** [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- **Star**: 32.9k (6,196 stars this month)
- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
### 2.advanced-java
- **Github 地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **Star**: 15.1k (4,012 stars this month)
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
### 3.spring-boot-examples
- **Github 地址**[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
- **Star**: 12.8k (3,462 stars this month)
- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
### 4. mall
- **Github 地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 9.7 k (2,418 stars this month)
- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 5. seata
- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
- **star**: 7.2 k (1359 stars this month)
- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
### 6. quarkus
- **Github 地址**[https://github.com/quarkusio/quarkus](https://github.com/quarkusio/quarkus)
- **star**: 12 k (1,224 stars this month)
- **介绍**: Quarkus 是为 GraalVM 和 HotSpot 量身定制的 Kubernetes Native Java 框架,由最佳的 Java 库和标准精心打造而成。Quarkus 的目标是使 Java 成为 Kubernetes 和无服务器环境中的领先平台,同时为开发人员提供统一的反应式和命令式编程模型,以优化地满足更广泛的分布式应用程序架构。
### 7. arthas
- **Github 地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 11.6 k (1,199 stars this month)
- **介绍**: Arthas 是 Alibaba 开源的 Java 诊断工具。
### 8.DoraemonKit
- **Github 地址** <https://github.com/didi/DoraemonKit>
- **Star**: 6.2k (1,177 stars this month)
- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
### 9.elasticsearch
- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
- **Star**: 39.7k (1,069 stars this month)
- **介绍**: 开源分布式RESTful 搜索引擎。
### 10. tutorials
- **Github 地址**[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
- **star**: 13 k (998 stars this month)
- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - SpringSpring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 JavaJacksonHttpClientGuava。

View File

@ -0,0 +1,98 @@
以下涉及到的数据统计与 2019 年 5 月 1 日 12 点,数据来源:<https://github.com/trending/java?since=monthly>
下面的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,比如下面推荐到的开源项目 Hutool 就是近期比较热门的项目之一,它是 Java 工具包,能够帮助我们简化代码!我觉得下面这些项目对于学习 Java 的朋友还是很有帮助的!
### 1. JavaGuide
- **Github 地址** [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
- **Star**: 37.9k (5,660 stars this month)
- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
### 2. advanced-java
- **Github 地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **Star**: 15.1k (4,654 stars this month)
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
### 3. CS-Notes
- **Github 地址**<https://github.com/CyC2018/CS-Notes>
- **Star**: 59.2k (4,012 stars this month)
- **介绍**: 技术面试必备基础知识。
### 4. ghidra
- **Github 地址**<https://github.com/NationalSecurityAgency/ghidra>
- **Star**: 15.0k (2,995 stars this month)
- **介绍**: Ghidra是一个软件逆向工程SRE框架。
### 5. mall
- **Github 地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 11.6 k (2,100 stars this month)
- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 6. ZXBlog
- **Github 地址** <https://github.com/ZXZxin/ZXBlog>
- **star**: 2.1 k (2,086 stars this month)
- **介绍**: 记录各种学习笔记(算法、Java、数据库、并发......)。
### 7.DoraemonKit
- **Github地址** <https://github.com/didi/DoraemonKit>
- **Star**: 7.6k (1,541 stars this month)
- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
### 8. spring-boot
- **Github地址** [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
- **star:** 37.3k (1,489 stars this month)
- **介绍** 虽然Spring的组件代码是轻量级的但它的配置却是重量级的需要大量XML配置,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的我个人非常有必要学习一下。
**Spring Boot官方的介绍**
> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”可能是run Application或java -jar 或 tomcat 或 maven插件run 或 shell脚本便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
### 9. spring-boot-examples
- **Github 地址**[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
- **Star**: 12.8k (1,453 stars this month)
- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
### 10. seata
- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
- **star**: 8.4 k (1441 stars this month)
- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
### 11. litemall
- **Github 地址**[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
- **Star**: 6.0k (1,427 stars this month)
- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
### 12. skywalking
- **Github 地址**<https://github.com/apache/skywalking>
- **Star**: 8.0k (1,381 stars this month)
- **介绍**: 针对分布式系统的应用性能监控,尤其是针对微服务、云原生和面向容器的分布式系统架构。
### 13. elasticsearch
- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
- **Star**: 4.0k (1,068stars this month)
- **介绍**: 开源分布式RESTful 搜索引擎。
### 14. arthas
- **Github地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 12.6 k (1,080 stars this month)
- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
### 15. hutool
- **Github地址**<https://github.com/looly/hutool>
- **star**: 4.5 k (1,031 stars this month)
- **介绍**: Hutool是一个Java工具包也只是一个工具包它帮助我们简化每一行代码减少每一个方法让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理后来慢慢积累并加入更多非业务相关功能并广泛学习其它开源项目精髓经过自己整理修改最终形成丰富的开源工具集。官网:<https://www.hutool.cn/>

View File

@ -0,0 +1,125 @@
以下涉及到的数据统计与 2019 年 6 月 1 日 18 点,数据来源:<https://github.com/trending/java?since=monthly> 。下面推荐的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,建议收藏+在看!
### 1.LeetCodeAnimation
- **Github 地址** <https://github.com/MisterBooo/LeetCodeAnimation>
- **Star**: 29.0k (11,492 stars this month)
- **介绍**: Demonstrate all the questions on LeetCode in the form of animation.用动画的形式呈现解LeetCode题目的思路
### 2.CS-Notes
- **Github 地址**<https://github.com/CyC2018/CS-Notes>
- **Star**: 64.4k (5513 stars this month)
- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
### 3.JavaGuide
- **Github 地址**<https://github.com/Snailclimb/JavaGuide>
- **Star**: 42.0k (4,442 stars this month)
- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
### 4.mall
- **Github 地址** [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
- **star**: 14.6 k (3,086 stars this month)
- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
### 5.advanced-java
- **Github 地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **Star**: 20.8k (2,394 stars this month)
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
### 6.spring-boot
- **Github地址** [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
- **star:** 38.5k (1,339 stars this month)
- **介绍** 虽然Spring的组件代码是轻量级的但它的配置却是重量级的需要大量XML配置,不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的我个人非常有必要学习一下。
**Spring Boot官方的介绍**
> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”可能是run Application或java -jar 或 tomcat 或 maven插件run 或 shell脚本便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
### 7. Java
- **Github 地址**<https://github.com/TheAlgorithms/Java>
- **Star**:14.3k (1,334 stars this month)
- **介绍**: All Algorithms implemented in Java。
### 8.server
- **Github 地址**<https://github.com/wildfirechat/server>
- **star**: 2.2 k (1,275 stars this month)
- **介绍**: 全开源即时通讯(IM)系统。
### 9.litemall
- **Github 地址**<https://github.com/linlinjava/litemall>
- **Star**: 7.1k (1,114 stars this month)
- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
### 10.Linkage-RecyclerView
- **Github 地址**<https://github.com/KunMinX/Linkage-RecyclerView>
- **Star**: 10.0k (1,093 stars this month)
- **介绍**: 即使不用饿了么订餐,也请务必收藏好该库!🔥 一行代码即可接入,二级联动订餐列表 - Even if you don't order food by PrubHub, be sure to collect this library, please! 🔥 This secondary linkage list widget can be accessed by only one line of code. Supporting by RecyclerView & AndroidX.
### 11.toBeTopJavaer
- **Github 地址** : <https://github.com/hollischuang/toBeTopJavaer>
- **Star**: 3.3k (1,007 stars this month)
- **介绍**: To Be Top Javaer - Java工程师成神之路
### 12.elasticsearch
- **Github 地址** : [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
- **Star**: 48.0k (968 stars this month)
- **介绍**: Design patterns implemented in Java。
### 13.java-design-patterns
- **Github 地址** : <https://github.com/iluwatar/java-design-patterns>
- **Star**: 41.5k (955 stars this month)
- **介绍**: 开源分布式RESTful 搜索引擎。
### 14.apollo
- **Github 地址** : <https://github.com/ctripcorp/apollo>
- **Star**: 14.5k (927 stars this month)
- **介绍**: Apollo阿波罗是携程框架部门研发的分布式配置中心能够集中化管理应用不同环境、不同集群的配置配置修改后能够实时推送到应用端并且具备规范的权限、流程治理等特性适用于微服务配置管理场景。
### 15.arthas
- **Github地址**[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
- **star**: 13.5 k (933 stars this month)
- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
### 16.dubbo
- **Github地址**<https://github.com/apache/dubbo>
- **star**: 26.9 k (769 stars this month)
- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
### 17.DoraemonKit
- **Github地址** <https://github.com/didi/DoraemonKit>
- **Star**: 8.5k (909 stars this month)
- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
### 18.halo
- **Github地址** <https://github.com/halo-dev/halo>
- **Star**: 4.1k (829 stars this month)
- **介绍**: Halo 可能是最好的 Java 博客系统。
### 19.seata
- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
- **star**: 9.2 k (776 stars this month)
- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
### 20.hutool
- **Github地址**<https://github.com/looly/hutool>
- **star**: 5,3 k (812 stars this month)
- **介绍**: Hutool是一个Java工具包也只是一个工具包它帮助我们简化每一行代码减少每一个方法让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理后来慢慢积累并加入更多非业务相关功能并广泛学习其它开源项目精髓经过自己整理修改最终形成丰富的开源工具集。官网:<https://www.hutool.cn/>

View File

@ -0,0 +1,119 @@
### 1.CS-Notes
- **Github 地址**https://github.com/CyC2018/CS-Notes
- **Star**: 69.8k
- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
### 2.toBeTopJavaer
- **Github 地址:**[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
- **Star**: 4.7k
- **介绍**: To Be Top Javaer - Java工程师成神之路。
### 3.p3c
- **Github 地址:** [https://github.com/alibaba/p3c](https://github.com/alibaba/p3c)
- **Star**: 16.6k
- **介绍**: Alibaba Java Coding Guidelines pmd implements and IDE plugin。Eclipse 和 IDEA 上都有该插件,推荐使用!
### 4.SpringCloudLearning
- **Github 地址:** [https://github.com/forezp/SpringCloudLearning](https://github.com/forezp/SpringCloudLearning)
- **Star**: 8.7k
- **介绍**: 史上最简单的Spring Cloud教程源码。
### 5.dubbo
- **Github地址**<https://github.com/apache/dubbo>
- **star**: 27.6 k
- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
### 6.jeecg-boot
- **Github地址** [https://github.com/zhangdaiscott/jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)
- **star**: 3.3 k
- **介绍**: 一款基于代码生成器的JAVA快速开发平台全新架构前后端分离SpringBoot 2.xAnt Design&VueMybatisShiroJWT。强大的代码生成器让前后端代码一键生成无需写任何代码绝对是全栈开发福音 JeecgBoot的宗旨是提高UI能力的同时,降低前后分离的开发成本JeecgBoot还独创在线开发模式No代码概念一系列在线智能开发在线配置表单、在线配置报表、在线设计流程等等。
### 7.advanced-java
- **Github 地址**[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
- **Star**: 24.2k
- **介绍**: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
### 8.FEBS-Shiro
- **Github 地址**[https://github.com/wuyouzhuguli/FEBS-Shiro](https://github.com/wuyouzhuguli/FEBS-Shiro)
- **Star**: 2.6k
- **介绍**: Spring Boot 2.1.3Shiro1.4.0 & Layui 2.5.4 权限管理系统。预览地址http://49.234.20.223:8080/login。
### 9.SpringAll
- **Github 地址**: [https://github.com/wuyouzhuguli/SpringAll](https://github.com/wuyouzhuguli/SpringAll)
- **Star**: 5.4k
- **介绍**: 循序渐进学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2博客Spring系列源码。
### 10.JavaGuide
- **Github 地址**<https://github.com/Snailclimb/JavaGuide>
- **Star**: 47.2k
- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
### 11.vhr
- **Github 地址**[https://github.com/lenve/vhr](https://github.com/lenve/vhr)
- **Star**: 4.9k
- **介绍**: 微人事是一个前后端分离的人力资源管理系统项目采用SpringBoot+Vue开发。
### 12. tutorials
- **Github 地址**[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
- **star**: 15.4 k
- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - SpringSpring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 JavaJacksonHttpClientGuava。
### 13.EasyScheduler
- **Github 地址**[https://github.com/analysys/EasyScheduler](https://github.com/analysys/EasyScheduler)
- **star**: 1.1 k
- **介绍**: Easy Scheduler是一个分布式工作流任务调度系统主要解决“复杂任务依赖但无法直接监控任务健康状态”的问题。Easy Scheduler以DAG方式组装任务可以实时监控任务的运行状态。同时它支持重试重新运行等操作... 。https://analysys.github.io/easyscheduler_docs_cn/
### 14.thingsboard
- **Github 地址**[https://github.com/thingsboard/thingsboard](https://github.com/thingsboard/thingsboard)
- **star**: 3.7 k
- **介绍**: 开源物联网平台 - 设备管理,数据收集,处理和可视化。 [https://thingsboard.io](https://thingsboard.io/)
### 15.mall-learning
- **Github 地址**: [https://github.com/macrozheng/mall-learning](https://github.com/macrozheng/mall-learning)
- **star**: 0.6 k
- **介绍**: mall学习教程架构、业务、技术要点全方位解析。mall项目16k+star是一套电商系统使用现阶段主流技术实现。 涵盖了SpringBoot2.1.3、MyBatis3.4.6、Elasticsearch6.2.2、RabbitMQ3.7.15、Redis3.2、Mongodb3.2、Mysql5.7等技术采用Docker容器化部署。 https://github.com/macrozheng/mall
### 16. flink
- **Github地址**[https://github.com/apache/flink](https://github.com/apache/flink)
- **star**: 9.3 k
- **介绍**: Apache Flink是一个开源流处理框架具有强大的流和批处理功能。
### 17.spring-cloud-kubernetes
- **Github地址**[https://github.com/spring-cloud/spring-cloud-kubernetes](https://github.com/spring-cloud/spring-cloud-kubernetes)
- **star**: 1.4 k
- **介绍**: Kubernetes 集成 Spring Cloud Discovery Client, Configuration, etc...
### 18.springboot-learning-example
- **Github地址**[https://github.com/JeffLi1993/springboot-learning-example](https://github.com/JeffLi1993/springboot-learning-example)
- **star**: 10.0 k
- **介绍**: spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践。
### 19.canal
- **Github地址**[https://github.com/alibaba/canal](https://github.com/alibaba/canal)
- **star**: 9.3 k
- **介绍**: 阿里巴巴 MySQL binlog 增量订阅&消费组件。
### 20.react-native-device-info
- **Github地址**[https://github.com/react-native-community/react-native-device-info](https://github.com/react-native-community/react-native-device-info)
- **star**: 4.0 k
- **介绍**: React Native iOS和Android的设备信息。

View File

@ -0,0 +1,8 @@
- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2018-12.md)
- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-1.md)
- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-2.md)
- [2019 年 3 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-3.md)
- [2019 年 4 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-4.md)
- [2019 年 5 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-5.md)
- [2019 年 6 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-6.md)

45
docs/index.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaGuide</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/prism.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'JavaGuide',
repo: 'https://github.com/Snailclimb/JavaGuide',
maxLevel: 3,//最大支持渲染的标题层级
homepage: 'HomePage.md',
coverpage: true,//封面,_coverpage.md
auto2top: true,//切换页面后是否自动跳转到页面顶部
//ga: 'UA-138586553-1',
//logo: 'https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3logo-透明.png' ,
search: {
//maxAge: 86400000, // 过期时间单位毫秒默认一天
paths: 'auto',
placeholder: '搜索',
noData: '找不到结果',
// 搜索标题的最大程级, 1 - 6
depth: 3,
},
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<!--Java代码高亮-->
<script src="//unpkg.com/prismjs/components/prism-java.js"></script>
<!--全文搜索,直接用官方提供的无法生效-->
<script src="https://cdn.bootcss.com/docsify/4.5.9/plugins/search.min.js"></script>
<!--谷歌统计
<script src="//unpkg.com/docsify" data-ga="UA-138586553-1"></script>
<script src="//unpkg.com/docsify/lib/plugins/ga.js"></script>
-->
</body>
</html>

View File

@ -42,7 +42,7 @@
- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 - **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 - **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。
## 1. BIO (Blocking I/O) ## 1. BIO (Blocking I/O)
@ -73,7 +73,7 @@ BIO通信一请求一应答模型图如下(图源网络,原出处不明)
采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task该任务实现java.lang.Runnable接口投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task该任务实现java.lang.Runnable接口投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
伪异步I/O通信框架采用了线程池实现因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层然是同步阻塞的BIO模型因此无法从根本上解决问题。 伪异步I/O通信框架采用了线程池实现因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层然是同步阻塞的BIO模型因此无法从根本上解决问题。
### 1.3 代码示例 ### 1.3 代码示例

View File

@ -0,0 +1,383 @@
<!-- TOC -->
- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法)
- [Collections](#collections)
- [排序操作](#排序操作)
- [查找,替换操作](#查找替换操作)
- [同步控制](#同步控制)
- [Arrays类的常见操作](#arrays类的常见操作)
- [排序 : `sort()`](#排序--sort)
- [查找 : `binarySearch()`](#查找--binarysearch)
- [比较: `equals()`](#比较-equals)
- [填充 : `fill()`](#填充--fill)
- [转列表 `asList()`](#转列表-aslist)
- [转字符串 `toString()`](#转字符串-tostring)
- [复制 `copyOf()`](#复制-copyof)
<!-- /TOC -->
# Collections 工具类和 Arrays 工具类常见方法
## Collections
Collections 工具类常用方法:
1. 排序
2. 查找,替换操作
3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
### 排序操作
```java
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时将list后distance个元素整体移到前面。当distance为负数时将 list的前distance个元素整体移到后面。
```
**示例代码:**
```java
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始数组:");
System.out.println(arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
Collections.rotate(arrayList, 4);
System.out.println("Collections.rotate(arrayList, 4):");
System.out.println(arrayList);
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// void shuffle(List list),随机排序
Collections.shuffle(arrayList);
System.out.println("Collections.shuffle(arrayList):");
System.out.println(arrayList);
// void swap(List list, int i , int j),交换两个索引位置的元素
Collections.swap(arrayList, 2, 5);
System.out.println("Collections.swap(arrayList, 2, 5):");
System.out.println(arrayList);
// 定制排序的用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
```
### 查找,替换操作
```java
int binarySearch(List list, Object key)//对List进行二分查找返回索引注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序返回最大元素排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引找不到则返回-1类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
```
**示例代码:**
```java
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
ArrayList<Integer> arrayList2 = new ArrayList<Integer>();
arrayList2.add(-3);
arrayList2.add(-5);
arrayList2.add(7);
System.out.println("原始数组:");
System.out.println(arrayList);
System.out.println("Collections.max(arrayList):");
System.out.println(Collections.max(arrayList));
System.out.println("Collections.min(arrayList):");
System.out.println(Collections.min(arrayList));
System.out.println("Collections.replaceAll(arrayList, 3, -3):");
Collections.replaceAll(arrayList, 3, -3);
System.out.println(arrayList);
System.out.println("Collections.frequency(arrayList, -3):");
System.out.println(Collections.frequency(arrayList, -3));
System.out.println("Collections.indexOfSubList(arrayList, arrayList2):");
System.out.println(Collections.indexOfSubList(arrayList, arrayList2));
System.out.println("Collections.binarySearch(arrayList, 7):");
// 对List进行二分查找返回索引List必须是有序的
Collections.sort(arrayList);
System.out.println(Collections.binarySearch(arrayList, 7));
```
### 同步控制
Collections提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSetTreeSetArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。**
方法如下:
```java
synchronizedCollection(Collection<T> c) //返回指定 collection 支持的同步线程安全的collection。
synchronizedList(List<T> list)//返回指定列表支持的同步线程安全的List。
synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步线程安全的Map。
synchronizedSet(Set<T> s) //返回指定 set 支持的同步线程安全的set。
```
### Collections还可以设置不可变集合提供了如下三类方法
```java
emptyXxx(): 返回一个空的、不可变的集合对象此处的集合既可以是List也可以是Set还可以是Map。
singletonXxx(): 返回一个只包含指定对象只有一个或一个元素的不可变的集合对象此处的集合可以是ListSetMap。
unmodifiableXxx(): 返回指定集合对象的不可变视图此处的集合可以是ListSetMap。
上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。
```
**示例代码:**
```java
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
HashSet<Integer> integers1 = new HashSet<>();
integers1.add(1);
integers1.add(3);
integers1.add(2);
Map scores = new HashMap();
scores.put("语文" , 80);
scores.put("Java" , 82);
//Collections.emptyXXX();创建一个空的、不可改变的XXX对象
List<Object> list = Collections.emptyList();
System.out.println(list);//[]
Set<Object> objects = Collections.emptySet();
System.out.println(objects);//[]
Map<Object, Object> objectObjectMap = Collections.emptyMap();
System.out.println(objectObjectMap);//{}
//Collections.singletonXXX();
List<ArrayList<Integer>> arrayLists = Collections.singletonList(arrayList);
System.out.println(arrayLists);//[[-1, 3, 3, -5, 7, 4, -9, -7]]
//创建一个只有一个元素且不可改变的Set对象
Set<ArrayList<Integer>> singleton = Collections.singleton(arrayList);
System.out.println(singleton);//[[-1, 3, 3, -5, 7, 4, -9, -7]]
Map<String, String> nihao = Collections.singletonMap("1", "nihao");
System.out.println(nihao);//{1=nihao}
//unmodifiableXXX();创建普通XXX对象对应的不可变版本
List<Integer> integers = Collections.unmodifiableList(arrayList);
System.out.println(integers);//[-1, 3, 3, -5, 7, 4, -9, -7]
Set<Integer> integers2 = Collections.unmodifiableSet(integers1);
System.out.println(integers2);//[1, 2, 3]
Map<Object, Object> objectObjectMap2 = Collections.unmodifiableMap(scores);
System.out.println(objectObjectMap2);//{Java=82, 语文=80}
//添加出现异常java.lang.UnsupportedOperationException
// list.add(1);
// arrayLists.add(arrayList);
// integers.add(1);
```
## Arrays类的常见操作
1. 排序 : `sort()`
2. 查找 : `binarySearch()`
3. 比较: `equals()`
4. 填充 : `fill()`
5. 转列表: `asList()`
6. 转字符串 : `toString()`
7. 复制: `copyOf()`
### 排序 : `sort()`
```java
// *************排序 sort****************
int a[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
// sort(int[] a)方法按照数字顺序排列指定的数组。
Arrays.sort(a);
System.out.println("Arrays.sort(a):");
for (int i : a) {
System.out.print(i);
}
// 换行
System.out.println();
// sort(int[] a,int fromIndex,int toIndex)按升序排列数组的指定范围
int b[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
Arrays.sort(b, 2, 6);
System.out.println("Arrays.sort(b, 2, 6):");
for (int i : b) {
System.out.print(i);
}
// 换行
System.out.println();
int c[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
// parallelSort(int[] a) 按照数字顺序排列指定的数组(并行的)。同sort方法一样也有按范围的排序
Arrays.parallelSort(c);
System.out.println("Arrays.parallelSort(c)");
for (int i : c) {
System.out.print(i);
}
// 换行
System.out.println();
// parallelSort给字符数组排序sort也可以
char d[] = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
Arrays.parallelSort(d);
System.out.println("Arrays.parallelSort(d)");
for (char d2 : d) {
System.out.print(d2);
}
// 换行
System.out.println();
```
在做算法面试题的时候,我们还可能会经常遇到对字符串排序的情况,`Arrays.sort()` 对每个字符串的特定位置进行比较,然后按照升序排序。
```java
String[] strs = { "abcdehg", "abcdefg", "abcdeag" };
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
```
### 查找 : `binarySearch()`
```java
// *************查找 binarySearch()****************
char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
// 排序后再进行二分查找,否则找不到
Arrays.sort(e);
System.out.println("Arrays.sort(e)" + Arrays.toString(e));
System.out.println("Arrays.binarySearch(e, 'c')");
int s = Arrays.binarySearch(e, 'c');
System.out.println("字符c在数组的位置" + s);
```
### 比较: `equals()`
```java
// *************比较 equals****************
char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
char[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
/*
* 元素数量相同,并且相同位置的元素相同。 另外如果两个数组引用都是null则它们被认为是相等的 。
*/
// 输出true
System.out.println("Arrays.equals(e, f):" + Arrays.equals(e, f));
```
### 填充 : `fill()`
```java
// *************填充fill(批量初始化)****************
int[] g = { 1, 2, 3, 3, 3, 3, 6, 6, 6 };
// 数组中所有元素重新分配值
Arrays.fill(g, 3);
System.out.println("Arrays.fill(g, 3)");
// 输出结果333333333
for (int i : g) {
System.out.print(i);
}
// 换行
System.out.println();
int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
// 数组中指定范围元素重新分配值
Arrays.fill(h, 0, 2, 9);
System.out.println("Arrays.fill(h, 0, 2, 9);");
// 输出结果993333666
for (int i : h) {
System.out.print(i);
}
```
### 转列表 `asList()`
```java
// *************转列表 asList()****************
/*
* 返回由指定数组支持的固定大小的列表。
* 将返回的列表更改为“写入数组”。该方法作为基于数组和基于集合的API之间的桥梁与Collection.toArray()相结合 。
* 返回的列表是可序列化的并实现RandomAccess 。
* 此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表如下:
*/
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
System.out.println(stooges);
```
### 转字符串 `toString()`
```java
// *************转字符串 toString()****************
/*
* 返回指定数组的内容的字符串表示形式。
*/
char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]
```
### 复制 `copyOf()`
```java
// *************复制 copy****************
// copyOf 方法实现数组复制,h为数组6为复制的长度
int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
int i[] = Arrays.copyOf(h, 6);
System.out.println("Arrays.copyOf(h, 6);");
// 输出结果123333
for (int j : i) {
System.out.print(j);
}
// 换行
System.out.println();
// copyOfRange将指定数组的指定范围复制到新数组中
int j[] = Arrays.copyOfRange(h, 6, 11);
System.out.println("Arrays.copyOfRange(h, 6, 11)");
// 输出结果66600(h数组只有9个元素这里是从索引6到索引11复制所以不足的就为0)
for (int j2 : j) {
System.out.print(j2);
}
// 换行
System.out.println();
```

View File

@ -1,5 +1,110 @@
<!-- MarkdownTOC -->
# static 关键字 - [final,static,this,super 关键字总结](#finalstaticthissuper-关键字总结)
- [final 关键字](#final-关键字)
- [static 关键字](#static-关键字)
- [this 关键字](#this-关键字)
- [super 关键字](#super-关键字)
- [参考](#参考)
- [static 关键字详解](#static-关键字详解)
- [static 关键字主要有以下四种使用场景](#static-关键字主要有以下四种使用场景)
- [修饰成员变量和成员方法\(常用\)](#修饰成员变量和成员方法常用)
- [静态代码块](#静态代码块)
- [静态内部类](#静态内部类)
- [静态导包](#静态导包)
- [补充内容](#补充内容)
- [静态方法与非静态方法](#静态方法与非静态方法)
- [static{}静态代码块与{}非静态代码块\(构造代码块\)](#static静态代码块与非静态代码块构造代码块)
- [参考](#参考-1)
<!-- /MarkdownTOC -->
# final,static,this,super 关键字总结
## final 关键字
**final关键字主要用在三个地方变量、方法、类。**
1. **对于一个final变量如果是基本数据类型的变量则其数值一旦在初始化之后便不能更改如果是引用类型的变量则在对其初始化之后便不能再让其指向另一个对象。**
2. **当用final修饰一个类时表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。**
3. 使用final方法的原因有两个。第一个原因是把方法锁定以防任何继承类修改它的含义第二个原因是效率。在早期的Java实现版本中会将final方法转为内嵌调用。但是如果方法过于庞大可能看不到内嵌调用带来的任何性能提升现在的Java版本已经不需要使用final方法进行这些优化了。类中所有的private方法都隐式地指定为final。
## static 关键字
**static 关键字主要有以下四种使用场景:**
1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类不属于单个这个类的某个对象被类中所有对象共享可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
3. **静态内部类static修饰类的话只能修饰内部类** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用该引用是指向创建它的外围类但是静态内部类却没有。没有这个引用就意味着1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
4. **静态导包(用来导入类中的静态资源1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
## this 关键字
this关键字用于引用类的当前实例。 例如:
```java
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
```
在上面的示例中this关键字用于两个地方
- this.employees.length访问类Manager的当前实例的变量。
- this.report调用类Manager的当前实例的方法。
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
## super 关键字
super关键字用于从子类访问父类的变量和方法。 例如:
```java
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
```
在上面的例子中Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber` 方法。
**使用 this 和 super 要注意的问题:**
- 在构造器中使用 `super` 调用父类中的其他构造方法时该语句必须处于构造器的首行否则编译器会报错。另外this 调用本类中的其他构造方法时,也要放在首行。
- this、super不能用在static方法中。
**简单解释一下:**
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西而静态方法是属于类范畴的东西**
## 参考
- https://www.codejava.net/java-core/the-java-language/java-keywords
- https://blog.csdn.net/u013393958/article/details/79881037
# static 关键字详解
## static 关键字主要有以下四种使用场景 ## static 关键字主要有以下四种使用场景
@ -8,7 +113,7 @@
3. 修饰类(只能修饰内部类) 3. 修饰类(只能修饰内部类)
4. 静态导包(用来导入类中的静态资源1.5之后的新特性) 4. 静态导包(用来导入类中的静态资源1.5之后的新特性)
### 修饰成员变量和成员方法(常用) ### 修饰成员变量和成员方法(常用)
被 static 修饰的成员属于类不属于单个这个类的某个对象被类中所有对象共享可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 被 static 修饰的成员属于类不属于单个这个类的某个对象被类中所有对象共享可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
@ -93,7 +198,7 @@ static {
静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着: 静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
1. 它的创建是不需要依赖外围类的创建。 1. 它的创建是不需要依赖外围类的创建。
2. 它不能使用任何外围类的非static成员变量和方法。 2. 它不能使用任何外围类的非static成员变量和方法。
Example静态内部类实现单例模式 Example静态内部类实现单例模式
@ -137,11 +242,11 @@ import static java.lang.Math.;
换成import static java.lang.Math.max;具有一样的效果 换成import static java.lang.Math.max;具有一样的效果
public class Demo { public class Demo {
public static void main(String[] args) { public static void main(String[] args) {
int max = max(1,2); int max = max(1,2);
System.out.println(max); System.out.println(max);
} }
} }
``` ```
@ -180,7 +285,7 @@ class Foo {
- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 - 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 - 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
### static{}静态代码块与{}非静态代码块(构造代码块) ### static{}静态代码块与{}非静态代码块(构造代码块)
相同点: 都是在JVM加载类时且在构造方法执行之前执行在类中都可以定义多个定义多个时按定义的顺序执行一般在代码块中对一些static变量进行赋值。 相同点: 都是在JVM加载类时且在构造方法执行之前执行在类中都可以定义多个定义多个时按定义的顺序执行一般在代码块中对一些static变量进行赋值。

View File

@ -1,3 +1,5 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- MarkdownTOC --> <!-- MarkdownTOC -->
- [Servlet总结](#servlet总结) - [Servlet总结](#servlet总结)
@ -26,7 +28,7 @@
## Servlet总结 ## Servlet总结
在Java Web程序中**Servlet**主要负责接收用户请求**HttpServletRequest**,在**doGet()**,**doPost()**中做相应的处理,并将回应**HttpServletResponse**反馈给用户。Servlet可以设置初始化参数供Servlet内部使用。一个Servlet类只会有一个实例在它初始化时调用**init()方法**,销毁时调用**destroy()方法**。**Servlet需要在web.xml中配置**MyEclipse中创建Servlet会自动配置**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。 在Java Web程序中**Servlet**主要负责接收用户请求 `HttpServletRequest`,在`doGet()`,`doPost()`中做相应的处理,并将回应`HttpServletResponse`反馈给用户。**Servlet** 可以设置初始化参数供Servlet内部使用。一个Servlet类只会有一个实例在它初始化时调用`init()`方法,销毁时调用`destroy()`方法**。**Servlet需要在web.xml中配置MyEclipse中创建Servlet会自动配置**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。
## 阐述Servlet和CGI的区别? ## 阐述Servlet和CGI的区别?
@ -55,13 +57,13 @@
## Servlet接口中有哪些方法及Servlet生命周期探秘 ## Servlet接口中有哪些方法及Servlet生命周期探秘
Servlet接口定义了5个方法其中**前三个方法与Servlet生命周期相关** Servlet接口定义了5个方法其中**前三个方法与Servlet生命周期相关**
- **void init(ServletConfig config) throws ServletException** - `void init(ServletConfig config) throws ServletException`
- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException** - `void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException`
- **void destory()** - `void destory()`
- java.lang.String getServletInfo() - `java.lang.String getServletInfo()`
- ServletConfig getServletConfig() - `ServletConfig getServletConfig()`
**生命周期:** **Web容器加载Servlet并将其实例化后Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化请求到达时调用Servlet的**service()方法**service()方法会根据需要调用与请求对应的**doGet或doPost**等方法当服务器关闭或项目被卸载时服务器会将Servlet实例销毁此时会调用Servlet的**destroy()方法**。**init方法和destory方法只会执行一次service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源因此可以把初始化资源的代码放入init方法中销毁资源的代码放入destroy方法中这样就不需要每次处理客户端的请求都要初始化与销毁资源。 **生命周期:** **Web容器加载Servlet并将其实例化后Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化请求到达时调用Servlet的**service()方法**service()方法会根据需要调用与请求对应的**doGet或doPost**等方法当服务器关闭或项目被卸载时服务器会将Servlet实例销毁此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源因此可以把初始化资源的代码放入init方法中销毁资源的代码放入destroy方法中这样就不需要每次处理客户端的请求都要初始化与销毁资源。
参考《javaweb整合开发王者归来》P81 参考《javaweb整合开发王者归来》P81
@ -93,7 +95,7 @@ Form标签里的method的属性为get时调用doGet()为post时调用doPost()
**转发是服务器行为,重定向是客户端行为。** **转发是服务器行为,重定向是客户端行为。**
**转发Forword** **转发Forward**
通过RequestDispatcher对象的forwardHttpServletRequest request,HttpServletResponse response方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。 通过RequestDispatcher对象的forwardHttpServletRequest request,HttpServletResponse response方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。
```java ```java
request.getRequestDispatcher("login_success.jsp").forward(request, response); request.getRequestDispatcher("login_success.jsp").forward(request, response);
@ -143,13 +145,11 @@ Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm");
JSP是一种Servlet但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类接口Servlet的一个子类。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。 JSP是一种Servlet但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类接口Servlet的一个子类。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。
工程JspLoginDemo下有一个名为login.jsp的Jsp文件把工程第一次部署到服务器上后访问这个Jsp文件我们发现这个目录下多了下图这两个东东。 工程JspLoginDemo下有一个名为login.jsp的Jsp文件把工程第一次部署到服务器上后访问这个Jsp文件我们发现这个目录下多了下图这两个东东。
.class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候Tomcat将不再重新编译JSP文件而是直接调用class文件来响应客户端请求。 .class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候Tomcat将不再重新编译JSP文件而是直接调用class文件来响应客户端请求。
![JSP工作原理](https://user-gold-cdn.xitu.io/2018/3/31/1627bee073079a28?w=675&h=292&f=jpeg&s=133553) ![JSP工作原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/1.png)
由于JSP只会在客户端第一次请求的时候被编译 因此第一次请求JSP时会感觉比较慢之后就会感觉快很多。如果把服务器保存的class文件删除服务器也会重新编译JSP。 由于JSP只会在客户端第一次请求的时候被编译 因此第一次请求JSP时会感觉比较慢之后就会感觉快很多。如果把服务器保存的class文件删除服务器也会重新编译JSP。
开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP而不需要重启Tomcat。这种自动检测功能是默认开启的检测改动会消耗少量的时间在部署Web应用的时候可以在web.xml中将它关掉。 开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP而不需要重启Tomcat。这种自动检测功能是默认开启的检测改动会消耗少量的时间在部署Web应用的时候可以在web.xml中将它关掉。
参考《javaweb整合开发王者归来》P97 参考《javaweb整合开发王者归来》P97
## JSP有哪些内置对象、作用分别是什么 ## JSP有哪些内置对象、作用分别是什么
@ -195,31 +195,31 @@ JSP有9个内置对象
## request.getAttribute()和 request.getParameter()有何区别 ## request.getAttribute()和 request.getParameter()有何区别
**从获取方向来看:** **从获取方向来看:**
getParameter()是获取 POST/GET 传递的参数值; `getParameter()`是获取 POST/GET 传递的参数值;
getAttribute()是获取对象容器中的数据值; `getAttribute()`是获取对象容器中的数据值;
**从用途来看:** **从用途来看:**
getParameter用于客户端重定向时即点击了链接或提交按扭时传值用即用于在用表单或url重定向传值时接收数据用。 `getParameter()`用于客户端重定向时即点击了链接或提交按扭时传值用即用于在用表单或url重定向传值时接收数据用。
getAttribute用于服务器端重定向时即在 sevlet 中使用了 forward 函数,或 struts 中使用了 `getAttribute()` 用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了
mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。 mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。
另外,可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。 另外,可以用 `setAttribute()`,`getAttribute()` 发送接收对象.而 `getParameter()` 显然只能传字符串。
setAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去当你的页面服务器重定向到另一个页面时应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值当然这种方法可以传对象。session也一样只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时取得你设在表单或 url 重定向时的值。 `setAttribute()` 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样`getAttribute()`就能取得你所设下的值当然这种方法可以传对象。session也一样只是对象在内存中的生命周期不一样而已。`getParameter()`只是应用服务器在分析你送上来的 request页面的文本时取得你设在表单或 url 重定向时的值。
**总结:** **总结:**
getParameter 返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等) `getParameter()`返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等)
getAttribute 返回的是Object需进行转换,可用setAttribute 设置成任意对象,使用很灵活,可随时用 `getAttribute()`返回的是Object需进行转换,可用`setAttribute()`设置成任意对象,使用很灵活,可随时用
## include指令include的行为的区别 ## include指令include的行为的区别
**include指令** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分会被同时编译执行。 语法格式如下: **include指令** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分会被同时编译执行。 语法格式如下:
<%@ include file="文件相对 url 地址" %> <%@ include file="文件相对 url 地址" %>
i**nclude动作** <jsp:include>动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下: i**nclude动作** `<jsp:include>`动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下:
<jsp:include page="相对 URL 地址" flush="true" /> <jsp:include page="相对 URL 地址" flush="true" />
## JSP九大内置对象七大动作三大指令 ## JSP九大内置对象七大动作三大指令
@ -232,11 +232,9 @@ JSP中的四种作用域包括page、request、session和application具体来
- **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 - **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- **application**代表与整个Web应用程序相关的对象和属性它实质上是跨越整个Web应用程序包括多个页面、请求和会话的一个全局作用域。 - **application**代表与整个Web应用程序相关的对象和属性它实质上是跨越整个Web应用程序包括多个页面、请求和会话的一个全局作用域。
## 如何实现JSP或Servlet的单线程模式 ## 如何实现JSP或Servlet的单线程模式
对于JSP页面可以通过page指令进行设置。 对于JSP页面可以通过page指令进行设置。
<%@page isThreadSafe=”false”%> `<%@page isThreadSafe=”false”%>`
对于Servlet可以让自定义的Servlet实现SingleThreadModel标识接口。 对于Servlet可以让自定义的Servlet实现SingleThreadModel标识接口。
@ -294,12 +292,20 @@ if(cookies !=null){
在所有会话跟踪技术中HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession通过HttpSession的setAttribute方法可以将一个值放在HttpSession中通过调用 HttpSession对象的getAttribute方法同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是HttpSession放在服务器的内存中因此不要将过大的对象放在里面即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象这个对象最好实现了 Serializable接口这样Servlet容器在必要的时候可以将其序列化到文件中否则在序列化时就会出现异常。 在所有会话跟踪技术中HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession通过HttpSession的setAttribute方法可以将一个值放在HttpSession中通过调用 HttpSession对象的getAttribute方法同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是HttpSession放在服务器的内存中因此不要将过大的对象放在里面即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象这个对象最好实现了 Serializable接口这样Servlet容器在必要的时候可以将其序列化到文件中否则在序列化时就会出现异常。
## Cookie和Session的的区别 ## Cookie和Session的的区别
1. 由于HTTP协议是无状态的协议所以服务端需要记录用户的状态时就需要用某种机制来识具体的用户这个机制就是Session.典型的场景比如购物车当你点击下单按钮时由于HTTP协议无状态所以并不知道是哪个用户操作的所以服务端要为特定的用户创建了特定的Session用用于标识这个用户并且跟踪用户这样才知道购物车里面有几本书。这个Session是保存在服务端的有一个唯一标识。在服务端保存Session的方法很多内存、数据库、文件都有。集群的时候也要考虑Session的转移在大型的网站一般会有专门的Session服务器集群用来保存用户会话这个时候 Session 信息都是放在内存的使用一些缓存服务比如Memcached之类的来放 Session。 Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式但是两者的应用场景不太一样。
2. 思考一下服务端如何识别特定的客户这个时候Cookie就登场了。每次HTTP请求的时候客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的第一次创建Session的时候服务端会在HTTP协议中告诉客户端需要在 Cookie 里面记录一个Session ID以后每次请求把这个会话ID发送到服务器我就知道你是谁了。有人问如果客户端的浏览器禁用了 Cookie 怎么办一般这种情况下会使用一种叫做URL重写的技术来进行会话跟踪即每次HTTP交互URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
3. Cookie其实还可以用在一些方便用户的场景下设想你某次登陆过一个网站下次登录的时候不想再次输入账号了怎么办这个信息可以写到Cookie里面访问网站的时候网站页面的脚本可以读取这个信息就自动帮你把用户名给填了能够方便一下用户。这也是Cookie名称的由来给用户的一点甜头。所以总结一下Session是在服务端保存的一个数据结构用来跟踪用户的状态这个数据可以保存在集群、数据库、文件中Cookie是客户端保存用户信息的一种机制用来记录用户的一些信息也是实现Session的一种方式。
参考: **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
https://www.zhihu.com/question/19786827/answer/28752144 Cookie 数据保存在客户端(浏览器端)Session 数据保存在服务器端。
《javaweb整合开发王者归来》P158 Cookie和Session的比较 Cookie 存储在客户端中而Session存储在服务器上相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -27,12 +27,12 @@
**1 按操作方式分类结构图:** **1 按操作方式分类结构图:**
![按操作方式分类结构图:](https://user-gold-cdn.xitu.io/2018/5/16/16367d4fd1ce1b46?w=720&h=1080&f=jpeg&s=69522) ![IO-操作方式分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作方式分类.png)
**2按操作对象分类结构图** **2按操作对象分类结构图**
![按操作对象分类结构图](https://user-gold-cdn.xitu.io/2018/5/16/16367d673b0e268d?w=720&h=535&f=jpeg&s=46081) ![IO-操作对象分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作对象分类.png)
### [ java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105) ### [ java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105)
1. **IO流的分类** 1. **IO流的分类**

View File

@ -1,75 +1,70 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
<!-- MarkdownTOC -->
- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别)
- [面向过程](#面向过程) - [面向过程](#面向过程)
- [面向对象](#面向对象) - [面向对象](#面向对象)
- [2. Java 语言有哪些特点](#2-java-语言有哪些特点) - [2. Java 语言有哪些特点?](#2-java-语言有哪些特点)
- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答) - [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答)
- [JVM](#jvm) - [JVM](#jvm)
- [JDK 和 JRE](#jdk-和-jre) - [JDK 和 JRE](#jdk-和-jre)
- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比) - [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比)
- [5. Java和C++的区别](#5-java和c的区别) - [5. Java和C++的区别?](#5-java和c的区别)
- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) - [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同)
- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) - [7. Java 应用程序与小程序之间有那些差别?](#7-java-应用程序与小程序之间有那些差别)
- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) - [8. 字符型常量和字符串常量的区别?](#8-字符型常量和字符串常量的区别)
- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) - [9. 构造器 Constructor 是否可被 override?](#9-构造器-constructor-是否可被-override)
- [10. 重载和重写的区别](#10-重载和重写的区别) - [10. 重载和重写的区别](#10-重载和重写的区别)
- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态) - [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态)
- [封装](#封装) - [封装](#封装)
- [继承](#继承) - [继承](#继承)
- [多态](#多态) - [多态](#多态)
- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) - [12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的)
- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) - [13. 自动装箱与拆箱](#13-自动装箱与拆箱)
- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) - [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的)
- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) - [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用)
- [16. import java和javax有什么区别](#16-import-java和javax有什么区别) - [16. import java和javax有什么区别](#16-import-java和javax有什么区别)
- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) - [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么)
- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) - [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些)
- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同) - [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同)
- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么) - [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么)
- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) - [21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么)
- [22. 构造方法有哪些特性](#22-构造方法有哪些特性) - [22. 构造方法有哪些特性](#22-构造方法有哪些特性)
- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) - [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同)
- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) - [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同)
- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) - [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是)
- [26. == 与 equals\(重要\)](#26--与-equals重要) - [26. == 与 equals(重要)](#26--与-equals重要)
- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要) - [27. hashCode 与 equals (重要)](#27-hashcode-与-equals-重要)
- [hashCode介绍](#hashcode介绍) - [hashCode介绍](#hashcode介绍)
- [为什么要有 hashCode](#为什么要有-hashcode) - [为什么要有 hashCode](#为什么要有-hashcode)
- [hashCode与equals的相关规定](#hashcode与equals的相关规定) - [hashCode与equals的相关规定](#hashcode与equals的相关规定)
- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) - [28. 为什么Java中只有值传递](#28-为什么java中只有值传递)
- [29. 简述线程程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么) - [29. 简述线程程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程程序进程的基本概念以及他们之间关系是什么)
- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态) - [30. 线程有哪些基本状态?](#30-线程有哪些基本状态)
- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) - [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结)
- [32 Java 中的异常处理](#32-java-中的异常处理) - [32 Java 中的异常处理](#32-java-中的异常处理)
- [Java异常类层次结构图](#java异常类层次结构图) - [Java异常类层次结构图](#java异常类层次结构图)
- [Throwable类常用方法](#throwable类常用方法) - [Throwable类常用方法](#throwable类常用方法)
- [异常处理总结](#异常处理总结) - [异常处理总结](#异常处理总结)
- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办) - [33 Java序列化中如果有些字段不想进行序列化,怎么办?](#33-java序列化中如果有些字段不想进行序列化怎么办)
- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法) - [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法)
- [35 Java 中 IO 流分为几种?BIO,NIO,AIO 有什么区别?](#35-java-中-io-流分为几种bionioaio-有什么区别)
- [java 中 IO 流分为几种?](#java-中-io-流分为几种)
- [BIO,NIO,AIO 有什么区别?](#bionioaio-有什么区别)
- [36. 常见关键字总结:static,final,this,super](#36-常见关键字总结staticfinalthissuper)
- [37. Collections 工具类和 Arrays 工具类常见方法总结](#37-collections-工具类和-arrays-工具类常见方法总结)
- [参考](#参考) - [参考](#参考)
- [公众号](#公众号)
<!-- /MarkdownTOC --> <!-- /TOC -->
## 1. 面向对象和面向过程的区别 ## 1. 面向对象和面向过程的区别
### 面向过程 - **面向过程** **面向过程性能比面向对象高。** 因为类调用时需要实例化开销比较大比较消耗资源所以当性能是最重要的考量因素的时候比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是**面向过程没有面向对象易维护、易复用、易扩展。**
- **面向对象** **面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。
**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发性能是最重要的因素。 ## 2. Java 语言有哪些特点?
**缺点:** 没有面向对象易维护、易复用、易扩展
### 面向对象
**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
**缺点:** 性能比面向过程低
## 2. Java 语言有哪些特点
1. 简单易学; 1. 简单易学;
2. 面向对象(封装,继承,多态); 2. 面向对象(封装,继承,多态);
@ -88,17 +83,19 @@ Java虚拟机JVM是运行 Java 字节码的虚拟机。JVM有针对不同
**什么是字节码?采用字节码的好处是什么?** **什么是字节码?采用字节码的好处是什么?**
> 在 Java 中JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件它不面向任何特定的处理器只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不对一种特定的机器因此Java程序无须重新编译便可在多种不同的计算机上运行。 > 在 Java 中JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件它不面向任何特定的处理器只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不对一种特定的机器因此Java程序无须重新编译便可在多种不同操作系统的计算机上运行。
**Java 程序从源代码到运行一般有下面3步** **Java 程序从源代码到运行一般有下面3步**
![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) ![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png)
我们需要格外注意的是 .class->机器码 这一步。在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
> HotSpot采用了惰性评估(Lazy Evaluation)的做法根据二八定律消耗大部分系统资源的只有那一小部分的代码热点代码而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化因此执行的次数越多它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation)它是直接将字节码编译成机器码这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 > HotSpot采用了惰性评估(Lazy Evaluation)的做法根据二八定律消耗大部分系统资源的只有那一小部分的代码热点代码而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化因此执行的次数越多它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation)它是直接将字节码编译成机器码这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
总结Java虚拟机JVM是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现WindowsLinuxmacOS目的是使用相同的字节码它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 **总结:**
Java虚拟机JVM是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现WindowsLinuxmacOS目的是使用相同的字节码它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
### JDK 和 JRE ### JDK 和 JRE
@ -110,7 +107,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
## 4. Oracle JDK 和 OpenJDK 的对比 ## 4. Oracle JDK 和 OpenJDK 的对比
可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异下面通过我通过我收集到一些资料对你解答这个被很多人忽视的问题。 可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
对于Java 7没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外OpenJDK被选为Java 7的参考实现由Oracle工程师维护。关于JVMJDKJRE和OpenJDK之间的区别Oracle博客帖子在2012年有一个更详细的答案 对于Java 7没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外OpenJDK被选为Java 7的参考实现由Oracle工程师维护。关于JVMJDKJRE和OpenJDK之间的区别Oracle博客帖子在2012年有一个更详细的答案
@ -118,7 +115,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
> >
> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建只添加了几个部分例如部署代码其中包括Oracle的Java插件和Java WebStart的实现以及一些封闭的源代码派对组件如图形光栅化器一些开源的第三方组件如Rhino以及一些零碎的东西如附加文档或第三方字体。展望未来我们的目的是开源Oracle JDK的所有部分除了我们考虑商业功能的部分。 > 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建只添加了几个部分例如部署代码其中包括Oracle的Java插件和Java WebStart的实现以及一些封闭的源代码派对组件如图形光栅化器一些开源的第三方组件如Rhino以及一些零碎的东西如附加文档或第三方字体。展望未来我们的目的是开源Oracle JDK的所有部分除了我们考虑商业功能的部分。
总结: **总结:**
1. Oracle JDK版本将每三年发布一次而OpenJDK版本每三个月发布一次 1. Oracle JDK版本将每三年发布一次而OpenJDK版本每三个月发布一次
2. OpenJDK 是一个参考模型并且是完全开源的而Oracle JDK是OpenJDK的一个实现并不是完全开源的 2. OpenJDK 是一个参考模型并且是完全开源的而Oracle JDK是OpenJDK的一个实现并不是完全开源的
@ -127,7 +124,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
5. Oracle JDK不会为即将发布的版本提供长期支持用户每次都必须通过更新到最新版本获得支持来获取最新版本 5. Oracle JDK不会为即将发布的版本提供长期支持用户每次都必须通过更新到最新版本获得支持来获取最新版本
6. Oracle JDK根据二进制代码许可协议获得许可而OpenJDK根据GPL v2许可获得许可。 6. Oracle JDK根据二进制代码许可协议获得许可而OpenJDK根据GPL v2许可获得许可。
## 5. Java和C++的区别 ## 5. Java和C++的区别?
我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀没办法就算没学过C++,也要记下来! 我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀没办法就算没学过C++,也要记下来!
@ -137,32 +134,31 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
- Java 有自动内存管理机制,不需要程序员手动释放无用内存 - Java 有自动内存管理机制,不需要程序员手动释放无用内存
## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同 ## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?
一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。
## 7. Java 应用程序与小程序之间有那些差别 ## 7. Java 应用程序与小程序之间有那些差别?
简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。 简单说应用程序是从主线程启动(也就是 `main()` 方法)。applet 小程序没有 `main()` 方法,主要是嵌在浏览器页面上运行(调用`init()`或者`run()`来启动),嵌入浏览器这点跟 flash 的小游戏类似。
## 8. 字符型常量和字符串常量的区别 ## 8. 字符型常量和字符串常量的区别?
1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符
2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) 2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) 3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**)
> java编程思想第四版2.2.2节 > java编程思想第四版2.2.2节
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg)
## 9. 构造器 Constructor 是否可被 override ## 9. 构造器 Constructor 是否可被 override?
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override重写,但是可以 overload重载,所以你可以看到一个类中有多个构造函数的情况。 在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override重写,但是可以 overload重载,所以你可以看到一个类中有多个构造函数的情况。
## 10. 重载和重写的区别 ## 10. 重载和重写的区别
**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    - **重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。   
- **重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
## 11. Java 面向对象编程三大特性: 封装 继承 多态 ## 11. Java 面向对象编程三大特性: 封装 继承 多态
@ -176,7 +172,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
**关于继承如下 3 点请记住:** **关于继承如下 3 点请记住:**
1. 子类拥有父类非 private 的属性和方法 1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**
2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
@ -186,12 +182,11 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
在Java中有两种形式可以实现多态继承多个子类对同一方法的重写和接口实现接口并覆盖接口中同一方法 在Java中有两种形式可以实现多态继承多个子类对同一方法的重写和接口实现接口并覆盖接口中同一方法
## 12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的 ## 12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
**可变性** **可变性**
 
简单的来说String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 简单的来说String 类中使用 final 关键字修饰字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
@ -211,52 +206,53 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
**线程安全性** **线程安全性**
String 中的对象是不可变的也就可以理解为常量线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 String 中的对象是不可变的也就可以理解为常量线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 
  
**性能** **性能**
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
**对于三者使用的总结:** **对于三者使用的总结:**
1. 操作少量的数据 = String
2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder 1. 操作少量的数据: 适用String
3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer 2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder
3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer
## 13. 自动装箱与拆箱 ## 13. 自动装箱与拆箱
**装箱**:将基本类型用它们对应的引用类型包装起来;
**拆箱**:将包装类型转换为基本数据类型; - **装箱**:将基本类型用它们对应的引用类型包装起来;
- **拆箱**:将包装类型转换为基本数据类型;
## 14. 在一个静态方法内调用一个非静态成员为什么是非法的 ## 14. 在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用 ## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用
 Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
 
## 16. import java和javax有什么区别
刚开始的时候 JavaAPI 所必需的包是 java 开头的包javax 当时只是扩展 API 包来说使用。然而随着时间的推移javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 Java 程序在执行子类的构造方法之前,如果没有用 `super() `来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 `super() `来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
 
## 16. import java和javax有什么区别
刚开始的时候 JavaAPI 所必需的包是 java 开头的包javax 当时只是扩展 API 包来使用。然而随着时间的推移javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
所以实际上java和javax没有区别。这都是一个名字。 所以实际上java和javax没有区别。这都是一个名字。
## 17. 接口和抽象类的区别是什么 ## 17. 接口和抽象类的区别是什么
1. 接口的方法默认是 public所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 1. 接口的方法默认是 public所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 2. 接口中除了static、final变量不能有其他变量而抽象类中则不一定。
3. 一个类可以实现多个接口,但最多只能实现一个抽象类 3. 一个类可以实现多个接口但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 4. 接口方法默认修饰符是public抽象方法可以有public、protected和default这些修饰符抽象方法就是为了被重写所以不能使用private关键字修饰
5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
备注:在JDK8中接口也可以定义静态方法可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口接口中定义了一样的默认方法必须重写不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)) 备注在JDK8中接口也可以定义静态方法可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口接口中定义了一样的默认方法必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146))
## 18. 成员变量与局部变量的区别有那些 ## 18. 成员变量与局部变量的区别有那些
1. 从语法形式上看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰 1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰
2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存
3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。 4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? ## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
@ -266,21 +262,21 @@ new运算符new创建对象实例对象实例在堆内存中对象
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么? ## 21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
## 22. 构造方法有哪些特性 ## 22. 构造方法有哪些特性
1. 名字与类名相同 1. 名字与类名相同
2. 没有返回值但不能用void声明构造函数 2. 没有返回值但不能用void声明构造函数
3. 生成类的对象时自动执行,无需调用。 3. 生成类的对象时自动执行,无需调用。
## 23. 静态方法和实例方法有何不同 ## 23. 静态方法和实例方法有何不同
1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. 2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
## 24. 对象的相等与指向他们的引用相等,两者有什么不同? ## 24. 对象的相等与指向他们的引用相等,两者有什么不同?
@ -292,11 +288,11 @@ new运算符new创建对象实例对象实例在堆内存中对象
## 26. == 与 equals(重要) ## 26. == 与 equals(重要)
**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
- 情况1类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 - 情况1类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - 情况2类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
**举个例子:** **举个例子:**
@ -322,11 +318,10 @@ public class test1 {
``` ```
**说明:** **说明:**
- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 - String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
## 27. hashCode 与 equals (重要) ## 27. hashCode 与 equals (重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么为什么重写equals时必须重写hashCode方法 面试官可能会问你:“你重写过 hashcode 和 equals 么为什么重写equals时必须重写hashCode方法
@ -338,12 +333,9 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返
### 为什么要有 hashCode ### 为什么要有 hashCode
**我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode** 当你把对象加入 HashSet 时HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同HashSet 就不会让其加入操作成功。如果不同的话就会重新散列到其他位置。摘自我的Java启蒙书《Head first java》第二版。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode** 通过我们可以看出:`hashCode()` 的作用就是**获取哈希码**也称为散列码它实际上是返回一个int整数。这个**哈希码的作用**是确定该对象在哈希表中的索引位置。**`hashCode() `在散列表中才有用,在其它情况下没用。**在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
当你把对象加入 HashSet 时HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals方法来检查 hashcode 相等的对象是否真的相同。如果两者相同HashSet 就不会让其加入操作成功。如果不同的话就会重新散列到其他位置。摘自我的Java启蒙书《Head first java》第二版。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
### hashCode与equals的相关规定 ### hashCode与equals的相关规定
@ -353,13 +345,15 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返
4. **因此equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** 4. **因此equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) 5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
推荐阅读:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
## 28. 为什么Java中只有值传递
[为什么Java中只有值传递](https://github.com/Snailclimb/Java-Guide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/%E6%9C%80%E6%9C%80%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84Java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md)
## 29. 简述线程,程序,进程的基本概念.以及他们之间关系是什么? ## 28. 为什么Java中只有值传递
[为什么Java中只有值传递](https://github.com/Snailclimb/JavaGuide/blob/master/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md)
## 29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 **线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
@ -370,16 +364,26 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返
## 30. 线程有哪些基本状态? ## 30. 线程有哪些基本状态?
参考《Java 并发编程艺术》4.1.4节。 Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态图源《Java 并发编程艺术》4.1.4节)。
Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态。
![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png) ![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示: 线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示图源《Java 并发编程艺术》4.1.4节)
![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png) ![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)
由上图可以看出:
线程创建之后它将处于 **NEW新建** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY可运行** 状态。可运行状态的线程获得了 cpu 时间片timeslice后就处于 **RUNNING运行** 状态。
> 操作系统隐藏 Java虚拟机JVM中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/)[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE运行中** 状态 。
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)
当线程执行 `wait()`方法之后,线程进入 **WAITING等待**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED阻塞** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED终止** 状态。
## 31 关于 final 关键字的一些总结 ## 31 关于 final 关键字的一些总结
final关键字主要用在三个地方变量、方法、类。 final关键字主要用在三个地方变量、方法、类。
@ -393,7 +397,10 @@ final关键字主要用在三个地方变量、方法、类。
### Java异常类层次结构图 ### Java异常类层次结构图
![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png) ![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png)
在 Java 中所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable 有两个重要的子类:**Exception异常** 和 **Error错误** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
在 Java 中所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable 有两个重要的子类:**Exception异常** 和 **Error错误** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
**Error错误:是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVMJava 虚拟机出现的问题。例如Java虚拟机运行错误Virtual MachineError当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时Java虚拟机JVM一般会选择线程终止。 **Error错误:是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVMJava 虚拟机出现的问题。例如Java虚拟机运行错误Virtual MachineError当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时Java虚拟机JVM一般会选择线程终止。
@ -412,18 +419,36 @@ final关键字主要用在三个地方变量、方法、类。
### 异常处理总结 ### 异常处理总结
- try 块用于捕获异常。其后可接零个或多个catch块如果没有catch块则必须跟一个finally块。 - **try 块:**用于捕获异常。其后可接零个或多个catch块如果没有catch块则必须跟一个finally块。
- catch 块用于处理try捕获到的异常。 - **catch 块:**用于处理try捕获到的异常。
- finally 块无论是否捕获或处理异常finally块里的语句都会被执行。当在try块或catch块中遇到return语句时finally语句块将在方法返回之前被执行。 - **finally 块:**无论是否捕获或处理异常finally块里的语句都会被执行。当在try块或catch块中遇到return语句时finally语句块将在方法返回之前被执行。
**在以下4种特殊情况下finally块不会被执行** **在以下4种特殊情况下finally块不会被执行**
1. 在finally语句块中发生了异常。 1. 在finally语句块第一行发生了异常。 因为在其他行finally块还是会得到执行
2. 在前面的代码中用了System.exit()退出程序。 2. 在前面的代码中用了System.exit(int)退出程序。 exit是带参函数 若该语句在异常语句之后finally会执行
3. 程序所在的线程死亡。 3. 程序所在的线程死亡。
4. 关闭CPU。 4. 关闭CPU。
## 33 Java序列化中如果有些字段不想进行序列化 怎么办 下面这部分内容来自issue:<https://github.com/Snailclimb/JavaGuide/issues/190>
**注意:** 当try语句和finally语句中都有return语句时在方法返回之前finally语句的内容将被执行并且finally语句的返回值将会覆盖原始的返回值。如下
```java
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
```
如果调用 `f(2)`返回值将是0因为finally语句的返回值覆盖了try语句块的返回值。
## 33 Java序列化中如果有些字段不想进行序列化怎么办
对于不想进行序列化的变量使用transient关键字修饰。 对于不想进行序列化的变量使用transient关键字修饰。
@ -446,8 +471,54 @@ BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine(); String s = input.readLine();
``` ```
## 35 Java 中 IO 流分为几种?BIO,NIO,AIO 有什么区别?
### java 中 IO 流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类这些类看上去很杂乱但实际上很有规则而且彼此之间存在非常紧密的联系 Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:
![IO-操作方式分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作方式分类.png)
按操作对象分类结构图:
![IO-操作对象分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-操作对象分类.png)
### BIO,NIO,AIO 有什么区别?
- **BIO (Blocking I/O):** 同步阻塞I/O模式数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高小于单机1000的情况下这种模型是比较不错的可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- **NIO (New I/O):** NIO是一种同步非阻塞的I/O模型在Java 1.4 中引入了NIO框架对应 java.nio 包,提供了 Channel , SelectorBuffer等抽象。NIO中的N可以理解为Non-blocking不单纯是New。它支持面向缓冲的基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket``ServerSocket` 相对应的 `SocketChannel``ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样比较简单但是性能和可靠性都不好非阻塞模式正好与之相反。对于低负载、低并发的应用程序可以使用同步阻塞I/O来提升开发速率和更好的维护性对于高负载、高并发的网络应用应使用 NIO 的非阻塞模式来开发
- **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会堵塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作IO操作本身是同步的。查阅网上相关资料我发现就目前来说 AIO 的应用还不是很广泛Netty 之前也尝试使用过 AIO不过又放弃了。
## 36. 常见关键字总结:static,final,this,super
详见笔主的这篇文章: <https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/finalstaticthissuper.md>
## 37. Collections 工具类和 Arrays 工具类常见方法总结
详见笔主的这篇文章: <https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/Arrays,CollectionsCommonMethods.md>
## 参考 ## 参考
- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre - https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
- https://www.educba.com/oracle-vs-openjdk/ - https://www.educba.com/oracle-vs-openjdk/
- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top - https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

374
docs/java/Java疑难点.md Normal file
View File

@ -0,0 +1,374 @@
<!-- TOC -->
- [1. 基础](#1-基础)
- [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
- [1.2. 整形包装类值的比较](#12-整形包装类值的比较)
- [1.3. BigDecimal](#13-bigdecimal)
- [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
- [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
- [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数)
- [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项)
- [1.3.5. 总结](#135-总结)
- [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准)
- [2. 集合](#2-集合)
- [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南)
- [2.1.1. 简介](#211-简介)
- [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述)
- [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结)
- [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist)
- [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组)
- [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作)
<!-- /TOC -->
# 1. 基础
## 1.1. 正确使用 equals 方法
Object的equals方法容易抛空指针异常应使用常量或确定有值的对象来调用 equals。
举个例子:
```java
// 不能使用一个值为null的引用类型变量来调用非静态方法否则会抛出异常
String str = null;
if (str.equals("SnailClimb")) {
...
} else {
..
}
```
运行上面的程序会抛出空指针异常但是我们把第二行的条件判断语句改为下面这样的话就不会抛出空指针异常else 语句块得到执行。:
```java
"SnailClimb".equals(str);// false
```
不过更推荐使用 `java.util.Objects#equals`(JDK7 引入的工具类)。
```java
Objects.equals(null,"SnailClimb");// false
```
我们看一下`java.util.Objects#equals`的源码就知道原因了。
```java
public static boolean equals(Object a, Object b) {
// 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
return (a == b) || (a != null && a.equals(b));
}
```
**注意:**
Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894)
- 每种原始类型都有默认值一样如int默认值为 0boolean 的默认值为 falsenull 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
- 可以使用==或者!=操作来比较null值但是不能使用其他算法或者逻辑操作。在Java中`null==null`将返回true。
- 不能使用一个值为null的引用类型变量来调用非静态方法否则会抛出异常
## 1.2. 整形包装类值的比较
所有整形包装类对象值得比较必须使用equals方法。
先看下面这个例子:
```java
Integer x = 3;
Integer y = 3;
System.out.println(x == y);// true
Integer a = new Integer(3);
Integer b = new Integer(3);
System.out.println(a == b);//false
System.out.println(a.equals(b));//true
```
当使用自动装箱方式创建一个Integer对象时当数值在-128 ~127时会将创建的 Integer 对象缓存起来当下次再出现该数值时直接从缓存中取出对应的Integer对象。所以上述代码中x和y引用的是相同的Integer对象。
**注意:**如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。
## 1.3. BigDecimal
### 1.3.1. BigDecimal 的用处
《阿里巴巴Java开发手册》中提到**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
```java
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
```
具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**
```java
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true
```
### 1.3.2. BigDecimal 的大小比较
`a.compareTo(b)` : 返回 -1 表示小于0 表示 等于, 1表示 大于。
```java
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
```
### 1.3.3. BigDecimal 保留几位小数
通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种不需要记IDEA会提示。
```java
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255
```
### 1.3.4. BigDecimal 的使用注意事项
注意我们在使用BigDecimal时为了防止精度丢失推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
![《阿里巴巴Java开发手册》对这部分BigDecimal的描述](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/BigDecimal.png)
### 1.3.5. 总结
BigDecimal 主要用来操作浮点数BigInteger 主要用来操作大整数(超过 long 类型)。
BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
## 1.4. 基本数据类型与包装数据类型的使用标准
Reference:《阿里巴巴Java开发手册》
- 【强制】所有的 POJO 类属性必须使用包装数据类型。
- 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
- 【推荐】所有的局部变量使用基本数据类型。
比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或
者入库检查,都由使用者来保证。
**正例** : 数据库的查询结果可能是 null因为自动拆箱用基本数据类型接收有 NPE 风险。
**反例** : 比如显示成交总额涨跌情况,即正负 x%x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
# 2. 集合
## 2.1. Arrays.asList()使用指南
最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还是特别全面。所以,自己对于这块小知识点进行了简单的总结。
### 2.1.1. 简介
`Arrays.asList()`在平时开发中还是比较常见的我们可以使用它将一个数组转换为一个List集合。
```java
String[] myArray = { "Apple", "Banana", "Orange" }
List<String> myList = Arrays.asList(myArray);
//上面两个语句等价于下面一条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");
```
JDK 源码对于这个方法的说明:
```java
/**
*返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
*/
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
```
### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述
`Arrays.asList()`将数组转换为集合后,底层其实还是数组《阿里巴巴Java 开发手册》对于这个方法有如下描述:
![阿里巴巴Java开发手-Arrays.asList()方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里巴巴Java开发手-Arrays.asList()方法.png)
### 2.1.3. 使用时的注意事项总结
**传递的数组必须是对象数组,而不是基本类型。**
`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。
```java
int[] myArray = { 1, 2, 3 };
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//数组地址值
System.out.println(myList.get(1));//报错ArrayIndexOutOfBoundsException
int [] array=(int[]) myList.get(0);
System.out.println(array[0]);//1
```
当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素而是数组对象本身此时List 的唯一元素就是这个数组,这也就解释了上面的代码。
我们使用包装类型数组就可以解决这个问题。
```java
Integer[] myArray = { 1, 2, 3 };
```
**使用集合的修改方法:`add()``remove()``clear()`会抛出异常。**
```java
List myList = Arrays.asList(1, 2, 3);
myList.add(4);//运行时报错UnsupportedOperationException
myList.remove(1);//运行时报错UnsupportedOperationException
myList.clear();//运行时报错UnsupportedOperationException
```
`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
```java
List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
```
下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。
```java
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
...
@Override
public E get(int index) {
...
}
@Override
public E set(int index, E element) {
...
}
@Override
public int indexOf(Object o) {
...
}
@Override
public boolean contains(Object o) {
...
}
@Override
public void forEach(Consumer<? super E> action) {
...
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
...
}
@Override
public void sort(Comparator<? super E> c) {
...
}
}
```
我们再看一下`java.util.AbstractList``remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`
```java
public E remove(int index) {
throw new UnsupportedOperationException();
}
```
### 2.1.4. 如何正确的将数组转换为ArrayList?
stackoverflowhttps://dwz.cn/vcBkTiTW
**1. 自己动手实现(教育目的)**
```java
//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {
final List<T> l = new ArrayList<T>(array.length);
for (final T s : array) {
l.add(s);
}
return (l);
}
```
```java
Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
```
**2. 最简便的方法(推荐)**
```java
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
```
**3. 使用 Java8 的Stream(推荐)**
```java
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换依赖boxed的装箱操作
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
```
**4. 使用 Guava(推荐)**
对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)
```java
List<String> il = ImmutableList.of("string", "elements"); // from varargs
List<String> il = ImmutableList.copyOf(aStringArray); // from array
```
对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:
```java
List<String> l1 = Lists.newArrayList(anotherListOrCollection); // from collection
List<String> l2 = Lists.newArrayList(aStringArray); // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
```
**5. 使用 Apache Commons Collections**
```java
List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);
```
## 2.2. Collection.toArray()方法使用的坑&如何反转数组
该方法是一个泛型方法:`<T> T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。
```java
String [] s= new String[]{
"dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
s=list.toArray(new String[0]);//没有指定类型的话会报错
```
由于JVM优化`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用指定了返回数组的类型0是为了节省空间因为它只是为了说明返回的类型。详见<https://shipilev.net/blog/2016/arrays-wisdom-ancients/>
## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作
如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**
> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时可能会抛出ConcurrentModificationException单线程下也会出现这种情况上面已经提到过。
`java.util`包下面的所有的集合类都是fail-fast的`java.util.concurrent`包下面的所有的类都是fail-safe的。
![不要在 foreach 循环里进行元素的 remove/add 操作](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/foreach-remove:add.png)

View File

@ -0,0 +1,125 @@
## 泛型的实际应用
### 实现最小值函数
自己设计一个泛型的获取数组最小值的函数.并且这个方法只能接受Number的子类并且实现了Comparable接口。
```java
//注意Number并没有实现Comparable
private static <T extends Number & Comparable<? super T>> T min(T[] values) {
if (values == null || values.length == 0) return null;
T min = values[0];
for (int i = 1; i < values.length; i++) {
if (min.compareTo(values[i]) > 0) min = values[i];
}
return min;
}
```
测试:
```java
int minInteger = min(new Integer[]{1, 2, 3});//result:1
double minDouble = min(new Double[]{1.2, 2.2, -1d});//result:-1d
String typeError = min(new String[]{"1","3"});//报错
```
## 数据结构
### 使用数组实现栈
**自己实现一个栈,要求这个栈具有`push()``pop()`(返回栈顶元素并出栈)、`peek()` (返回栈顶元素不出栈)、`isEmpty()``size()`这些基本的方法。**
提示:每次入栈之前先判断栈的容量是否够用,如果不够用就用`Arrays.copyOf()`进行扩容;
```java
public class MyStack {
private int[] storage;//存放栈中元素的数组
private int capacity;//栈的容量
private int count;//栈中元素数量
private static final int GROW_FACTOR = 2;
//TODO不带初始容量的构造方法。默认容量为8
public MyStack() {
this.capacity = 8;
this.storage=new int[8];
this.count = 0;
}
//TODO带初始容量的构造方法
public MyStack(int initialCapacity) {
if (initialCapacity < 1)
throw new IllegalArgumentException("Capacity too small.");
this.capacity = initialCapacity;
this.storage = new int[initialCapacity];
this.count = 0;
}
//TODO入栈
public void push(int value) {
if (count == capacity) {
ensureCapacity();
}
storage[count++] = value;
}
//TODO确保容量大小
private void ensureCapacity() {
int newCapacity = capacity * GROW_FACTOR;
storage = Arrays.copyOf(storage, newCapacity);
capacity = newCapacity;
}
//TODO返回栈顶元素并出栈
private int pop() {
count--;
if (count == -1)
throw new IllegalArgumentException("Stack is empty.");
return storage[count];
}
//TODO返回栈顶元素不出栈
private int peek() {
if (count == 0){
throw new IllegalArgumentException("Stack is empty.");
}else {
return storage[count-1];
}
}
//TODO判断栈是否为空
private boolean isEmpty() {
return count == 0;
}
//TODO返回栈中元素的个数
private int size() {
return count;
}
}
```
验证
```java
MyStack myStack = new MyStack(3);
myStack.push(1);
myStack.push(2);
myStack.push(3);
myStack.push(4);
myStack.push(5);
myStack.push(6);
myStack.push(7);
myStack.push(8);
System.out.println(myStack.peek());//8
System.out.println(myStack.size());//8
for (int i = 0; i < 8; i++) {
System.out.println(myStack.pop());
}
System.out.println(myStack.isEmpty());//true
myStack.pop();//报错java.lang.IllegalArgumentException: Stack is empty.
```

View File

@ -0,0 +1,10 @@
根据各位建议加上了这部分内容,我暂时只是给出了两个资源,后续可能会对重要的点进行总结,然后更新在这里,如果你总结过这类东西,欢迎与我联系!
### 团队
- **阿里巴巴Java开发手册详尽版** <https://github.com/alibaba/p3c/blob/master/阿里巴巴Java开发手册华山版.pdf>
- **Google Java编程风格指南** <http://hawstein.com/2014/01/20/google-java-style/>
### 个人
- **程序员你为什么这么累:** <https://xwjie.github.io/rule/>

View File

@ -0,0 +1,407 @@
# Java 并发基础知识
Java 并发的基础知识,可能会在笔试中遇到,技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你:“谈谈自己对于进程和线程的理解,两者的区别是什么?”
**本节思维导图:**
## 一 进程和线程
进程和线程的对比这一知识点由于过于基础,所以在面试中很少碰到,但是极有可能会在笔试题中碰到。
常见的提问形式是这样的:**“什么是线程和进程?,请简要描述线程与进程的关系、区别及优缺点? ”**。
### 1.1. 何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
![进程 ](https://images.gitbook.cn/a0929b60-d133-11e8-88a4-5328c5b70145)
### 1.2 何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
```java
public class MultiThread {
public static void main(String[] args) {
// 获取 Java 线程管理 MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程 ID 和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
}
}
}
```
上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
```
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //调用对象 finalize 方法的线程
[2] Reference Handler //清除 reference 线程
[1] main //main 线程,程序入口
```
从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
### 1.3 从 JVM 角度说进程和线程之间的关系(重要)
#### 1.3.1 图解进程和线程的关系
下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下我的这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](<https://github.com/Snailclimb/JavaGuide/blob/3965c02cc0f294b0bd3580df4868d5e396959e2e/Java%E7%9B%B8%E5%85%B3/%E5%8F%AF%E8%83%BD%E6%98%AF%E6%8A%8AJava%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AE%B2%E7%9A%84%E6%9C%80%E6%B8%85%E6%A5%9A%E7%9A%84%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0.md>)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E5%9F%9FJDK1.8.png" width="600px"/>
</div>
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**
下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
#### 1.3.2 程序计数器为什么是私有的?
程序计数器主要有下面两个作用:
1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
#### 1.3.3 虚拟机栈和本地方法栈为什么是私有的?
- **虚拟机栈:**每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
- **本地方法栈:**和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
#### 1.3.4 一句话简单了解堆和方法区
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
## 二 多线程并发编程
### 2.1 并发与并行概念解读
- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行)
- **并行:**单位时间内,多个任务同时执行。
### 2.2 为什么要使用多线程?
先从总体上来说:
- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
再深入到计算机底层来探讨:
- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时IO 设备空闲;进行 IO 操作时CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子假如我们要计算一个复杂的任务我们只用一个线程的话CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
### 2.3 使用多线程可能带来的问题
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
## 三 线程的创建与运行
前两种实际上很少使用,一般都是用线程池的方式比较多一点。
### 3.1 继承 Thread 类的方式
```java
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
```
Run.java
```java
public class Run {
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
System.out.println("运行结束");
}
}
```
运行结果:
![结果 ](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380)
从上面的运行结果可以看出线程是一个子任务CPU 以不确定的方式,或者说是以随机的时间来调用线程中的 run 方法。
### 3.2 实现 Runnable 接口的方式
推荐实现 Runnable 接口方式开发多线程,因为 Java 单继承但是可以实现多个接口。
MyRunnable.java
```java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable");
}
}
```
Run.java
```java
public class Run {
public static void main(String[] args) {
Runnable runnable=new MyRunnable();
Thread thread=new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}
```
运行结果:
![运行结果 ](https://user-gold-cdn.xitu.io/2018/3/20/16243f4373c6141a?w=137&h=46&f=jpeg&s=7316)
### 3.3 使用线程池的方式
使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴 Java 开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。这里就不给大家演示代码了,线程池这一节会详细介绍到这部分内容。
## 四 线程的生命周期和状态
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态图源《Java 并发编程艺术》4.1.4 节)。
![Java 线程的状态 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示图源《Java 并发编程艺术》4.1.4 节):
![Java 线程状态变迁 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)
由上图可以看出:线程创建之后它将处于 **NEW新建** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY可运行** 状态。可运行状态的线程获得了 CPU 时间片timeslice后就处于 **RUNNING运行** 状态。
> 操作系统隐藏 Java 虚拟机JVM中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/)[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE运行中** 状态 。
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)
当线程执行 `wait()`方法之后,线程进入 **WAITING等待**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED阻塞** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED终止** 状态。
## 五 线程优先级
**理论上**来说系统会根据优先级来决定首先使哪个线程进入运行状态。当 CPU 比较闲的时候,设置线程优先级几乎不会有任何作用,而且很多操作系统压根不会不会理会你设置的线程优先级,所以不要让业务过度依赖于线程的优先级。
另外,**线程优先级具有继承特性**比如 A 线程启动 B 线程,则 B 线程的优先级和 A 是一样的。**线程优先级还具有随机性** 也就是说线程优先级高的不一定每一次都先执行完。
Thread 类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY常数 1****Thread.NORM_PRIORITY常数 5**,**Thread.MAX_PRIORITY常数 10**。其中每个线程的优先级都在**1** 到**10** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY常数 5**。
**一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了。**
**相关方法:**
```java
public final void setPriority(int newPriority) //为线程设定优先级
public final int getPriority() //获取线程的优先级
```
**设置线程优先级方法源码:**
```java
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
//线程游戏优先级不能小于 1 也不能大于 10否则会抛出异常
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
//如果指定的线程优先级大于该线程所在线程组的最大优先级,那么该线程的优先级将设为线程组的最大优先级
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
```
## 六 守护线程和用户线程
**守护线程和用户线程简介:**
- **用户 (User) 线程:**运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
- **守护 (Daemon) 线程:**运行在后台,为其他前台线程服务.也可以说守护线程是 JVM 中非守护线程的 **“佣人”**。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作.
main 函数所在的线程就是一个用户线程啊main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。
**那么守护线程和用户线程有什么区别呢?**
比较明显的区别之一是用户线程结束JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。
**注意事项:**
1. `setDaemon(true)`必须在`start`方法前执行,否则会抛出 `IllegalThreadStateException` 异常
2. 在守护线程中产生的新线程也是守护线程
3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
4. 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。
## 七 上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用为了让这些线程都能得到有效执行CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
## 八 线程死锁
### 认识线程死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2线程 B 持有资源 1他们同时都想申请对方的资源所以这两个线程就会互相等待而进入死锁状态。
![线程死锁示意图 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4死锁1.png)
下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》)
```java
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
```
Output
```
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1
```
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
1. 互斥条件:该资源任意一个时刻只由一个线程占用。
1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
1. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
1. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
### 如何避免线程死锁?
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
**破坏互斥条件**
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
**破坏请求与保持条件**
一次性申请所有的资源。
**破坏不剥夺条件**
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
**破坏循环等待条件**
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
我们对线程 2 的代码修改成下面这样就不会产生死锁了。
```java
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 2").start();
```
Output
```
Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2
Process finished with exit code 0
```
我们分析一下上面的代码为什么避免了死锁的发生?
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
## 参考
- 《Java 并发编程之美》
- 《Java 并发编程的艺术》
- https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/

View File

@ -1,5 +1,5 @@
**目录:** 点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- MarkdownTOC --> <!-- MarkdownTOC -->
- [1 AQS 简单介绍](#1-aqs-简单介绍) - [1 AQS 简单介绍](#1-aqs-简单介绍)
@ -23,10 +23,6 @@
> 常见问题AQS原理;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么用过Semaphore吗 > 常见问题AQS原理;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么用过Semaphore吗
**本节思维导图:**
![并发编程面试必备AQS 原理以及 AQS 同步组件总结](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-31/61115865.jpg)
### 1 AQS 简单介绍 ### 1 AQS 简单介绍
AQS的全称为AbstractQueuedSynchronizer这个类在java.util.concurrent.locks包下面。 AQS的全称为AbstractQueuedSynchronizer这个类在java.util.concurrent.locks包下面。
@ -37,7 +33,7 @@ AQS是一个用来构建锁和同步器的框架使用AQS能简单且高效
### 2 AQS 原理 ### 2 AQS 原理
> 在面试中被问到并发知识的时候大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加面试不是背题大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 > 在面试中被问到并发知识的时候大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加面试不是背题大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
下面大部分内容其实在AQS类注释上已经给出了不过是英语看着比较吃力一点感兴趣的话可以看看源码。 下面大部分内容其实在AQS类注释上已经给出了不过是英语看着比较吃力一点感兴趣的话可以看看源码。
@ -128,7 +124,7 @@ tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true
### 3 Semaphore(信号量)-允许多个线程同时访问 ### 3 Semaphore(信号量)-允许多个线程同时访问
**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下: **synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。** 示例代码如下:
```java ```java
/** /**
@ -336,7 +332,8 @@ public class CyclicBarrierExample2 {
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready"); System.out.println("threadnum:" + threadnum + "is ready");
try { try {
cyclicBarrier.await(2000, TimeUnit.MILLISECONDS); /**等待60秒保证子线程完全执行结束*/
cyclicBarrier.await(60, TimeUnit.SECONDS);
} catch (Exception e) { } catch (Exception e) {
System.out.println("-----CyclicBarrierException------"); System.out.println("-----CyclicBarrierException------");
} }
@ -470,6 +467,12 @@ CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文
ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。 ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。
由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。 ## 公众号
- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd) 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,556 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
> 个人觉得这一节掌握基本的使用即可!
<!-- TOC -->
- [1 Atomic 原子类介绍](#1-atomic-原子类介绍)
- [2 基本类型原子类](#2-基本类型原子类)
- [2.1 基本类型原子类介绍](#21-基本类型原子类介绍)
- [2.2 AtomicInteger 常见方法使用](#22-atomicinteger-常见方法使用)
- [2.3 基本数据类型原子类的优势](#23-基本数据类型原子类的优势)
- [2.4 AtomicInteger 线程安全原理简单分析](#24-atomicinteger-线程安全原理简单分析)
- [3 数组类型原子类](#3-数组类型原子类)
- [3.1 数组类型原子类介绍](#31-数组类型原子类介绍)
- [3.2 AtomicIntegerArray 常见方法使用](#32-atomicintegerarray-常见方法使用)
- [4 引用类型原子类](#4-引用类型原子类)
- [4.1 引用类型原子类介绍](#41--引用类型原子类介绍)
- [4.2 AtomicReference 类使用示例](#42-atomicreference-类使用示例)
- [4.3 AtomicStampedReference 类使用示例](#43-atomicstampedreference-类使用示例)
- [4.4 AtomicMarkableReference 类使用示例](#44-atomicmarkablereference-类使用示例)
- [5 对象的属性修改类型原子类](#5-对象的属性修改类型原子类)
- [5.1 对象的属性修改类型原子类介绍](#51-对象的属性修改类型原子类介绍)
- [5.2 AtomicIntegerFieldUpdater 类使用示例](#52-atomicintegerfieldupdater-类使用示例)
<!-- /TOC -->
### 1 Atomic 原子类介绍
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png)
根据操作的数据类型可以将JUC包中的原子类分为4类
**基本类型**
使用原子的方式更新基本类型
- AtomicInteger整型原子类
- AtomicLong长整型原子类
- AtomicBoolean :布尔型原子类
**数组类型**
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray整型数组原子类
- AtomicLongArray长整型数组原子类
- AtomicReferenceArray :引用类型数组原子类
**引用类型**
- AtomicReference引用类型原子类
- AtomicStampedReference原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
**对象的属性修改类型**
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater原子更新长整型字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
- AtomicMarkableReference原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
**CAS ABA 问题**
- 描述: 第一个线程取到了变量 x 的值 A然后巴拉巴拉干别的事总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A然后把变量 x 的值改为 B然后巴拉巴拉干别的事最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A所以 compareAndSet 操作是成功。
- 例子描述(可能不太合适,但好理解): 年初,现金为零,然后通过正常劳动赚了三百万,之后正常消费了(比如买房子)三百万。年末,虽然现金零收入(可能变成其他形式了),但是赚了钱是事实,还是得交税的!
- 代码例子(以``` AtomicInteger ```为例)
```java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDefectDemo {
public static void main(String[] args) {
defectOfABA();
}
static void defectOfABA() {
final AtomicInteger atomicInteger = new AtomicInteger(1);
Thread coreThread = new Thread(
() -> {
final int currentValue = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue);
// 这段目的:模拟处理其他业务花费的时间
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean casResult = atomicInteger.compareAndSet(1, 2);
System.out.println(Thread.currentThread().getName()
+ " ------ currentValue=" + currentValue
+ ", finalValue=" + atomicInteger.get()
+ ", compareAndSet Result=" + casResult);
}
);
coreThread.start();
// 这段目的:为了让 coreThread 线程先跑起来
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread amateurThread = new Thread(
() -> {
int currentValue = atomicInteger.get();
boolean casResult = atomicInteger.compareAndSet(1, 2);
System.out.println(Thread.currentThread().getName()
+ " ------ currentValue=" + currentValue
+ ", finalValue=" + atomicInteger.get()
+ ", compareAndSet Result=" + casResult);
currentValue = atomicInteger.get();
casResult = atomicInteger.compareAndSet(2, 1);
System.out.println(Thread.currentThread().getName()
+ " ------ currentValue=" + currentValue
+ ", finalValue=" + atomicInteger.get()
+ ", compareAndSet Result=" + casResult);
}
);
amateurThread.start();
}
}
```
输出内容如下:
```
Thread-0 ------ currentValue=1
Thread-1 ------ currentValue=1, finalValue=2, compareAndSet Result=true
Thread-1 ------ currentValue=2, finalValue=1, compareAndSet Result=true
Thread-0 ------ currentValue=1, finalValue=2, compareAndSet Result=true
```
下面我们来详细介绍一下这些原子类。
### 2 基本类型原子类
#### 2.1 基本类型原子类介绍
使用原子的方式更新基本类型
- AtomicInteger整型原子类
- AtomicLong长整型原子类
- AtomicBoolean :布尔型原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
**AtomicInteger 类常用方法**
```java
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值则以原子方式将该值设置为输入值update
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
```
#### 2.2 AtomicInteger 常见方法使用
```java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
AtomicInteger i = new AtomicInteger(0);
temvalue = i.getAndSet(3);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3
temvalue = i.getAndIncrement();
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4
temvalue = i.getAndAdd(5);
System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9
}
}
```
#### 2.3 基本数据类型原子类的优势
通过一个简单例子带大家看一下基本数据类型原子类的优势
**①多线程环境不使用原子类保证线程安全(基本数据类型)**
```java
class Test {
private volatile int count = 0;
//若要线程安全执行执行count++,需要加锁
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
```
**②多线程环境使用原子类保证线程安全(基本数据类型)**
```java
class Test2 {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
//使用AtomicInteger之后不需要加锁也可以实现线程安全。
public int getCount() {
return count.get();
}
}
```
#### 2.4 AtomicInteger 线程安全原理简单分析
AtomicInteger 类的部分源码:
```java
// setup to use Unsafe.compareAndSwapInt for updates更新操作时提供“比较并替换”的作用
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
```
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量在内存中可见因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
### 3 数组类型原子类
#### 3.1 数组类型原子类介绍
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray整形数组原子类
- AtomicLongArray长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。
**AtomicIntegerArray 类常用方法**
```java
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值并将其设置为新值newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值update
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
```
#### 3.2 AtomicIntegerArray 常见方法使用
```java
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(i.get(j));
}
temvalue = i.getAndSet(0, 2);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndIncrement(0);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndAdd(0, 5);
System.out.println("temvalue:" + temvalue + "; i:" + i);
}
}
```
### 4 引用类型原子类
#### 4.1 引用类型原子类介绍
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。
- AtomicReference引用类型原子类
- AtomicStampedReference原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
#### 4.2 AtomicReference 类使用示例
```java
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<Person> ar = new AtomicReference<Person>();
Person person = new Person("SnailClimb", 22);
ar.set(person);
Person updatePerson = new Person("Daisy", 20);
ar.compareAndSet(person, updatePerson);
System.out.println(ar.get().getName());
System.out.println(ar.get().getAge());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
```
Daisy
20
```
#### 4.3 AtomicStampedReference 类使用示例
```java
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
public static void main(String[] args) {
// 实例化、取当前值和 stamp 值
final Integer initialRef = 0, initialStamp = 0;
final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);
System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// compare and set
final Integer newReference = 666, newStamp = 999;
final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
System.out.println("currentValue=" + asr.getReference()
+ ", currentStamp=" + asr.getStamp()
+ ", casResult=" + casResult);
// 获取当前的值和当前的 stamp 值
int[] arr = new int[1];
final Integer currentValue = asr.get(arr);
final int currentStamp = arr[0];
System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
// 单独设置 stamp 值
final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
System.out.println("currentValue=" + asr.getReference()
+ ", currentStamp=" + asr.getStamp()
+ ", attemptStampResult=" + attemptStampResult);
// 重新设置当前值和 stamp 值
asr.set(initialRef, initialStamp);
System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// [不推荐使用,除非搞清楚注释的意思了] weak compare and set
// 困惑weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
// 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
// so is only rarely an appropriate alternative to compareAndSet."
// todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);
System.out.println("currentValue=" + asr.getReference()
+ ", currentStamp=" + asr.getStamp()
+ ", wCasResult=" + wCasResult);
}
}
```
输出结果如下:
```
currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, casResult=true
currentValue=666, currentStamp=999
currentValue=666, currentStamp=88, attemptStampResult=true
currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, wCasResult=true
```
#### 4.4 AtomicMarkableReference 类使用示例
``` java
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceDemo {
public static void main(String[] args) {
// 实例化、取当前值和 mark 值
final Boolean initialRef = null, initialMark = false;
final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);
System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// compare and set
final Boolean newReference1 = true, newMark1 = true;
final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);
System.out.println("currentValue=" + amr.getReference()
+ ", currentMark=" + amr.isMarked()
+ ", casResult=" + casResult);
// 获取当前的值和当前的 mark 值
boolean[] arr = new boolean[1];
final Boolean currentValue = amr.get(arr);
final boolean currentMark = arr[0];
System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
// 单独设置 mark 值
final boolean attemptMarkResult = amr.attemptMark(newReference1, false);
System.out.println("currentValue=" + amr.getReference()
+ ", currentMark=" + amr.isMarked()
+ ", attemptMarkResult=" + attemptMarkResult);
// 重新设置当前值和 mark 值
amr.set(initialRef, initialMark);
System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// [不推荐使用,除非搞清楚注释的意思了] weak compare and set
// 困惑weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
// 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
// so is only rarely an appropriate alternative to compareAndSet."
// todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);
System.out.println("currentValue=" + amr.getReference()
+ ", currentMark=" + amr.isMarked()
+ ", wCasResult=" + wCasResult);
}
}
```
输出结果如下:
```
currentValue=null, currentMark=false
currentValue=true, currentMark=true, casResult=true
currentValue=true, currentMark=true
currentValue=true, currentMark=false, attemptMarkResult=true
currentValue=null, currentMark=false
currentValue=true, currentMark=true, wCasResult=true
```
### 5 对象的属性修改类型原子类
#### 5.1 对象的属性修改类型原子类介绍
如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater原子更新长整形字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。
#### 5.2 AtomicIntegerFieldUpdater 类使用示例
```java
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22);
System.out.println(a.getAndIncrement(user));// 22
System.out.println(a.get(user));// 23
}
}
class User {
private String name;
public volatile int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
输出结果:
```
22
23
```
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -1,29 +1,68 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [Java 并发进阶常见面试题总结](#java-并发进阶常见面试题总结)
- [1. synchronized 关键字](#1-synchronized-关键字)
- [1.1. 说一说自己对于 synchronized 关键字的了解](#11-说一说自己对于-synchronized-关键字的了解)
- [1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗](#12-说说自己是怎么使用-synchronized-关键字在项目中用到了吗)
- [1.3. 讲一下 synchronized 关键字的底层原理](#13-讲一下-synchronized-关键字的底层原理)
- [1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗](#14-说说-jdk16-之后的synchronized-关键字底层做了哪些优化可以详细介绍一下这些优化吗)
- [1.5. 谈谈 synchronized和ReentrantLock 的区别](#15-谈谈-synchronized和reentrantlock-的区别)
- [2. volatile关键字](#2-volatile关键字)
- [2.1. 讲一下Java内存模型](#21-讲一下java内存模型)
- [2.2. 说说 synchronized 关键字和 volatile 关键字的区别](#22-说说-synchronized-关键字和-volatile-关键字的区别)
- [3. ThreadLocal](#3-threadlocal)
- [3.1. ThreadLocal简介](#31-threadlocal简介)
- [3.2. ThreadLocal示例](#32-threadlocal示例)
- [3.3. ThreadLocal原理](#33-threadlocal原理)
- [3.4. ThreadLocal 内存泄露问题](#34-threadlocal-内存泄露问题)
- [4. 线程池](#4-线程池)
- [4.1. 为什么要用线程池?](#41-为什么要用线程池)
- [4.2. 实现Runnable接口和Callable接口的区别](#42-实现runnable接口和callable接口的区别)
- [4.3. 执行execute()方法和submit()方法的区别是什么呢?](#43-执行execute方法和submit方法的区别是什么呢)
- [4.4. 如何创建线程池](#44-如何创建线程池)
- [5. Atomic 原子类](#5-atomic-原子类)
- [5.1. 介绍一下Atomic 原子类](#51-介绍一下atomic-原子类)
- [5.2. JUC 包中的原子类是哪4类?](#52-juc-包中的原子类是哪4类)
- [5.3. 讲讲 AtomicInteger 的使用](#53-讲讲-atomicinteger-的使用)
- [5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理](#54-能不能给我简单介绍一下-atomicinteger-类的原理)
- [6. AQS](#6-aqs)
- [6.1. AQS 介绍](#61-aqs-介绍)
- [6.2. AQS 原理分析](#62-aqs-原理分析)
- [6.2.1. AQS 原理概览](#621-aqs-原理概览)
- [6.2.2. AQS 对资源的共享方式](#622-aqs-对资源的共享方式)
- [6.2.3. AQS底层使用了模板方法模式](#623-aqs底层使用了模板方法模式)
- [6.3. AQS 组件总结](#63-aqs-组件总结)
- [7 Reference](#7-reference)
# 一 面试中关于 synchronized 关键字的 5 连击 <!-- /TOC -->
### 1.1 说一说自己对于 synchronized 关键字的了解 # Java 并发进阶常见面试题总结
## 1. synchronized 关键字
### 1.1. 说一说自己对于 synchronized 关键字的了解
synchronized关键字解决的是多个线程之间访问资源的同步性synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 synchronized关键字解决的是多个线程之间访问资源的同步性synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中synchronized属于重量级锁效率低下因为监视器锁monitor是依赖于底层的操作系统的 Mutex Lock 来实现的Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 另外,在 Java 早期版本中synchronized属于重量级锁效率低下因为监视器锁monitor是依赖于底层的操作系统的 Mutex Lock 来实现的Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 ### 1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
**synchronized关键字最主要的三种使用方式** **synchronized关键字最主要的三种使用方式**
- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** - **修饰实例方法:** 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源不管new了多少个对象只有一份所以对该类的所有对象都加了锁。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 - **修饰静态方法:** :也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源不管new了多少个对象只有一份。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中字符串常量池具有缓冲功能 - **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 **总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中字符串常量池具有缓存功能
下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” 面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
**双重校验锁实现对象单例(线程安全)** **双重校验锁实现对象单例(线程安全)**
```java ```java
@ -60,7 +99,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueIns
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
### 1.3 讲一下 synchronized 关键字的底层原理 ### 1.3. 讲一下 synchronized 关键字的底层原理
**synchronized 关键字底层原理属于 JVM 层面。** **synchronized 关键字底层原理属于 JVM 层面。**
@ -79,11 +118,11 @@ public class SynchronizedDemo {
通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class` 通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`
![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add616a292bcf?w=917&h=633&f=png&s=21863) ![synchronized关键字原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/synchronized关键字原理.png)
从上面我们可以看出: 从上面我们可以看出:
**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后将锁计数器设为0表明锁被释放。如果获取对象锁失败那当前线程就要阻塞等待直到锁被另外一个线程释放为止。 **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因) 的持有权当计数器为0则可以成功获取获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后将锁计数器设为0表明锁被释放。如果获取对象锁失败那当前线程就要阻塞等待直到锁被另外一个线程释放为止。
**② synchronized 修饰方法的的情况** **② synchronized 修饰方法的的情况**
@ -96,59 +135,59 @@ public class SynchronizedDemo2 {
``` ```
![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add6169fc206d?w=875&h=421&f=png&s=16114) ![synchronized关键字原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/synchronized关键字原理2.png)
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识该标识指明了该方法是一个同步方法JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识该标识指明了该方法是一个同步方法JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 ### 1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
关于这几种优化的详细信息可以查看[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd) 关于这几种优化的详细信息可以查看笔主的这篇文章:<https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md>
### 1.5 谈谈 synchronized和ReenTrantLock 的区别 ### 1.5. 谈谈 synchronized和ReentrantLock 的区别
**① 两者都是可重入锁** **① 两者都是可重入锁**
两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。 两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。
**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** **② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
**③ ReenTrantLock 比 synchronized 增加了一些高级功能** **③ ReentrantLock 比 synchronized 增加了一些高级功能**
相比synchronizedReenTrantLock增加了一些高级功能。主要来说主要有三点**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** 相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - **ReentrantLock提供了一种能够中断等待锁的线程的机制**通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 - **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视器**线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的用ReentrantLock类结合Condition实例可以实现“选择性通知”** 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 - synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视器**线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的用ReentrantLock类结合Condition实例可以实现“选择性通知”** 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能那么选择ReenTrantLock是一个不错的选择。 如果你想使用上述功能那么选择ReentrantLock是一个不错的选择。
**④ 性能已不是选择标准** **④ 性能已不是选择标准**
# 二 面试中关于线程池的 4 连击 ## 2. volatile关键字
### 2.1 讲一下Java内存模型 ### 2.1. 讲一下Java内存模型
在 JDK1.2 之前Java的内存模型实现总是从<font color="red">**主存**(即共享内存)读取变量</font>,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存<font color="red">**本地内存**</font>比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成<font color="red">**数据的不一致**</font> 在 JDK1.2 之前Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。
![数据的不一致](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4423ba2?w=273&h=166&f=jpeg&s=7268) ![数据不一致](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/数据不一致.png)
要解决这个问题,就需要把变量声明为<font color="red"> **volatile**</font>,这就指示 JVM这个变量是不稳定的每次使用它都到主存中进行读取。 要解决这个问题,就需要把变量声明为**volatile**,这就指示 JVM这个变量是不稳定的每次使用它都到主存中进行读取。
说白了,<font color="red"> **volatile**</font> 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
![volatile关键字的可见性](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4b9f501?w=474&h=238&f=jpeg&s=9942) ![volatile关键字的可见性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/volatile关键字的可见性.png)
### 2.2 说说 synchronized 关键字和 volatile 关键字的区别 ### 2.2. 说说 synchronized 关键字和 volatile 关键字的区别
synchronized关键字和volatile关键字比较 synchronized关键字和volatile关键字比较
@ -157,11 +196,164 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
- **volatile关键字能保证数据的可见性但不能保证数据的原子性。synchronized关键字两者都能保证。** - **volatile关键字能保证数据的可见性但不能保证数据的原子性。synchronized关键字两者都能保证。**
- **volatile关键字主要用于解决变量在多个线程之间的可见性而 synchronized关键字解决的是多个线程之间访问资源的同步性。** - **volatile关键字主要用于解决变量在多个线程之间的可见性而 synchronized关键字解决的是多个线程之间访问资源的同步性。**
## 3. ThreadLocal
# 三 面试中关于 线程池的 2 连击 ### 3.1. ThreadLocal简介
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
### 3.1 为什么要用线程池? **如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get``set` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
再举个简单的例子:
比如有两个人去宝屋收集宝物这两个共用一个袋子的话肯定会产生争执但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话那么ThreadLocal就是用来避免这两个线程竞争的。
### 3.2. ThreadLocal示例
相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
```java
import java.text.SimpleDateFormat;
import java.util.Random;
public class ThreadLocalExample implements Runnable{
// SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
}
}
```
Output:
```
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
```
从输出中可以看出Thread-0已经改变了formatter的值但仍然是thread-2默认格式化程序与初始化值相同其他线程也一样。
上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识它等于下面这段代码如果你写了下面这段代码的话IDEA会提示你转换为Java8的格式(IDEA真的不错)。因为ThreadLocal类在Java 8中扩展使用一个新的方法`withInitial()`将Supplier功能接口作为参数。
```java
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
```
### 3.3. ThreadLocal原理
`Thread`类源代码入手。
```java
public class Thread implements Runnable {
......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......
}
```
从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null只有当前线程调用 `ThreadLocal` 类的 `set``get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()``set() `方法。
`ThreadLocal`类的`set()`方法
```java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
```
通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。
**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了 ThreadLocal 声明的变量为什么在每一个线程都有自己的专属本地变量。
`ThreadLocalMap``ThreadLocal`的静态内部类。
![ThreadLocal内部类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ThreadLocal内部类.png)
### 3.4. ThreadLocal 内存泄露问题
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话value 永远无法被GC 回收这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况在调用 `set()``get()``remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
```java
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
```
**弱引用介绍:**
> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
>
> 弱引用可以和一个引用队列ReferenceQueue联合使用如果弱引用所引用的对象被垃圾回收Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
## 4. 线程池
### 4.1. 为什么要用线程池?
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
@ -171,31 +363,29 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 - **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
### 4.2. 实现Runnable接口和Callable接口的区别
### 3.2 实现Runnable接口和Callable接口的区别
如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。 如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
**备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callableRunnable task``Executors.callableRunnable taskObject resule`)。 **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callableRunnable task``Executors.callableRunnable taskObject resule`)。
### 3.3 执行execute()方法和submit()方法的区别是什么呢? ### 4.3. 执行execute()方法和submit()方法的区别是什么呢?
1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象通过这个future对象可以判断任务是否执行成功**并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit unit`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 2)**`submit()` 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象通过这个Future对象可以判断任务是否执行成功**并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成,而使用 `getlong timeoutTimeUnit unit`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
### 4.4. 如何创建线程池
### 3.4 如何创建线程池
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
> Executors 返回线程池对象的弊端如下: > Executors 返回线程池对象的弊端如下:
> >
> - **FixedThreadPool 和 SingleThreadExecutor** 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致OOM。 > - **FixedThreadPool 和 SingleThreadExecutor** 允许请求的队列长度为 Integer.MAX_VALUE 可能堆积大量的请求从而导致OOM。
> - **CachedThreadPool 和 ScheduledThreadPool** 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致OOM。 > - **CachedThreadPool 和 ScheduledThreadPool** 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致OOM。
**方式一:通过构造方法实现** **方式一:通过构造方法实现**
![通过构造方法实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baac923e9?w=925&h=158&f=jpeg&s=29190) ![ThreadPoolExecutor构造方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ThreadPoolExecutor构造方法.png)
**方式二通过Executor 框架的工具类Executors来实现** **方式二通过Executor 框架的工具类Executors来实现**
我们可以创建三种类型的ThreadPoolExecutor 我们可以创建三种类型的ThreadPoolExecutor
@ -204,12 +394,11 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
- **CachedThreadPool** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 - **CachedThreadPool** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
对应Executors工具类中的方法如图所示 对应Executors工具类中的方法如图所示
![通过Executor 框架的工具类Executors来实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baa9ca5e9?w=645&h=222&f=jpeg&s=31710) ![Executor框架的工具类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Executor框架的工具类.png)
## 5. Atomic 原子类
# 四 面试中关于 Atomic 原子类的 4 连击 ### 5.1. 介绍一下Atomic 原子类
### 4.1 介绍一下Atomic 原子类
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
@ -218,9 +407,9 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) ![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png)
### 4.2 JUC 包中的原子类是哪4类? ### 5.2. JUC 包中的原子类是哪4类?
**基本类型** **基本类型**
@ -228,7 +417,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
- AtomicInteger整形原子类 - AtomicInteger整形原子类
- AtomicLong长整型原子类 - AtomicLong长整型原子类
- AtomicBoolean :布尔型原子类 - AtomicBoolean布尔型原子类
**数组类型** **数组类型**
@ -237,22 +426,22 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
- AtomicIntegerArray整形数组原子类 - AtomicIntegerArray整形数组原子类
- AtomicLongArray长整形数组原子类 - AtomicLongArray长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类 - AtomicReferenceArray引用类型数组原子类
**引用类型** **引用类型**
- AtomicReference引用类型原子类 - AtomicReference引用类型原子类
- AtomicStampedRerence原子更新引用类型里的字段原子类 - AtomicStampedReference原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型 - AtomicMarkableReference :原子更新带有标记位的引用类型
**对象的属性修改类型** **对象的属性修改类型**
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 - AtomicIntegerFieldUpdater原子更新整形字段的更新器
- AtomicLongFieldUpdater原子更新长整形字段的更新器 - AtomicLongFieldUpdater原子更新长整形字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
### 4.3 讲讲 AtomicInteger 的使用 ### 5.3. 讲讲 AtomicInteger 的使用
**AtomicInteger 类常用方法** **AtomicInteger 类常用方法**
@ -284,7 +473,7 @@ class AtomicIntegerTest {
``` ```
### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理 ### 5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理
AtomicInteger 线程安全原理简单分析 AtomicInteger 线程安全原理简单分析
@ -311,17 +500,17 @@ CAS的原理是拿期望的值和原本的一个值作比较如果相同则
关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) 关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
# AQS ## 6. AQS
### 5.1 AQS 介绍 ### 6.1. AQS 介绍
AQS的全称为AbstractQueuedSynchronizer这个类在java.util.concurrent.locks包下面。 AQS的全称为AbstractQueuedSynchronizer这个类在java.util.concurrent.locks包下面。
![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bb575d4a690?w=317&h=338&f=png&s=14122) ![AQS类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/AQS类.png)
AQS是一个用来构建锁和同步器的框架使用AQS能简单且高效地构造出应用广泛的大量的同步器比如我们提到的ReentrantLockSemaphore其他的诸如ReentrantReadWriteLockSynchronousQueueFutureTask等等皆是基于AQS的。当然我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 AQS是一个用来构建锁和同步器的框架使用AQS能简单且高效地构造出应用广泛的大量的同步器比如我们提到的ReentrantLockSemaphore其他的诸如ReentrantReadWriteLockSynchronousQueueFutureTask等等皆是基于AQS的。当然我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
### 5.2 AQS 原理分析 ### 6.2. AQS 原理分析
AQS 原理这部分参考了部分博客在5.2节末尾放了链接。 AQS 原理这部分参考了部分博客在5.2节末尾放了链接。
@ -329,9 +518,7 @@ AQS 原理这部分参考了部分博客在5.2节末尾放了链接。
下面大部分内容其实在AQS类注释上已经给出了不过是英语看着比较吃力一点感兴趣的话可以看看源码。 下面大部分内容其实在AQS类注释上已经给出了不过是英语看着比较吃力一点感兴趣的话可以看看源码。
#### 5.2.1 AQS 原理概览 #### 6.2.1. AQS 原理概览
**AQS核心思想是如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制这个机制AQS是用CLH队列锁实现的即将暂时获取不到锁的线程加入到队列中。** **AQS核心思想是如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制这个机制AQS是用CLH队列锁实现的即将暂时获取不到锁的线程加入到队列中。**
@ -340,7 +527,7 @@ AQS 原理这部分参考了部分博客在5.2节末尾放了链接。
看个AQS(AbstractQueuedSynchronizer)原理图: 看个AQS(AbstractQueuedSynchronizer)原理图:
![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bbe4a9c5ae7?w=852&h=401&f=png&s=21797) ![AQS原理图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/AQS原理图.png)
AQS使用一个int成员变量来表示同步状态通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 AQS使用一个int成员变量来表示同步状态通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
@ -348,7 +535,7 @@ AQS使用一个int成员变量来表示同步状态通过内置的FIFO队列
private volatile int state;//共享变量使用volatile修饰保证线程可见性 private volatile int state;//共享变量使用volatile修饰保证线程可见性
``` ```
状态信息通过procted类型的getStatesetStatecompareAndSetState进行操作 状态信息通过protected类型的getStatesetStatecompareAndSetState进行操作
```java ```java
@ -366,20 +553,20 @@ protected final boolean compareAndSetState(int expect, int update) {
} }
``` ```
#### 5.2.2 AQS 对资源的共享方式 #### 6.2.2. AQS 对资源的共享方式
**AQS定义两种资源共享方式** **AQS定义两种资源共享方式**
- **Exclusive**独占只有一个线程能执行如ReentrantLock。又可分为公平锁和非公平锁 - **Exclusive**独占只有一个线程能执行如ReentrantLock。又可分为公平锁和非公平锁
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
- **Share**共享多个线程可同时执行如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 - **Share**共享多个线程可同时执行如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 ReentrantReadWriteLock 可以看成是组合式因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等AQS已经在顶层实现好了。 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等AQS已经在顶层实现好了。
#### 5.2.3 AQS底层使用了模板方法模式 #### 6.2.3. AQS底层使用了模板方法模式
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): 同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
@ -412,18 +599,27 @@ tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true
- http://www.cnblogs.com/waterystone/p/4920797.html - http://www.cnblogs.com/waterystone/p/4920797.html
- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html - https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
### 5.3 AQS 组件总结 ### 6.3. AQS 组件总结
- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。 - **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源。
- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类用来协调多个线程之间的同步。这个工具通常用来控制线程等待它可以让某一个线程等待直到倒计时结束再开始执行。 - **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类用来协调多个线程之间的同步。这个工具通常用来控制线程等待它可以让某一个线程等待直到倒计时结束再开始执行。
- **CyclicBarrier(循环栅栏)** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - **CyclicBarrier(循环栅栏)** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) ## 7 Reference
# Reference
- 《深入理解 Java 虚拟机》 - 《深入理解 Java 虚拟机》
- 《实战 Java 高并发程序设计》 - 《实战 Java 高并发程序设计》
- 《Java并发编程的艺术》 - 《Java并发编程的艺术》
- http://www.cnblogs.com/waterystone/p/4920797.html - http://www.cnblogs.com/waterystone/p/4920797.html
- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html - https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
- <https://www.journaldev.com/1076/java-threadlocal-example>
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,316 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [Java 并发基础常见面试题总结](#java-并发基础常见面试题总结)
- [1. 什么是线程和进程?](#1-什么是线程和进程)
- [1.1. 何为进程?](#11-何为进程)
- [1.2. 何为线程?](#12-何为线程)
- [2. 请简要描述线程与进程的关系,区别及优缺点?](#2-请简要描述线程与进程的关系区别及优缺点)
- [2.1. 图解进程和线程的关系](#21-图解进程和线程的关系)
- [2.2. 程序计数器为什么是私有的?](#22-程序计数器为什么是私有的)
- [2.3. 虚拟机栈和本地方法栈为什么是私有的?](#23-虚拟机栈和本地方法栈为什么是私有的)
- [2.4. 一句话简单了解堆和方法区](#24-一句话简单了解堆和方法区)
- [3. 说说并发与并行的区别?](#3-说说并发与并行的区别)
- [4. 为什么要使用多线程呢?](#4-为什么要使用多线程呢)
- [5. 使用多线程可能带来什么问题?](#5-使用多线程可能带来什么问题)
- [6. 说说线程的生命周期和状态?](#6-说说线程的生命周期和状态)
- [7. 什么是上下文切换?](#7-什么是上下文切换)
- [8. 什么是线程死锁?如何避免死锁?](#8-什么是线程死锁如何避免死锁)
- [8.1. 认识线程死锁](#81-认识线程死锁)
- [8.2. 如何避免线程死锁?](#82-如何避免线程死锁)
- [9. 说说 sleep() 方法和 wait() 方法区别和共同点?](#9-说说-sleep-方法和-wait-方法区别和共同点)
- [10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?](#10-为什么我们调用-start-方法时会执行-run-方法为什么我们不能直接调用-run-方法)
<!-- /TOC -->
# Java 并发基础常见面试题总结
## 1. 什么是线程和进程?
### 1.1. 何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
![进程示例图片-Windows](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/进程示例图片-Windows.png)
### 1.2. 何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
```java
public class MultiThread {
public static void main(String[] args) {
// 获取 Java 线程管理 MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程 ID 和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
}
}
}
```
上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
```
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //调用对象 finalize 方法的线程
[2] Reference Handler //清除 reference 线程
[1] main //main 线程,程序入口
```
从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
## 2. 请简要描述线程与进程的关系,区别及优缺点?
**从 JVM 角度说进程和线程之间的关系**
### 2.1. 图解进程和线程的关系
下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](<https://github.com/Snailclimb/JavaGuide/blob/3965c02cc0f294b0bd3580df4868d5e396959e2e/Java%E7%9B%B8%E5%85%B3/%E5%8F%AF%E8%83%BD%E6%98%AF%E6%8A%8AJava%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AE%B2%E7%9A%84%E6%9C%80%E6%B8%85%E6%A5%9A%E7%9A%84%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0.md>)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png" width="600px"/>
</div>
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**
**总结:** 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
下面是该知识点的扩展内容!
下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
### 2.2. 程序计数器为什么是私有的?
程序计数器主要有下面两个作用:
1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
### 2.3. 虚拟机栈和本地方法栈为什么是私有的?
- **虚拟机栈:** 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
- **本地方法栈:** 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
### 2.4. 一句话简单了解堆和方法区
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
## 3. 说说并发与并行的区别?
- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行)
- **并行:** 单位时间内,多个任务同时执行。
## 4. 为什么要使用多线程呢?
先从总体上来说:
- **从计算机底层来说:** 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
- **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
再深入到计算机底层来探讨:
- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时IO 设备空闲;进行 IO 操作时CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子假如我们要计算一个复杂的任务我们只用一个线程的话CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
## 5. 使用多线程可能带来什么问题?
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
## 6. 说说线程的生命周期和状态?
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态图源《Java 并发编程艺术》4.1.4 节)。
![Java 线程的状态 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png)
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示图源《Java 并发编程艺术》4.1.4 节):
![Java 线程状态变迁 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java+%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png)
由上图可以看出:线程创建之后它将处于 **NEW新建** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY可运行** 状态。可运行状态的线程获得了 CPU 时间片timeslice后就处于 **RUNNING运行** 状态。
> 操作系统隐藏 Java 虚拟机JVM中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/)[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE运行中** 状态 。
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png)
当线程执行 `wait()`方法之后,线程进入 **WAITING等待** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED阻塞** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED终止** 状态。
## 7. 什么是上下文切换?
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用为了让这些线程都能得到有效执行CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
## 8. 什么是线程死锁?如何避免死锁?
### 8.1. 认识线程死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2线程 B 持有资源 1他们同时都想申请对方的资源所以这两个线程就会互相等待而进入死锁状态。
![线程死锁示意图 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4%E6%AD%BB%E9%94%811.png)
下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》)
```java
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
```
Output
```
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1
```
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
1. 互斥条件:该资源任意一个时刻只由一个线程占用。
2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
### 8.2. 如何避免线程死锁?
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
**破坏互斥条件**
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
**破坏请求与保持条件**
一次性申请所有的资源。
**破坏不剥夺条件**
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
**破坏循环等待条件**
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
我们对线程 2 的代码修改成下面这样就不会产生死锁了。
```java
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 2").start();
```
Output
```
Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2
Process finished with exit code 0
```
我们分析一下上面的代码为什么避免了死锁的发生?
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
## 9. 说说 sleep() 方法和 wait() 方法区别和共同点?
- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。
- 两者都可以暂停线程的执行。
- Wait 通常被用于线程间交互/通信sleep 通常被用于暂停执行。
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
## 10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
new 一个 Thread线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
**总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。**
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,158 @@
[ThreadLocal造成OOM内存溢出案例演示与原理分析](https://blog.csdn.net/xlgen157387/article/details/78298840)
[深入理解 Java 之 ThreadLocal 工作原理](<https://allenwu.itscoder.com/threadlocal-source>)
## ThreadLocal
### ThreadLocal简介
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get``set` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
再举个简单的例子:
比如有两个人去宝屋收集宝物这两个共用一个袋子的话肯定会产生争执但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话那么ThreadLocal就是用来这两个线程竞争的。
### ThreadLocal示例
相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
```java
import java.text.SimpleDateFormat;
import java.util.Random;
public class ThreadLocalExample implements Runnable{
// SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
}
}
```
Output:
```
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
```
从输出中可以看出Thread-0已经改变了formatter的值但仍然是thread-2默认格式化程序与初始化值相同其他线程也一样。
上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识它等于下面这段代码如果你写了下面这段代码的话IDEA会提示你转换为Java8的格式(IDEA真的不错)。因为ThreadLocal类在Java 8中扩展使用一个新的方法`withInitial()`将Supplier功能接口作为参数。
```java
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
```
### ThreadLocal原理
`Thread`类源代码入手。
```java
public class Thread implements Runnable {
......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......
}
```
从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null只有当前线程调用 `ThreadLocal` 类的 `set``get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()``set() `方法。
`ThreadLocal`类的`set()`方法
```java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
```
通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal`ThreadLocal 可以理解为只是ThreadLocalMap的封装传递了变量值。**
**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key的键值对。这里解释了为什么每个线程访问同一个`ThreadLocal`,得到的确是不同的数值。另外,`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。**
`ThreadLocalMap``ThreadLocal`的静态内部类。
![ThreadLocal内部类](https://ws1.sinaimg.cn/large/006rNwoDgy1g2f47u9li2j30ka08cq43.jpg)
### ThreadLocal 内存泄露问题
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话value 永远无法被GC 回收这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况在调用 `set()``get()``remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
```java
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
```
**弱引用介绍:**
> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
>
> 弱引用可以和一个引用队列ReferenceQueue联合使用如果弱引用所引用的对象被垃圾回收Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

View File

@ -1,6 +1,4 @@
以下内容摘自我的 Gitchat [Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb),欢迎订阅!
Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md](https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md)
![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png) ![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png)
@ -12,7 +10,7 @@ Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/
下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单模式的原理呗!” 面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单模式的原理呗!”
@ -48,7 +46,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueIns
2. 初始化 uniqueInstance 2. 初始化 uniqueInstance
3. 将 uniqueInstance 指向分配的内存地址 3. 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance但此时 uniqueInstance 还未被初始化。 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
@ -141,7 +139,7 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、
**⑤ 锁粗化** **⑤ 锁粗化**
原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。
大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。
@ -168,4 +166,4 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
**④ 性能已不是选择标准** **④ 性能已不是选择标准**
在JDK1.6之前synchronized 的性能是比 ReenTrantLock 差很多。具体表示为synchronized 关键字吞吐量线程数的增加下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的JDK1.6之后性能已经不是选择synchronized和ReenTrantLock的影响因素了而且虚拟机在未来的性能改进中会更偏向于原生的synchronized所以还是提倡在synchronized能满足你的需求的情况下优先考虑使用synchronized关键字来进行同步优化后的synchronized和ReenTrantLock一样在很多地方都是用到了CAS操作**。 在JDK1.6之前synchronized 的性能是比 ReenTrantLock 差很多。具体表示为synchronized 关键字吞吐量线程数的增加下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的JDK1.6之后性能已经不是选择synchronized和ReenTrantLock的影响因素了而且虚拟机在未来的性能改进中会更偏向于原生的synchronized所以还是提倡在synchronized能满足你的需求的情况下优先考虑使用synchronized关键字来进行同步优化后的synchronized和ReenTrantLock一样在很多地方都是用到了CAS操作**。

View File

@ -1,3 +1,4 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- MarkdownTOC --> <!-- MarkdownTOC -->
@ -25,9 +26,9 @@
JDK提供的这些容器大部分在 `java.util.concurrent` 包中。 JDK提供的这些容器大部分在 `java.util.concurrent` 包中。
- **ConcurrentHashMap** 线程安全的HashMap - **ConcurrentHashMap:** 线程安全的HashMap
- **CopyOnWriteArrayList:** 线程安全的List在读多写少的场合性能非常好远远好于Vector. - **CopyOnWriteArrayList:** 线程安全的List在读多写少的场合性能非常好远远好于Vector.
- **ConcurrentLinkedQueue**高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList这是一个非阻塞队列。 - **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList这是一个非阻塞队列。
- **BlockingQueue:** 这是一个接口JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列非常适合用于作为数据共享的通道。 - **BlockingQueue:** 这是一个接口JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列非常适合用于作为数据共享的通道。
- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map使用跳表的数据结构进行快速查找。 - **ConcurrentSkipListMap:** 跳表的实现。这是一个Map使用跳表的数据结构进行快速查找。
@ -37,10 +38,10 @@ JDK提供的这些容器大部分在 `java.util.concurrent` 包中。
所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中无论是读操作还是写操作都能保证很高的性能在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。 所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中无论是读操作还是写操作都能保证很高的性能在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
关于 ConcurrentHashMap 相关问题,我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题: 关于 ConcurrentHashMap 相关问题,我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题:
- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB) - [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB)
- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0) - [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0)
@ -214,10 +215,18 @@ PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无
使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是哈希并不会保存元素的顺序而跳表内所有的元素都是排序的。因此在对跳表进行遍历时你会得到一个有序的结果。所以如果你的应用需要有序性那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。 使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是哈希并不会保存元素的顺序而跳表内所有的元素都是排序的。因此在对跳表进行遍历时你会得到一个有序的结果。所以如果你的应用需要有序性那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。
## 七 参考 ## 七 参考
- 《实战Java高并发程序设计》 - 《实战Java高并发程序设计》
- https://javadoop.com/post/java-concurrent-queue - https://javadoop.com/post/java-concurrent-queue
- https://juejin.im/post/5aeebd02518825672f19c546 - https://juejin.im/post/5aeebd02518825672f19c546
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,163 @@
JDK8接口规范
===
在JDK8中引入了lambda表达式出现了函数式接口的概念为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能)Java接口规范发生了一些改变。。
---
## 1.JDK8以前的接口规范
- JDK8以前接口可以定义的变量和方法
- 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```,它实际上都是```public static final```的。
- 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```,它实际上都是```public abstract```的。
```java
public interface AInterfaceBeforeJDK8 {
int FIELD = 0;
void simpleMethod();
}
```
以上接口信息反编译以后可以看到字节码信息里Filed是public static final的而方法是public abstract的即是你没有显示的去声明它。
```java
{
public static final int FIELD;
descriptor: I
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 0
public abstract void simpleMethod();
descriptor: ()V
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
```
## 2.JDK8之后的接口规范
- JDK8之后接口可以定义的变量和方法
- 变量(Field)仍然必须是 ```java public static final```的
- 方法(Method)除了可以是public abstract之外还可以是public static或者是default(相当于仅public修饰的实例方法)的。
从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
<br>下面是一个JDK8之后的接口例子
```java
public interface AInterfaceInJDK8 {
int simpleFiled = 0;
static int staticField = 1;
public static void main(String[] args) {
}
static void staticMethod(){}
default void defaultMethod(){}
void simpleMethod() throws IOException;
}
```
进行反编译(去除了一些没用信息)
```java
{
public static final int simpleFiled;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final int staticField;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static void main(java.lang.String[]);
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
public static void staticMethod();
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
public void defaultMethod();
flags: (0x0001) ACC_PUBLIC
public abstract void simpleMethod() throws java.io.IOException;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
Exceptions:
throws java.io.IOException
}
```
可以看到 default关键字修饰的方法是像实例方法一样定义的所以我们来定义一个只有default的方法并且实现一下试一试。
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
public class DefaultMethod implements Default {
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
//compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
//! DefaultMethod.defaultMethod();
}
}
```
可以看到default方法确实像实例方法一样必须有实例对象才能调用并且子类在实现接口时可以不用实现default方法也可以覆盖该方法。
这有点像子类继承父类实例方法。
<br>
接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
```java
interface Static {
static int staticMethod() {
return 4396;
}
}
... main(String...args)
//!compile error: Static method may be invoked on containing interface class only
//!aInstanceOfStatic.staticMethod();
...
```
另一个问题是多继承问题大家知道Java中类是不支持多继承的但是接口是多继承和多实现(implements后跟多个接口)的,
那么如果一个接口继承另一个接口两个接口都有同名的default方法会怎么样呢答案是会像类继承一样覆写(@Override)以下代码在IDE中可以顺利编译
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
interface Default2 extends Default {
@Override
default int defaultMethod() {
return 9527;
}
}
public class DefaultMethod implements Default,Default2 {
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
}
}
输出 : 9527
```
出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
<br>
那么如果一个类实现了两个没有继承关系的接口且这两个接口有同名方法的话会怎么样呢IDE会要求你重写这个冲突的方法让你自己选择去执行哪个方法因为IDE它
还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
interface Default2 {
default int defaultMethod() {
return 9527;
}
}
//如果不重写
//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
public class DefaultMethod implements Default,Default2 {
@Override
public int defaultMethod() {
System.out.println(Default.super.defaultMethod());
System.out.println(Default2.super.defaultMethod());
return 996;
}
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
}
}
运行输出 :
4396
9527
996
```

View File

@ -0,0 +1,933 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
随着 Java 8 的普及度越来越高很多人都提到面试中关于Java 8 也是非常常问的知识点。应各位要求和需要我打算对这部分知识做一个总结。本来准备自己总结的后面看到Github 上有一个相关的仓库,地址:
[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的,我对其进行了翻译并添加和修改了部分内容,下面是正文了。
<!-- MarkdownTOC -->
- [Java 8 Tutorial](#java-8-tutorial)
- [接口的默认方法\(Default Methods for Interfaces\)](#接口的默认方法default-methods-for-interfaces)
- [Lambda表达式\(Lambda expressions\)](#lambda表达式lambda-expressions)
- [函数式接口\(Functional Interfaces\)](#函数式接口functional-interfaces)
- [方法和构造函数引用\(Method and Constructor References\)](#方法和构造函数引用method-and-constructor-references)
- [Lamda 表达式作用域\(Lambda Scopes\)](#lamda-表达式作用域lambda-scopes)
- [访问局部变量](#访问局部变量)
- [访问字段和静态变量](#访问字段和静态变量)
- [访问默认接口方法](#访问默认接口方法)
- [内置函数式接口\(Built-in Functional Interfaces\)](#内置函数式接口built-in-functional-interfaces)
- [Predicates](#predicates)
- [Functions](#functions)
- [Suppliers](#suppliers)
- [Consumers](#consumers)
- [Comparators](#comparators)
- [Optionals](#optionals)
- [Streams\(流\)](#streams流)
- [Filter\(过滤\)](#filter过滤)
- [Sorted\(排序\)](#sorted排序)
- [Map\(映射\)](#map映射)
- [Match\(匹配\)](#match匹配)
- [Count\(计数\)](#count计数)
- [Reduce\(规约\)](#reduce规约)
- [Parallel Streams\(并行流\)](#parallel-streams并行流)
- [Sequential Sort\(串行排序\)](#sequential-sort串行排序)
- [Parallel Sort\(并行排序\)](#parallel-sort并行排序)
- [Maps](#maps)
- [Date API\(日期相关API\)](#date-api日期相关api)
- [Clock](#clock)
- [Timezones\(时区\)](#timezones时区)
- [LocalTime\(本地时间\)](#localtime本地时间)
- [LocalDate\(本地日期\)](#localdate本地日期)
- [LocalDateTime\(本地日期时间\)](#localdatetime本地日期时间)
- [Annotations\(注解\)](#annotations注解)
- [Whete to go from here?](#whete-to-go-from-here)
<!-- /MarkdownTOC -->
# Java 8 Tutorial
欢迎阅读我对Java 8的介绍。本教程将逐步指导您完成所有新语言功能。 在简短的代码示例的基础上您将学习如何使用默认接口方法lambda表达式方法引用和可重复注释。 在本文的最后,您将熟悉最新的 API 更改,如流,函数式接口(Functional Interfaces)Map 类的扩展和新的 Date API。 没有大段枯燥的文字,只有一堆注释的代码片段。
### 接口的默认方法(Default Methods for Interfaces)
Java 8使我们能够通过使用 `default` 关键字向接口添加非抽象方法实现。 此功能也称为[虚拟扩展方法](http://stackoverflow.com/a/24102730)。
第一个例子:
```java
interface Formula{
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
```
Formula 接口中除了抽象方法计算接口公式还定义了默认方法 `sqrt`。 实现该接口的类只需要实现抽象方法 `calculate`。 默认方法`sqrt` 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。
```java
public class Main {
public static void main(String[] args) {
// TODO 通过匿名内部类方式访问接口
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100)); // 100.0
System.out.println(formula.sqrt(16)); // 4.0
}
}
```
formula 是作为匿名对象实现的。该代码非常容易理解6行代码实现了计算 `sqrt(a * 100)`。在下一节中,我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。
**译者注:** 不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。
### Lambda表达式(Lambda expressions)
首先看看在老版本的Java中是如何排列字符串的
```java
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
```
只需要给静态方法` Collections.sort` 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 `sort` 方法。
在Java 8 中你就没必要使用这种传统的匿名对象的方式了Java 8提供了更简洁的语法lambda表达式
```java
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
```
可以看出,代码变得更段且更具有可读性,但是实际上还可以写得更短:
```java
Collections.sort(names, (String a, String b) -> b.compareTo(a));
```
对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字但是你还可以写得更短点
```java
names.sort((a, b) -> b.compareTo(a));
```
List 类本身就有一个 `sort` 方法。并且Java编译器可以自动推导出参数类型所以你可以不用再写一次类型。接下来我们看看lambda表达式还有什么其他用法。
### 函数式接口(Functional Interfaces)
**译者注:** 原文对这部分解释不太清楚,故做了修改!
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口可以被隐式转换为lambda表达式。`java.lang.Runnable``java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解`@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用`@FunctionalInterface` 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示
![@FunctionalInterface 注解](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/@FunctionalInterface.png)
示例:
```java
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
```
```java
// TODO 将数字字符串转换为整数类型
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted.getClass()); //class java.lang.Integer
```
**译者注:** 大部分函数式接口都不用我们自己写Java8都给我们实现好了这些接口都在java.util.function包里。
### 方法和构造函数引用(Method and Constructor References)
前一节中的代码还可以通过静态方法引用来表示:
```java
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted.getClass()); //class java.lang.Integer
```
Java 8允许您通过`::`关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法:
```java
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
```
```java
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
```
接下来看看构造函数是如何使用`::`关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
```java
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
```
接下来我们指定一个用来创建Person对象的对象工厂接口
```java
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
```
这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂:
```java
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
```
我们只需要使用 `Person::new` 来获取Person类构造函数的引用Java编译器会自动根据`PersonFactory.create`方法的参数类型来选择合适的构造函数。
### Lamda 表达式作用域(Lambda Scopes)
#### 访问局部变量
我们可以直接在 lambda 表达式中访问外部的局部变量:
```java
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
```
但是和匿名对象不同的是这里的变量num可以不用声明为final该代码同样正确
```java
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
```
不过这里的 num 必须不可被后面的代码修改即隐性的具有final的语义例如下面的就无法编译
```java
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;//在lambda表达式中试图修改num同样是不允许的。
```
#### 访问字段和静态变量
与局部变量相比我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。
```java
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
```
#### 访问默认接口方法
还记得第一节中的 formula 示例吗? `Formula` 接口定义了一个默认方法`sqrt`,可以从包含匿名对象的每个 formula 实例访问该方法。 这不适用于lambda表达式。
无法从 lambda 表达式中访问默认方法,故以下代码无法编译:
```java
Formula formula = (a) -> sqrt(a * 100);
```
### 内置函数式接口(Built-in Functional Interfaces)
JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: `Comparator``Runnable`,这些接口都增加了`@FunctionalInterface`注解以便能用在 lambda 表达式上。
但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 [Google Guava](https://code.google.com/p/guava-libraries/) 库里的即便你对这些很熟悉了还是有必要看看这些是如何扩展到lambda上使用的。
#### Predicates
Predicate 接口是只有一个参数的返回布尔类型值的 **断言型** 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):
**译者注:** Predicate 接口源码如下
```java
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
// 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
boolean test(T t);
//and方法与关系型运算符"&&"相似两边都成立才返回true
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// 与关系运算符"!"相似,对判断进行取反
default Predicate<T> negate() {
return (t) -> !test(t);
}
//or方法与关系型运算符"||"相似两边只要有一个成立就返回true
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
// 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
```
示例:
```java
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
```
#### Functions
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起compose, andThen
**译者注:** Function 接口源码如下
```java
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
//将Function对象应用到输入的参数上然后返回计算结果。
R apply(T t);
//将两个Function整合并返回一个能够执行两个Function对象功能的Function对象。
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
```
```java
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
```
#### Suppliers
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同Supplier 接口不接受参数。
```java
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
```
#### Consumers
Consumer 接口表示要对单个输入参数执行的操作。
```java
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
```
#### Comparators
Comparator 是老Java中的经典接口 Java 8在此之上添加了多种默认方法
```java
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
```
## Optionals
Optionals不是函数式接口而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念让我们快速了解一下Optionals的工作原理。
Optional 是一个简单的容器其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回而在Java 8中你应该返回 Optional 而不是 null。
译者注:示例中每个方法的作用已经添加。
```java
//of为非null的值创建一个Optional
Optional<String> optional = Optional.of("bam");
// isPresent 如果值存在返回true否则返回false
optional.isPresent(); // true
//get()如果Optional有值则将其返回否则抛出NoSuchElementException
optional.get(); // "bam"
//orElse如果有值则将其返回否则返回指定的其它值
optional.orElse("fallback"); // "bam"
//ifPresent如果Optional实例有值则为其调用consumer否则不做处理
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
```
推荐阅读:[[Java8]如何正确使用Optional](https://blog.kaaass.net/archives/764)
## Streams(流)
`java.util.Stream` 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种最终操作返回一特定类型的计算结果而中间操作返回Stream本身这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如` java.util.Collection` 的子类List 或者 Set Map 不支持。Stream 的操作可以串行执行或者并行执行。
首先看看Stream是怎么用首先创建实例代码的用到的数据List
```java
List<String> stringList = new ArrayList<>();
stringList.add("ddd2");
stringList.add("aaa2");
stringList.add("bbb1");
stringList.add("aaa1");
stringList.add("bbb3");
stringList.add("ccc");
stringList.add("bbb2");
stringList.add("ddd1");
```
Java 8扩展了集合类可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作
### Filter(过滤)
过滤通过一个predicate接口来过滤并只保留符合条件的元素该操作属于**中间操作**所以我们可以在过滤后的结果来应用其他Stream操作比如forEach。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作所以我们不能在forEach之后来执行其他Stream操作。
```java
// 测试 Filter(过滤)
stringList
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);//aaa2 aaa1
```
forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。
### Sorted(排序)
排序是一个 **中间操作**,返回的是排序好后的 Stream。**如果你不指定一个自定义的 Comparator 则会使用默认排序。**
```java
// 测试 Sort (排序)
stringList
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);// aaa1 aaa2
```
需要注意的是排序只创建了一个排列好后的Stream而不会影响原有的数据源排序之后原数据stringCollection是不会被修改的
```java
System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
```
### Map(映射)
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
```java
// 测试 Map 操作
stringList
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
```
### Match(匹配)
Stream提供了多种匹配操作允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 **最终操作** ,并返回一个 boolean 类型的值。
```java
// 测试 Match (匹配)操作
boolean anyStartsWithA =
stringList
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringList
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringList
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
```
### Count(计数)
计数是一个 **最终操作**返回Stream中元素的个数**返回值类型是 long**。
```java
//测试 Count (计数)操作
long startsWithB =
stringList
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
```
### Reduce(规约)
这是一个 **最终操作** 允许通过指定的函数来讲stream中的多个元素规约为一个元素规约后的结果是通过Optional 接口表示的:
```java
//测试 Reduce (规约)操作
Optional<String> reduced =
stringList
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
```
**译者注:** 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值种子然后依照运算规则BinaryOperator和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于`Integer sum = integers.reduce(0, (a, b) -> a+b);`也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
```java
// 字符串连接concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤字符串连接concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
```
上面代码例如第一个示例的 reduce()第一个参数空白字符即为起始值第二个参数String::concat为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional请留意这个区别。更多内容查看 [IBMJava 8 中的 Streams API 详解](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html)
## Parallel Streams(并行流)
前面提到过Stream有串行和并行两种串行Stream上的操作是在一个线程中依次完成而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能
首先我们创建一个没有重复元素的大表:
```java
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
```
我们分别用串行和并行两种方式对其进行排序,最后看看所用时间的对比。
### Sequential Sort(串行排序)
```java
//串行排序
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
```
```
1000000
sequential sort took: 709 ms//串行排序所用的时间
```
### Parallel Sort(并行排序)
```java
//并行排序
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
```
```java
1000000
parallel sort took: 475 ms//串行排序所用的时间
```
上面两个代码几乎是一样的,但是并行版的快了 50% 左右,唯一需要做的改动就是将 `stream()` 改为`parallelStream()`
## Maps
前面提到过Map 类型不支持 streams不过Map提供了一些新的有用的方法来处理一些日常任务。Map接口本身没有可用的 `stream`方法,但是你可以在键,值上创建专门的流或者通过 `map.keySet().stream()`,`map.values().stream()``map.entrySet().stream()`
此外,Maps 支持各种新的和有用的方法来执行常见任务。
```java
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9
```
`putIfAbsent` 阻止我们在null检查时写入额外的代码;`forEach`接受一个 consumer 来对 map 中的每个元素操作。
此示例显示如何使用函数在 map 上计算代码:
```java
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
```
接下来展示如何在Map里删除一个键值全都匹配的项
```java
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
```
另外一个有用的方法:
```java
map.getOrDefault(42, "not found"); // not found
```
对Map的元素做合并也变得很容易了
```java
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
```
Merge 做的事情是如果键名不存在则插入否则则对原键对应的值做合并操作并重新插入到map中。
## Date API(日期相关API)
Java 8在 `java.time` 包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。
**译者注(总结)**
- Clock 类提供了访问当前日期和时间的方法Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。
- 在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`(在`java.time`包中)表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法,它返回所有区域标识符。
- jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 DateLocalDateTime代替 CalendarDateTimeFormatter 代替 SimpleDateFormat。
### Clock
Clock 类提供了访问当前日期和时间的方法Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。
```java
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
System.out.println(millis);//1552379579043
Instant instant = clock.instant();
System.out.println(instant);
Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z
System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019
```
### Timezones(时区)
在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`(在`java.time`包中)表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法,它返回所有区域标识符。
```java
//输出所有区域标识符
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]
System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00]
```
### LocalTime(本地时间)
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差
```java
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
```
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串.
```java
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
```
### LocalDate(本地日期)
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
```java
LocalDate today = LocalDate.now();//获取现在的日期
System.out.println("今天的日期: "+today);//2019-03-12
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
System.out.println("明天的日期: "+tomorrow);//2019-03-13
LocalDate yesterday = tomorrow.minusDays(2);
System.out.println("昨天的日期: "+yesterday);//2019-03-11
LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println("今天是周几:"+dayOfWeek);//TUESDAY
```
从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单,下面是使用 `DateTimeFormatter` 解析字符串的例子:
```java
String str1 = "2014==04==12 01时06分09秒";
// 根据需要解析的日期、时间字符串定义解析所用的格式器
DateTimeFormatter fomatter1 = DateTimeFormatter
.ofPattern("yyyy==MM==dd HH时mm分ss秒");
LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1);
System.out.println(dt1); // 输出 2014-04-12T01:06:09
String str2 = "2014$$$四月$$$13 20小时";
DateTimeFormatter fomatter2 = DateTimeFormatter
.ofPattern("yyy$$$MMM$$$dd HH小时");
LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);
System.out.println(dt2); // 输出 2014-04-13T20:00
```
再来看一个使用 `DateTimeFormatter` 格式化日期的示例
```java
LocalDateTime rightNow=LocalDateTime.now();
String date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
System.out.println(date);//2019-03-12T16:26:48.29
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
System.out.println(formatter.format(rightNow));//2019-03-12 16:26:48
```
### LocalDateTime(本地日期时间)
LocalDateTime 同时表示了时间和日期相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。
```java
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
```
只要附加上时区信息就可以将其转换为一个时间点Instant对象Instant时间点对象可以很容易的转换为老式的`java.util.Date`
```java
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
```
格式化LocalDateTime和格式化时间和日期一样的除了使用预定义好的格式外我们也可以自己定义格式
```java
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
```
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的所以它是线程安全的。
关于时间日期格式的详细信息在[这里](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。
## Annotations(注解)
在Java 8中支持多重注解了先看个例子来理解一下是什么意思。
首先定义一个包装类Hints注解用来放置一组具体的Hint注解
```java
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
```
Java 8允许我们把同一个类型的注解使用多次只需要给该注解标注一下`@Repeatable`即可。
例 1: 使用包装类当容器来存多个注解(老方法)
```java
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
```
例 2使用多重注解新方法
```java
@Hint("hint1")
@Hint("hint2")
class Person {}
```
第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:
```java
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length); // 2
```
即便我们没有在 `Person`类上定义 `@Hints`注解,我们还是可以通过 `getAnnotation(Hints.class) `来获取 `@Hints`注解,更加方便的方法是使用 `getAnnotationsByType` 可以直接获取到所有的`@Hint`注解。
另外Java 8的注解还增加到两种新的target上了
```java
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
```
## Where to go from here?
关于Java 8的新特性就写到这了肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如`Arrays.parallelSort`, `StampedLock``CompletableFuture`等等。
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,18 @@
### 书籍
- **《Java8 In Action》**
- **《写给大忙人看的Java SE 8》**
上述书籍的PDF版本见 https://shimo.im/docs/CPB0PK05rP4CFmI2/ 中的 “Java 书籍推荐”。
### 开源文档
- **【译】Java 8 简明教程**<https://github.com/wizardforcel/modern-java-zh>
- **30 seconds of java8:** <https://github.com/biezhi/30-seconds-of-java8>
### 视频
- **尚硅谷 Java 8 新特性**
视频资源见: https://shimo.im/docs/CPB0PK05rP4CFmI2/ 。

View File

@ -0,0 +1,235 @@
JDK8--Lambda表达式
===
## 1.什么是Lambda表达式
**Lambda表达式实质上是一个可传递的代码块Lambda又称为闭包或者匿名函数是函数式编程语法让方法可以像普通参数一样传递**
## 2.Lambda表达式语法
```(参数列表) -> {执行代码块}```
<br>参数列表可以为空```()->{}```
<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到lambda同样可以有返回值.
<br>在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
## 3.函数式接口
在了解Lambda表达式之前有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>
**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>
当需要一个函数式接口的对象时就可以用Lambda表达式来实现举个常用的例子:
<br>
```java
Thread thread = new Thread(() -> {
System.out.println("This is JDK8's Lambda!");
});
```
这段代码和函数式接口有啥关系我们回忆一下Thread类的构造函数里是不是有一个以Runnable接口为参数的
```java
public Thread(Runnable target) {...}
/**
* Runnable Interface
*/
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
```
到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
```java
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous class");
}
});
```
也就是说上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数只不过它的返回值、参数列表都
由编译器帮我们推断,因此可以减少很多代码量)。
<br>Lambda也可以这样用 :
```java
Runnable runnable = () -> {...};
```
其实这和上面的用法没有什么本质上的区别。
<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范
目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
<br>
除了上面说的Runnable接口之外JDK中已经存在了很多函数式接口
比如(当然不止这些):
- ```java.util.concurrent.Callable```
- ```java.util.Comparator```
- ```java.io.FileFilter```
<br>**关于JDK中的预定义的函数式接口**
- JDK在```java.util.function```下预定义了很多函数式接口
- ```Function<T, R> {R apply(T t);}``` 接受一个T对象然后返回一个R对象就像普通的函数。
- ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象没有返回值。
- ```Predicate<T> {boolean test(T t);}``` 判断接受一个T对象返回一个布尔值。
- ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。
- 其他的跟上面的相似大家可以看一下function包下的具体接口。
## 4.变量作用域
```java
public class VaraibleHide {
@FunctionalInterface
interface IInner {
void printInt(int x);
}
public static void main(String[] args) {
int x = 20;
IInner inner = new IInner() {
int x = 10;
@Override
public void printInt(int x) {
System.out.println(x);
}
};
inner.printInt(30);
inner = (s) -> {
//Variable used in lambda expression should be final or effectively final
//!int x = 10;
//!x= 50; error
System.out.print(x);
};
inner.printInt(30);
}
}
输出 :
30
20
```
对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量它是被lambda表达式捕获的。
<br>lambda表达式和内部类一样对外部自由变量捕获时外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,
同时lambda不像内部类/匿名类lambda表达式与外围嵌套块有着相同的作用域因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该
不难理解。
## 5.方法引用
**只需要提供方法的名字具体的调用过程由Lambda和函数式接口来确定这样的方法调用成为方法引用。**
<br>下面的例子会打印list中的每个元素:
```java
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(System.out::println);
```
其中```System.out::println```这个就是一个方法引用等价于Lambda表达式 ```(para)->{System.out.println(para);}```
<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口该接口是一个函数式接口
```java
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
```
大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
<br>我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
```java
public class MethodReference {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(MethodReference::myPrint);
}
static void myPrint(int i) {
System.out.print(i + ", ");
}
}
输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
```
可以看到,我们自己定义的方法也可以当做方法引用。
<br>到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
- 方法引用
- 类名::静态方法名
- 类名::实例方法名
- 类名::new (构造方法引用)
- 实例名::实例方法名
可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
方法引用的使用方法。<br>
**类名::实例方法名**<br>
先来看一段代码
```java
String[] strings = new String[10];
Arrays.sort(strings, String::compareToIgnoreCase);
```
**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>
我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,
可以看到第二个参数是一个Comparator接口该接口也是一个函数式接口其中的抽象方法是`int compare(T o1, T o2);`,再看一下
`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
<br>是的确实不一样但是别忘了String类的这个方法是个实例方法而不是静态方法也就是说这个方法是需要有一个接收者的。所谓接收者就是
instance.method(x)中的instance
它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
参数。我们再举一个自己实现的例子:
```java
public class MethodReference {
static Random random = new Random(47);
public static void main(String[] args) {
MethodReference[] methodReferences = new MethodReference[10];
Arrays.sort(methodReferences, MethodReference::myCompare);
}
int myCompare(MethodReference o) {
return random.nextInt(2) - 1;
}
}
```
上面的例子可以在IDE里通过编译大家有兴趣的可以模仿上面的例子自己写一个程序打印出排序后的结果。
<br>**构造器引用**<br>
构造器引用仍然需要与特定的函数式接口配合使用并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
```java
//compile error : String is not a functional interface
String str = String::new;
```
下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
```java
interface IFunctional<T> {
T func();
}
public class ConstructorReference {
public ConstructorReference() {
}
public static void main(String[] args) {
Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();
Supplier<ConstructorReference> supplier1 = ConstructorReference::new;
IFunctional<ConstructorReference> functional = () -> new ConstructorReference();
IFunctional<ConstructorReference> functional1 = ConstructorReference::new;
}
}
```
下面是一个JDK官方的例子
```java
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
DEST transferElements(
SOURCE sourceCollection,
Supplier<DEST> collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
}
...
Set<Person> rosterSet = transferElements(
roster, HashSet::new);
```
**实例::实例方法**
<br>
其实开始那个例子就是一个实例::实例方法的引用
```java
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(System.out::println);
```
其中System.out就是一个实例println是一个实例方法。相信不用再给大家做解释了。
## 总结
Lambda表达式是JDK8引入Java的函数式编程语法使用Lambda需要直接或者间接的与函数式接口配合在开发中使用Lambda可以减少代码量
但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降并且也节省不了多少代码
所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断同样对于这个特性需要斟酌使用。

View File

@ -0,0 +1,556 @@
JDK8新特性总结
======
总结了部分JDK8新特性另外一些新特性可以通过Oracle的官方文档查看毕竟是官方文档各种新特性都会介绍有兴趣的可以去看。<br>
[Oracle官方文档:What's New in JDK8](https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html)
-----
- [Java语言特性](#JavaProgrammingLanguage)
- [Lambda表达式是一个新的语言特性已经在JDK8中加入。它是一个可以传递的代码块你也可以把它们当做方法参数。
Lambda表达式允许您更紧凑地创建单虚方法接口称为功能接口的实例。](#LambdaExpressions)
- [方法引用为已经存在的具名方法提供易于阅读的Lambda表达式](#MethodReferences)
- [默认方法允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。](#DefaultMethods)
- [改进的类型推断。](#ImprovedTypeInference)
- [方法参数反射(通过反射获得方法参数信息)](#MethodParameterReflection)
- [流(stream)](#stream)
- [新java.util.stream包中的类提供Stream API以支持对元素流的功能样式操作。流(stream)和I/O里的流不是同一个概念
使用stream API可以更方便的操作集合。]()
- [国际化]()
- 待办
- 待办
___
<!-- ---------------------------------------------------Lambda表达式-------------------------------------------------------- -->
<!-- 标题与跳转-->
<span id="JavaProgrammingLanguage"></span>
<span id="LambdaExpressions"></span>
<!-- 标题与跳转结束-->
<!-- 正文-->
##              Lambda表达式
### 1.什么是Lambda表达式
**Lambda表达式实质上是一个可传递的代码块Lambda又称为闭包或者匿名函数是函数式编程语法让方法可以像普通参数一样传递**
### 2.Lambda表达式语法
```(参数列表) -> {执行代码块}```
<br>参数列表可以为空```()->{}```
<br>可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到lambda同样可以有返回值.
<br>在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
### 3.函数式接口
在了解Lambda表达式之前有必要先了解什么是函数式接口```(@FunctionalInterface)```<br>
**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br>
当需要一个函数式接口的对象时就可以用Lambda表达式来实现举个常用的例子:
<br>
```java
Thread thread = new Thread(() -> {
System.out.println("This is JDK8's Lambda!");
});
```
这段代码和函数式接口有啥关系我们回忆一下Thread类的构造函数里是不是有一个以Runnable接口为参数的
```java
public Thread(Runnable target) {...}
/**
* Runnable Interface
*/
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
```
到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
```java
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous class");
}
});
```
也就是说上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数只不过它的返回值、参数列表都
由编译器帮我们推断,因此可以减少很多代码量)。
<br>Lambda也可以这样用 :
```java
Runnable runnable = () -> {...};
```
其实这和上面的用法没有什么本质上的区别。
<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范
目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
<br>
除了上面说的Runnable接口之外JDK中已经存在了很多函数式接口
比如(当然不止这些):
- ```java.util.concurrent.Callable```
- ```java.util.Comparator```
- ```java.io.FileFilter```
<br>**关于JDK中的预定义的函数式接口**
- JDK在```java.util.function```下预定义了很多函数式接口
- ```Function<T, R> {R apply(T t);}``` 接受一个T对象然后返回一个R对象就像普通的函数。
- ```Consumer<T> {void accept(T t);}``` 消费者 接受一个T对象没有返回值。
- ```Predicate<T> {boolean test(T t);}``` 判断接受一个T对象返回一个布尔值。
- ```Supplier<T> {T get();} 提供者(工厂)``` 返回一个T对象。
- 其他的跟上面的相似大家可以看一下function包下的具体接口。
### 4.变量作用域
```java
public class VaraibleHide {
@FunctionalInterface
interface IInner {
void printInt(int x);
}
public static void main(String[] args) {
int x = 20;
IInner inner = new IInner() {
int x = 10;
@Override
public void printInt(int x) {
System.out.println(x);
}
};
inner.printInt(30);
inner = (s) -> {
//Variable used in lambda expression should be final or effectively final
//!int x = 10;
//!x= 50; error
System.out.print(x);
};
inner.printInt(30);
}
}
输出 :
30
20
```
对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的像这样并不是在lambda中定义或者通过lambda
的参数列表()获取的变量成为自由变量它是被lambda表达式捕获的。
<br>lambda表达式和内部类一样对外部自由变量捕获时外部自由变量必须为final或者是最终变量(effectively final)的也就是说这个变量初始化后就不能为它赋新值同时lambda不像内部类/匿名类lambda表达式与外围嵌套块有着相同的作用域因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。
<span id="MethodReferences"></span>
### 5.方法引用
**只需要提供方法的名字具体的调用过程由Lambda和函数式接口来确定这样的方法调用成为方法引用。**
<br>下面的例子会打印list中的每个元素:
```java
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(System.out::println);
```
其中```System.out::println```这个就是一个方法引用等价于Lambda表达式 ```(para)->{System.out.println(para);}```
<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口该接口是一个函数式接口
```java
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
```
大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
<br>我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
```java
public class MethodReference {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(MethodReference::myPrint);
}
static void myPrint(int i) {
System.out.print(i + ", ");
}
}
输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
```
可以看到,我们自己定义的方法也可以当做方法引用。
<br>到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
- 方法引用
- 类名::静态方法名
- 类名::实例方法名
- 类名::new (构造方法引用)
- 实例名::实例方法名
可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
方法引用的使用方法。<br>
**类名::实例方法名**<br>
先来看一段代码
```java
String[] strings = new String[10];
Arrays.sort(strings, String::compareToIgnoreCase);
```
**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**<br>
我们看一下`Arrays#sort`方法`public static <T> void sort(T[] a, Comparator<? super T> c)`,
可以看到第二个参数是一个Comparator接口该接口也是一个函数式接口其中的抽象方法是`int compare(T o1, T o2);`,再看一下
`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
<br>是的确实不一样但是别忘了String类的这个方法是个实例方法而不是静态方法也就是说这个方法是需要有一个接收者的。所谓接收者就是
instance.method(x)中的instance
它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
参数。我们再举一个自己实现的例子:
```java
public class MethodReference {
static Random random = new Random(47);
public static void main(String[] args) {
MethodReference[] methodReferences = new MethodReference[10];
Arrays.sort(methodReferences, MethodReference::myCompare);
}
int myCompare(MethodReference o) {
return random.nextInt(2) - 1;
}
}
```
上面的例子可以在IDE里通过编译大家有兴趣的可以模仿上面的例子自己写一个程序打印出排序后的结果。
<br>**构造器引用**<br>
构造器引用仍然需要与特定的函数式接口配合使用并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
```java
//compile error : String is not a functional interface
String str = String::new;
```
下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
```java
interface IFunctional<T> {
T func();
}
public class ConstructorReference {
public ConstructorReference() {
}
public static void main(String[] args) {
Supplier<ConstructorReference> supplier0 = () -> new ConstructorReference();
Supplier<ConstructorReference> supplier1 = ConstructorReference::new;
IFunctional<ConstructorReference> functional = () -> new ConstructorReference();
IFunctional<ConstructorReference> functional1 = ConstructorReference::new;
}
}
```
下面是一个JDK官方的例子
```java
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
DEST transferElements(
SOURCE sourceCollection,
Supplier<DEST> collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
}
...
Set<Person> rosterSet = transferElements(
roster, HashSet::new);
```
**实例::实例方法**
<br>
其实开始那个例子就是一个实例::实例方法的引用
```java
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
list.add(i);
}
list.forEach(System.out::println);
```
其中System.out就是一个实例println是一个实例方法。相信不用再给大家做解释了。
### 总结
Lambda表达式是JDK8引入Java的函数式编程语法使用Lambda需要直接或者间接的与函数式接口配合在开发中使用Lambda可以减少代码量
但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降并且也节省不了多少代码
所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断同样对于这个特性需要斟酌使用。
<!-- ---------------------------------------------------Lambda表达式结束---------------------------------------------------- -->
___
<!-- ---------------------------------------------------接口默认方法-------------------------------------------------------- -->
<span id="DefaultMethods"></span>
##              JDK8接口规范
### 在JDK8中引入了lambda表达式出现了函数式接口的概念为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法否则会报错。为了保持兼容性而做出妥协的特性还有泛型泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能)Java接口规范发生了一些改变。
### 1.JDK8以前的接口规范
- JDK8以前接口可以定义的变量和方法
- 所有变量(Field)不论是否<i>显式</i> 的声明为```public static final```,它实际上都是```public static final```的。
- 所有方法(Method)不论是否<i>显示</i> 的声明为```public abstract```,它实际上都是```public abstract```的。
```java
public interface AInterfaceBeforeJDK8 {
int FIELD = 0;
void simpleMethod();
}
```
以上接口信息反编译以后可以看到字节码信息里Filed是public static final的而方法是public abstract的即是你没有显示的去声明它。
```java
{
public static final int FIELD;
descriptor: I
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 0
public abstract void simpleMethod();
descriptor: ()V
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
```
### 2.JDK8之后的接口规范
- JDK8之后接口可以定义的变量和方法
- 变量(Field)仍然必须是 ```java public static final```的
- 方法(Method)除了可以是public abstract之外还可以是public static或者是default(相当于仅public修饰的实例方法)的。
从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
<br>下面是一个JDK8之后的接口例子
```java
public interface AInterfaceInJDK8 {
int simpleFiled = 0;
static int staticField = 1;
public static void main(String[] args) {
}
static void staticMethod(){}
default void defaultMethod(){}
void simpleMethod() throws IOException;
}
```
进行反编译(去除了一些没用信息)
```java
{
public static final int simpleFiled;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final int staticField;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static void main(java.lang.String[]);
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
public static void staticMethod();
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
public void defaultMethod();
flags: (0x0001) ACC_PUBLIC
public abstract void simpleMethod() throws java.io.IOException;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
Exceptions:
throws java.io.IOException
}
```
可以看到 default关键字修饰的方法是像实例方法(就是普通类中定义的普通方法)一样定义的所以我们来定义一个只有default方法的接口并且实现一下这个接口试一
试。
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
public class DefaultMethod implements Default {
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
//compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
//! DefaultMethod.defaultMethod();
}
}
```
可以看到default方法确实像实例方法一样必须有实例对象才能调用并且子类在实现接口时可以不用实现default方法也可以选择覆盖该方法。
这有点像子类继承父类实例方法。
<br>
接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
```java
interface Static {
static int staticMethod() {
return 4396;
}
}
... main(String...args)
//!compile error: Static method may be invoked on containing interface class only
//!aInstanceOfStatic.staticMethod();
...
```
另一个问题是多继承问题大家知道Java中类是不支持多继承的但是接口是多继承和多实现(implements后跟多个接口)的,
那么如果一个接口继承另一个接口两个接口都有同名的default方法会怎么样呢答案是会像类继承一样覆写(@Override)以下代码在IDE中可以顺利编译
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
interface Default2 extends Default {
@Override
default int defaultMethod() {
return 9527;
}
}
public class DefaultMethod implements Default,Default2 {
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
}
}
输出 : 9527
```
出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
<br>
那么如果一个类实现了两个没有继承关系的接口且这两个接口有同名方法的话会怎么样呢IDE会要求你重写这个冲突的方法让你自己选择去执行哪个方法因为IDE它还没智能到你不告诉它它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
```java
interface Default {
default int defaultMethod() {
return 4396;
}
}
interface Default2 {
default int defaultMethod() {
return 9527;
}
}
//如果不重写
//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
public class DefaultMethod implements Default,Default2 {
@Override
public int defaultMethod() {
System.out.println(Default.super.defaultMethod());
System.out.println(Default2.super.defaultMethod());
return 996;
}
public static void main(String[] args) {
DefaultMethod defaultMethod = new DefaultMethod();
System.out.println(defaultMethod.defaultMethod());
}
}
运行输出 :
4396
9527
996
```
<!-- ---------------------------------------------------接口默认方法结束---------------------------------------------------- -->
___
<!-- --------------------------------------------------- 改进的类型推断 ---------------------------------------------------- -->
<span id="ImprovedTypeInference"></span>
##              改进的类型推断
### 1.什么是类型推断
类型推断就像它的字面意思一样,编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型,这就是类型推断。
看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
```java
Map<String, Object> map = new Map<String, Object>();
```
而我们平常写的都是这样的:
```java
Map<String, Object> map = new Map<>();
```
这就是类型推断《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断)在Java编程思想里Bruce Eckel大叔还提到过这个问题
(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断☺)在JDK7中推出了上面这样的类型推断可以减少一些无用的代码。
(Java编程思想到现在还只有第四版是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢/滑稽)
<br>
在JDK7中类型推断只有上面例子的那样的能力即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息)下面的官方文档里的例子在JDK7里会编译
错误
```java
List<String> stringList = new ArrayList<>();
stringList.add("A");
//error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)
stringList.addAll(Arrays.asList());
```
但是上面的代码在JDK8里可以通过也就说JDK8里类型推断不仅可以用于赋值语句而且可以根据代码中上下文里的信息推断出更多的信息因此我们需要些的代码
会更少。加强的类型推断还有一个就是用于Lambda表达式了。
<br>
大家其实不必细究类型推断在日常使用中IDE会自动判断当IDE自己无法推断出足够的信息时就需要我们额外做一下工作比如在<>里添加更多的类型信息,
相信随着Java的进化这些便利的功能会越来越强大。
<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->
____
<!-- --------------------------------------------------- 反射获得方法参数信息------------------------------------------------- -->
<span id="MethodParameterReflection"></span>
##              通过反射获得方法的参数信息
JDK8之前 .class文件是不会存储方法参数信息的因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么当然就是Class类了)。即是是在JDK11里
也不会默认生成这些信息可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器可以把java文件编译成.class文件)。生成额外
的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如passwordJDK文档里这么说的)并且确实很多信息javac并不会为我们生成比如
LocalVariableTablejavac就不会默认生成需要你加上 -g:vars来强制让编译器生成同样的方法参数信息也需要加上
-parameters来让javac为你在.class文件中生成这些信息否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前先看一下javac加上该
参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
```java
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
}
```
先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
```java
//只截取了部分信息
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 5: 0
//这个方法的描述到这里就结束了
```
接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
```java
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 8: 0
MethodParameters:
Name Flags
canUGetMyName
yesICan
```
可以看到.class文件里多了一个MethodParameters信息这就是参数的名字可以看到默认是不保存的。
<br>下面看一下在Intelj Idea里运行的这个例子我们试一下通过反射获取方法名 :
```java
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> clazz = ByteCodeParameters.class;
Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
Parameter[] parameters = simple.getParameters();
for (Parameter p : parameters) {
System.out.println(p.getName());
}
}
}
输出 :
arg0
arg1
```
说好的方法名呢别急哈哈。前面说了默认是不生成参数名信息的因此我们需要做一些配置我们找到IDEA的settings里的Java Compiler选项
Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
己编译一个.class文件放在IDEA的out下然后再来运行 :
```java
输出 :
canUGetMyName
yesICan
```
这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
<br>
## 总结与补充
在JDK8之后可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息其实在SpringFramework
里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。
<!-- --------------------------------------------------- 反射获得方法参数信息结束------------------------------------------------- -->
____
<!-- --------------------------------------------------- JDK8流库------------------------------------------------------------- -->
<span id="stream"></span>
<!-- --------------------------------------------------- JDK8流库结束------------------------------------------------- -->
___

View File

@ -0,0 +1,75 @@
Stream API 旨在让编码更高效率、干净、简洁。
### 从迭代器到Stream操作
当使用 `Stream` 时,我们一般会通过三个阶段建立一个流水线:
1. 创建一个 `Stream`
2. 进行一个或多个中间操作;
3. 使用终止操作产生一个结果,`Stream` 就不会再被使用了。
**案例1统计 List 中的单词长度大于6的个数**
```java
/**
* 案例1统计 List 中的单词长度大于6的个数
*/
ArrayList<String> wordsList = new ArrayList<String>();
wordsList.add("Charles");
wordsList.add("Vincent");
wordsList.add("William");
wordsList.add("Joseph");
wordsList.add("Henry");
wordsList.add("Bill");
wordsList.add("Joan");
wordsList.add("Linda");
int count = 0;
```
Java8之前我们通常用迭代方法来完成上面的需求
```java
//迭代Java8之前的常用方法
//迭代不好的地方1. 代码多2 很难被并行运算。
for (String word : wordsList) {
if (word.length() > 6) {
count++;
}
}
System.out.println(count);//3
```
Java8之前我们使用 `Stream` 一行代码就能解决了,而且可以瞬间转换为并行执行的效果:
```java
//Stream
//将stream()改为parallelStream()就可以瞬间将代码编程并行执行的效果
long count2=wordsList.stream()
.filter(w->w.length()>6)
.count();
long count3=wordsList.parallelStream()
.filter(w->w.length()>6)
.count();
System.out.println(count2);
System.out.println(count3);
```
### `distinct()`
去除 List 中重复的 String
```java
List<String> list = list.stream()
.distinct()
.collect(Collectors.toList());
```
### `map`
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
```java
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取 List 中每个元素对应的平方数并去重
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
System.out.println(squaresList.toString());//[9, 4, 49, 25]
```

View File

@ -0,0 +1,30 @@
##              改进的类型推断
### 1.什么是类型推断
类型推断就像它的字面意思一样,编译器根据<b><i>你显示声明的已知的信息</i></b> 推断出你没有显示声明的类型,这就是类型推断。
看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
```java
Map<String, Object> map = new Map<String, Object>();
```
而我们平常写的都是这样的:
```java
Map<String, Object> map = new Map<>();
```
这就是类型推断《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断)在Java编程思想里Bruce Eckel大叔还提到过这个问题
(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断☺)在JDK7中推出了上面这样的类型推断可以减少一些无用的代码。
(Java编程思想到现在还只有第四版是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢/滑稽)
<br>
在JDK7中类型推断只有上面例子的那样的能力即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息)下面的官方文档里的例子在JDK7里会编译
错误
```java
List<String> stringList = new ArrayList<>();
stringList.add("A");
//error : addAll(java.util.Collection<? extends java.lang.String>)in List cannot be applied to (java.util.List<java.lang.Object>)
stringList.addAll(Arrays.asList());
```
但是上面的代码在JDK8里可以通过也就说JDK8里类型推断不仅可以用于赋值语句而且可以根据代码中上下文里的信息推断出更多的信息因此我们需要些的代码
会更少。加强的类型推断还有一个就是用于Lambda表达式了。
<br>
大家其实不必细究类型推断在日常使用中IDE会自动判断当IDE自己无法推断出足够的信息时就需要我们额外做一下工作比如在<>里添加更多的类型信息,
相信随着Java的进化这些便利的功能会越来越强大。
<!-- --------------------------------------------------- 改进的类型推断结束------------------------------------------------- -->

View File

@ -0,0 +1,79 @@
##              通过反射获得方法的参数信息
JDK8之前 .class文件是不会存储方法参数信息的因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么当然就是Class类了)。即是是在JDK11里
也不会默认生成这些信息可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器可以把java文件编译成.class文件)。生成额外
的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如passwordJDK文档里这么说的)并且确实很多信息javac并不会为我们生成比如
LocalVariableTablejavac就不会默认生成需要你加上 -g:vars来强制让编译器生成同样的方法参数信息也需要加上
-parameters来让javac为你在.class文件中生成这些信息否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前先看一下javac加上该
参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
```java
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
}
```
先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
```java
//只截取了部分信息
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 5: 0
//这个方法的描述到这里就结束了
```
接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
```java
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 8: 0
MethodParameters:
Name Flags
canUGetMyName
yesICan
```
可以看到.class文件里多了一个MethodParameters信息这就是参数的名字可以看到默认是不保存的。
<br>下面看一下在Intelj Idea里运行的这个例子我们试一下通过反射获取方法名 :
```java
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> clazz = ByteCodeParameters.class;
Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
Parameter[] parameters = simple.getParameters();
for (Parameter p : parameters) {
System.out.println(p.getName());
}
}
}
输出 :
arg0
arg1
```
说好的方法名呢别急哈哈。前面说了默认是不生成参数名信息的因此我们需要做一些配置我们找到IDEA的settings里的Java Compiler选项
Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
己编译一个.class文件放在IDEA的out下然后再来运行 :
```java
输出 :
canUGetMyName
yesICan
```
这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
<br>
## 总结与补充
在JDK8之后可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息其实在SpringFramework
里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。

View File

@ -145,7 +145,7 @@
} }
``` ```
**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!** 记清楚了!不是网上很多人说的 1.5 倍+1 **int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!JDK1.6版本以后)** JDk1.6版本时,扩容之后容量为 1.5 倍+1详情请参考源码
> ">>"(移位运算符):>>1 右移一位相当于除2右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   > ">>"(移位运算符):>>1 右移一位相当于除2右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源  
@ -270,7 +270,6 @@ public class ArrayscopyOfTest {
10 10
``` ```
### 3.3 两者联系和区别 ### 3.3 两者联系和区别
**联系:** **联系:**
@ -281,8 +280,6 @@ public class ArrayscopyOfTest {
`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 `arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。
## 四 `ensureCapacity`方法 ## 四 `ensureCapacity`方法
ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?
@ -341,7 +338,6 @@ public class EnsureCapacityTest {
``` ```
使用ensureCapacity方法前4637 使用ensureCapacity方法前4637
使用ensureCapacity方法后241 使用ensureCapacity方法后241
``` ```
通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数 通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数

View File

@ -660,7 +660,7 @@ public class ArrayList<E> extends AbstractList<E>
(3)private class SubList extends AbstractList<E> implements RandomAccess (3)private class SubList extends AbstractList<E> implements RandomAccess
(4)static final class ArrayListSpliterator<E> implements Spliterator<E> (4)static final class ArrayListSpliterator<E> implements Spliterator<E>
``` ```
  ArrayList有四个内部类其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()****next()****remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()****nextIndex()****previousIndex()****previous()****set(E e)****add(E e)**等方法,所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象修改对象逆向遍历等方法这些是Iterator不能实现的。   ArrayList有四个内部类其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()** **next()** **remove()** 等方法;其中的**ListItr** 继承 **Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()** **nextIndex()** **previousIndex()** **previous()** **set(E e)** **add(E e)** 等方法,所以这也可以看出了 **Iterator和ListIterator的区别:** ListIterator在Iterator的基础上增加了添加对象修改对象逆向遍历等方法这些是Iterator不能实现的。
### <font face="楷体" id="6"> ArrayList经典Demo</font> ### <font face="楷体" id="6"> ArrayList经典Demo</font>
```java ```java

View File

@ -235,7 +235,7 @@ HashMap只提供了put用于添加元素putVal方法只是给put方法调用
**对putVal方法添加元素的分析如下** **对putVal方法添加元素的分析如下**
- ①如果定位到的数组位置没有元素 就直接插入。 - ①如果定位到的数组位置没有元素 就直接插入。
- ②如果定位到的数组位置有元素就和要插入的key比较如果key相同就直接覆盖如果key不相同就判断p是否是一个树节点如果是就调用`e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入。 - ②如果定位到的数组位置有元素就和要插入的key比较如果key相同就直接覆盖如果key不相同就判断p是否是一个树节点如果是就调用`e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)

View File

@ -0,0 +1,456 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [剖析面试最常见问题之Java基础知识](#剖析面试最常见问题之java基础知识)
- [说说List,Set,Map三者的区别](#说说listsetmap三者的区别)
- [Arraylist 与 LinkedList 区别?](#arraylist-与-linkedlist-区别)
- [补充内容:RandomAccess接口](#补充内容randomaccess接口)
- [补充内容:双向链表和双向循环链表](#补充内容双向链表和双向循环链表)
- [ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢](#arraylist-与-vector-区别呢为什么要用arraylist取代vector呢)
- [说一说 ArrayList 的扩容机制吧](#说一说-arraylist-的扩容机制吧)
- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
- [HashMap 和 HashSet区别](#hashmap-和-hashset区别)
- [HashSet如何检查重复](#hashset如何检查重复)
- [HashMap的底层实现](#hashmap的底层实现)
- [JDK1.8之前](#jdk18之前)
- [JDK1.8之后](#jdk18之后)
- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)
- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)
- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)
- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)
- [JDK1.7(上面有示意图)](#jdk17上面有示意图)
- [JDK1.8 (上面有示意图)](#jdk18-上面有示意图)
- [comparable 和 Comparator的区别](#comparable-和-comparator的区别)
- [Comparator定制排序](#comparator定制排序)
- [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)
- [集合框架底层数据结构总结](#集合框架底层数据结构总结)
- [Collection](#collection)
- [1. List](#1-list)
- [2. Set](#2-set)
- [Map](#map)
- [如何选用集合?](#如何选用集合)
<!-- /TOC -->
# 剖析面试最常见问题之Java基础知识
## 说说List,Set,Map三者的区别
- **List(对付顺序的好帮手)** List接口存储一组不唯一可以有多个元素引用相同的对象有序的对象
- **Set(注重独一无二的性质):** 不允许重复的集合。不会有多个元素引用相同的对象。
- **Map(用Key来搜索的专家):** 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象但Key不能重复典型的Key是String类型但也可以是任何对象。
## Arraylist 与 LinkedList 区别?
- **1. 是否保证线程安全:** `ArrayList``LinkedList` 都是不同步的,也就是不保证线程安全;
- **2. 底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**`LinkedList` 底层使用的是 **双向链表** 数据结构JDK1.6之前为循环链表JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
- **3. 插入和删除是否受元素位置的影响:****`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O1而数组为近似 On。**
- **4. 是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间因为要存放直接后继和直接前驱以及数据
### **补充内容:RandomAccess接口**
```java
public interface RandomAccess {
}
```
查看源码我们发现实际上 `RandomAccess` 接口中什么都没有定义。所以,在我看来 `RandomAccess` 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
`binarySearch`方法中它要判断传入的list 是否 `RamdomAccess` 的实例,如果是,调用`indexedBinarySearch`方法,如果不是,那么调用`iteratorBinarySearch`方法
```java
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
```
`ArrayList` 实现了 `RandomAccess` 接口, 而 `LinkedList` 没有实现。为什么呢?我觉得还是和底层数据结构有关!`ArrayList` 底层是数组,而 `LinkedList` 底层是链表。数组天然支持随机访问,时间复杂度为 O1所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素时间复杂度为 On所以不支持快速随机访问。`ArrayList` 实现了 `RandomAccess` 接口,就表明了他具有快速随机访问功能。 `RandomAccess` 接口只是标识,并不是说 `ArrayList` 实现 `RandomAccess` 接口才具有快速随机访问功能的!
**下面再总结一下 list 的遍历方式选择:**
- 实现了 `RandomAccess` 接口的list优先选择普通 for 循环 ,其次 foreach,
- 未实现 `RandomAccess`接口的list优先选择iterator遍历foreach遍历底层也是通过iterator实现的,大size的数据千万不要使用普通for循环
### 补充内容:双向链表和双向循环链表
**双向链表:** 包含两个指针一个prev指向前一个节点一个next指向后一个节点。
![双向链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向链表.png)
**双向循环链表:** 最后一个节点的 next 指向head而 head 的prev指向最后一个节点构成一个环。
![双向循环链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向循环链表.png)
## ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢
`Vector`类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
`Arraylist`不是同步的所以在不需要保证线程安全时建议使用Arraylist。
## 说一说 ArrayList 的扩容机制吧
详见笔主的这篇文章:[通过源码一步一步分析ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList-Grow.md)
## HashMap 和 Hashtable 的区别
1. **线程是否安全:** HashMap 是非线程安全的HashTable 是线程安全的HashTable 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
2. **效率:** 因为线程安全的问题HashMap 要比 HashTable 效率高一点。另外HashTable 基本被淘汰,不要在代码中使用它;
3. **对Null key 和Null value的支持** HashMap 中null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null直接抛出 NullPointerException。
4. **初始容量大小和每次扩充容量大小的不同 ** ①创建时如果不指定容量初始值Hashtable 默认的初始大小为11之后每次扩充容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充容量变为原来的2倍。②创建时如果给定了容量初始值那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。Hashtable 没有这样的机制。
**HasMap 中带有初始容量的构造函数:**
```java
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
```
下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
```java
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
```
## HashMap 和 HashSet区别
如果你看过 `HashSet` 源码的话就应该知道HashSet 底层就是基于 HashMap 实现的。HashSet 的源码非常非常少,因为除了 `clone() ``writeObject()``readObject()`是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
| HashMap | HashSet |
| :------------------------------: | :----------------------------------------------------------: |
| 实现了Map接口 | 实现Set接口 |
| 存储键值对 | 仅存储对象 |
| 调用 `put`向map中添加元素 | 调用 `add`方法向Set中添加元素 |
| HashMap使用键Key计算Hashcode | HashSet使用成员对象来计算hashcode值对于两个对象来说hashcode可能相同所以equals()方法用来判断对象的相等性, |
## HashSet如何检查重复
当你把对象加入`HashSet`HashSet会先计算对象的`hashcode`值来判断对象加入的位置同时也会与其他加入的对象的hashcode值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象这时会调用`equals`方法来检查hashcode相等的对象是否真的相同。如果两者相同HashSet就不会让加入操作成功。摘自我的Java启蒙书《Head fist java》第二版
**hashCode与equals的相关规定**
1. 如果两个对象相等则hashcode一定也是相同的
2. 两个对象相等,对两个equals方法返回true
3. 两个对象有相同的hashcode值它们也不一定是相等的
4. 综上equals方法被覆盖过则hashCode方法也必须被覆盖
5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()则该class的两个对象无论如何都不会相等即使这两个对象指向相同的数据
**==与equals的区别**
1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
3. ==指引用是否相同 equals()指的是值是否相同
## HashMap的底层实现
### JDK1.8之前
JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
**JDK 1.8 HashMap 的 hash 方法源码:**
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
```java
static final int hash(Object key) {
int h;
// key.hashCode()返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移忽略符号位空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```
对比一下 JDK1.7的 HashMap 的 hash 方法源码.
```java
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
```
相比于 JDK1.8 的 hash 方法 JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
![jdk1.8之前的内部结构-HashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/jdk1.8之前的内部结构-HashMap.jpg)
### JDK1.8之后
相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间。
![jdk1.8之后的内部结构-HashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JDK1.8之后的HashMap底层数据结构.jpg)
> TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷因为二叉查找树在某些情况下会退化成一个线性结构。
**推荐阅读:**
- 《Java 8系列之重新认识HashMap》 <https://zhuanlan.zhihu.com/p/21673805>
## 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的幂次方。**
## HashMap 多线程操作导致死循环问题
主要原因在于 并发下的Rehash 会造成元素之间会形成一个循环链表。不过jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。
详情请查看:<https://coolshell.cn/articles/9606.html>
## ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- **实现线程安全的方式(重要):****在JDK1.7的时候ConcurrentHashMap分段锁** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。JDK1.6以后 对 synchronized锁做了很多优化** 整个看起来就像是优化过且线程安全的 HashMap虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get竞争会越来越激烈效率越低。
**两者的对比图:**
图片来源:<http://www.cnblogs.com/chengxiao/p/6842045.html>
**HashTable:**
![HashTable全表锁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HashTable全表锁.png)
**JDK1.7的ConcurrentHashMap**
![JDK1.7的ConcurrentHashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ConcurrentHashMap分段锁.jpg)
**JDK1.8的ConcurrentHashMapTreeBin: 红黑二叉树节点 Node: 链表节点):**
![JDK1.8的ConcurrentHashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JDK1.8-ConcurrentHashMap-Structure.jpg)
## ConcurrentHashMap线程安全的具体实现方式/底层具体实现
### JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁扮演锁的角色。HashEntry 用于存储键值对数据。
```java
static class Segment<K,V> extends ReentrantLock implements Serializable {
}
```
一个 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倍。
## comparable 和 Comparator的区别
- comparable接口实际上是出自java.lang包 它有一个 `compareTo(Object obj)`方法用来排序
- comparator接口实际上是出自 java.util 包它有一个`compare(Object obj1, Object obj2)`方法用来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写`compareTo()`方法或`compare()`方法当我们需要对某一个集合实现两种排序方式比如一个song对象中的歌名和歌手名分别采用一种排序方法的话我们可以重写`compareTo()`方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序第二种代表我们只能使用两个参数版的 `Collections.sort()`.
### Comparator定制排序
```java
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始数组:");
System.out.println(arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// 定制排序的用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
```
Output:
```
原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9]
```
### 重写compareTo方法实现按年龄来排序
```java
// person对象没有实现Comparable接口所以必须实现这样才不会出错才可以使treemap中的数据按顺序排列
// 前面一个例子的String类已经默认实现了Comparable接口详细可以查看String类的API文档另外其他
// 像Integer类等都已经实现了Comparable接口所以不需要另外实现了
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* TODO重写compareTo方法实现按年龄来排序
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
if (this.age > o.getAge()) {
return 1;
} else if (this.age < o.getAge()) {
return -1;
}
return age;
}
}
```
```java
public static void main(String[] args) {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("小红", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
```
Output
```
5-小红
10-王五
20-李四
30-张三
```
## 集合框架底层数据结构总结
### Collection
#### 1. List
- **Arraylist** Object数组
- **Vector** Object数组
- **LinkedList** 双向链表(JDK1.6之前为循环链表JDK1.7取消了循环)
#### 2. Set
- **HashSet无序唯一:** 基于 HashMap 实现的,底层采用 HashMap 来保存元素
- **LinkedHashSet** LinkedHashSet 继承与 HashSet并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
- **TreeSet有序唯一** 红黑树(自平衡的排序二叉树。)
### Map
- **HashMap** JDK1.8之前HashMap由数组+链表组成的数组是HashMap的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突。JDK1.8以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为8将链表转化为红黑树以减少搜索时间
- **LinkedHashMap** LinkedHashMap 继承自 HashMap所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析JDK1.8)》](https://www.imooc.com/article/22931)
- **Hashtable** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- **TreeMap** 红黑树(自平衡的排序二叉树)
## 如何选用集合?
主要根据集合的特点来选用比如我们需要根据键值获取到元素值时就选用Map接口下的集合需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时就选择实现Collection接口的集合需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet不需要就选择实现List接口的比如ArrayList或LinkedList然后再根据实现这些接口的集合的特点来选用。
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -186,7 +186,7 @@ public void addLast(E e) {
} }
``` ```
### <font face="楷体" id="3.3">根据位置取数据的方法</font> ### <font face="楷体" id="3.3">根据位置取数据的方法</font>
**get(int index)**根据指定索引返回数据 **get(int index)** 根据指定索引返回数据
```java ```java
public E get(int index) { public E get(int index) {
//检查index范围是否在size之内 //检查index范围是否在size之内
@ -288,7 +288,7 @@ public int lastIndexOf(Object o) {
return indexOf(o) != -1; return indexOf(o) != -1;
} }
``` ```
###<font face="楷体" id="3.6">删除方法</font> ### <font face="楷体" id="3.6">删除方法</font>
**remove()** ,**removeFirst(),pop():** 删除头节点 **remove()** ,**removeFirst(),pop():** 删除头节点
``` ```
public E pop() { public E pop() {
@ -359,7 +359,7 @@ E unlink(Node<E> x) {
//删除前驱指针 //删除前驱指针
if (prev == null) { if (prev == null) {
first = next;如果删除的节点是头节点,令头节点指向该节点的后继节点 first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点
} else { } else {
prev.next = next;//将前驱节点的后继节点指向后继节点 prev.next = next;//将前驱节点的后继节点指向后继节点
x.prev = null; x.prev = null;
@ -458,7 +458,7 @@ public class LinkedListDemo {
linkedList.add(3); linkedList.add(3);
linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表) linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表)
System.out.println("After removeFirstOccurrence(3):" + linkedList); System.out.println("After removeFirstOccurrence(3):" + linkedList);
linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表) linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表)
System.out.println("After removeFirstOccurrence(3):" + linkedList); System.out.println("After removeFirstOccurrence(3):" + linkedList);
/************************** 遍历操作 ************************/ /************************** 遍历操作 ************************/

View File

@ -0,0 +1,337 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [JDK 监控和故障处理工具总结](#jdk-监控和故障处理工具总结)
- [JDK 命令行工具](#jdk-命令行工具)
- [`jps`:查看所有 Java 进程](#jps查看所有-java-进程)
- [`jstat`: 监视虚拟机各种运行状态信息](#jstat-监视虚拟机各种运行状态信息)
- [` jinfo`: 实时地查看和调整虚拟机各项参数](#-jinfo-实时地查看和调整虚拟机各项参数)
- [`jmap`:生成堆转储快照](#jmap生成堆转储快照)
- [**`jhat`**: 分析 heapdump 文件](#jhat-分析-heapdump-文件)
- [**`jstack`** :生成虚拟机当前时刻的线程快照](#jstack-生成虚拟机当前时刻的线程快照)
- [JDK 可视化分析工具](#jdk-可视化分析工具)
- [JConsole:Java 监视与管理控制台](#jconsolejava-监视与管理控制台)
- [连接 Jconsole](#连接-jconsole)
- [查看 Java 程序概况](#查看-java-程序概况)
- [内存监控](#内存监控)
- [线程监控](#线程监控)
- [Visual VM:多合一故障处理工具](#visual-vm多合一故障处理工具)
<!-- /TOC -->
# JDK 监控和故障处理工具总结
## JDK 命令行工具
这些命令在 JDK 安装目录下的 bin 目录下:
- **`jps`** (JVM Process Status: 类似 UNIX 的 `ps` 命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
- **`jstat`** JVM Statistics Monitoring Tool: 用于收集 HotSpot 虚拟机各方面的运行数据;
- **`jinfo`** (Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息;
- **`jmap`** (Memory Map for Java) :生成堆转储快照;
- **`jhat`** (JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;
- **`jstack`** (Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
### `jps`:查看所有 Java 进程
`jps`(JVM Process Status) 命令类似 UNIX 的 `ps` 命令。
`jps`:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 IDLocal Virtual Machine Identifier,LVMID`jps -q` :只输出进程的本地虚拟机唯一 ID。
```powershell
C:\Users\SnailClimb>jps
7360 NettyClient2
17396
7972 Launcher
16504 Jps
17340 NettyServer
```
`jps -l`:输出主类的全名,如果进程执行的是 Jar 包,输出 Jar 路径。
```powershell
C:\Users\SnailClimb>jps -l
7360 firstNettyDemo.NettyClient2
17396
7972 org.jetbrains.jps.cmdline.Launcher
16492 sun.tools.jps.Jps
17340 firstNettyDemo.NettyServer
```
`jps -v`:输出虚拟机进程启动时 JVM 参数。
`jps -m`:输出传递给 Java 进程 main() 函数的参数。
### `jstat`: 监视虚拟机各种运行状态信息
jstatJVM Statistics Monitoring Tool 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程(需要远程主机提供 RMI 支持虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI只提供了纯文本控制台环境的服务器上它将是运行期间定位虚拟机性能问题的首选工具。
**`jstat` 命令使用格式:**
```powershell
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
```
比如 `jstat -gc -h3 31736 1000 10`表示分析进程 id 为 31736 的 gc 情况,每隔 1000ms 打印一次记录,打印 10 次停止,每 3 行后打印指标头部。
**常见的 option 如下:**
- `jstat -class vmid` :显示 ClassLoader 的相关信息;
- `jstat -compiler vmid` :显示 JIT 编译的相关信息;
- `jstat -gc vmid` :显示与 GC 相关的堆信息;
- `jstat -gccapacity vmid` :显示各个代的容量及使用情况;
- `jstat -gcnew vmid` :显示新生代信息;
- `jstat -gcnewcapcacity vmid` :显示新生代大小与使用情况;
- `jstat -gcold vmid` :显示老年代和永久代的信息;
- `jstat -gcoldcapacity vmid` :显示老年代的大小;
- `jstat -gcpermcapacity vmid` :显示永久代大小;
- `jstat -gcutil vmid` :显示垃圾收集信息;
另外,加上 `-t`参数可以在输出信息上加一个 Timestamp 列,显示程序的运行时间。
### ` jinfo`: 实时地查看和调整虚拟机各项参数
`jinfo vmid` :输出当前 jvm 进程的全部参数和系统属性 (第一部分是系统的属性,第二部分是 JVM 的参数)。
`jinfo -flag name vmid` :输出对应名称的参数的具体值。比如输出 MaxHeapSize、查看当前 jvm 进程是否开启打印 GC 日志 ( `-XX:PrintGCDetails` :详细 GC 日志模式,这两个都是默认关闭的)。
```powershell
C:\Users\SnailClimb>jinfo -flag MaxHeapSize 17340
-XX:MaxHeapSize=2124414976
C:\Users\SnailClimb>jinfo -flag PrintGC 17340
-XX:-PrintGC
```
使用 jinfo 可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。尤其在线上的环境特别有用,请看下面的例子:
`jinfo -flag [+|-]name vmid` 开启或者关闭对应名称的参数。
```powershell
C:\Users\SnailClimb>jinfo -flag PrintGC 17340
-XX:-PrintGC
C:\Users\SnailClimb>jinfo -flag +PrintGC 17340
C:\Users\SnailClimb>jinfo -flag PrintGC 17340
-XX:+PrintGC
```
### `jmap`:生成堆转储快照
`jmap`Memory Map for Java命令用于生成堆转储快照。 如果不使用 `jmap` 命令,要想获取 Java 堆转储,可以使用 `“-XX:+HeapDumpOnOutOfMemoryError”` 参数,可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件Linux 命令下可以通过 `kill -3` 发送进程退出信号也能拿到 dump 文件。
`jmap` 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalizer 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前使用的是哪种收集器等。和`jinfo`一样,`jmap`有不少功能在 Windows 平台下也是受限制的。
示例:将指定应用程序的堆快照输出到桌面。后面,可以通过 jhat、Visual VM 等工具分析该堆文件。
```powershell
C:\Users\SnailClimb>jmap -dump:format=b,file=C:\Users\SnailClimb\Desktop\heap.hprof 17340
Dumping heap to C:\Users\SnailClimb\Desktop\heap.hprof ...
Heap dump file created
```
### **`jhat`**: 分析 heapdump 文件
**`jhat`** 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。
```powershell
C:\Users\SnailClimb>jhat C:\Users\SnailClimb\Desktop\heap.hprof
Reading from C:\Users\SnailClimb\Desktop\heap.hprof...
Dump file created Sat May 04 12:30:31 CST 2019
Snapshot read, resolving...
Resolving 131419 objects...
Chasing references, expect 26 dots..........................
Eliminating duplicate references..........................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
```
访问 <http://localhost:7000/>
### **`jstack`** :生成虚拟机当前时刻的线程快照
`jstack`Stack Trace for Java命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合.
生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过`jstack`来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
**下面是一个线程死锁的代码。我们下面会通过 `jstack` 命令进行死锁检查,输出死锁信息,找到发生死锁的线程。**
```java
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
```
Output
```
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1
```
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
**通过 `jstack` 命令分析:**
```powershell
C:\Users\SnailClimb>jps
13792 KotlinCompileDaemon
7360 NettyClient2
17396
7972 Launcher
8932 Launcher
9256 DeadLockDemo
10764 Jps
17340 NettyServer
C:\Users\SnailClimb>jstack 9256
```
输出的部分内容如下:
```powershell
Found one Java-level deadlock:
=============================
"线程 2":
waiting to lock monitor 0x000000000333e668 (object 0x00000000d5efe1c0, a java.lang.Object),
which is held by "线程 1"
"线程 1":
waiting to lock monitor 0x000000000333be88 (object 0x00000000d5efe1d0, a java.lang.Object),
which is held by "线程 2"
Java stack information for the threads listed above:
===================================================
"线程 2":
at DeadLockDemo.lambda$main$1(DeadLockDemo.java:31)
- waiting to lock <0x00000000d5efe1c0> (a java.lang.Object)
- locked <0x00000000d5efe1d0> (a java.lang.Object)
at DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"线程 1":
at DeadLockDemo.lambda$main$0(DeadLockDemo.java:16)
- waiting to lock <0x00000000d5efe1d0> (a java.lang.Object)
- locked <0x00000000d5efe1c0> (a java.lang.Object)
at DeadLockDemo$$Lambda$1/1324119927.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
```
可以看到 `jstack` 命令已经帮我们找到发生死锁的线程的具体信息。
## JDK 可视化分析工具
### JConsole:Java 监视与管理控制台
JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监视本地及远程服务器的 java 进程的内存使用情况。你可以在控制台输出`console`命令启动或者在 JDK 目录下的 bin 目录找到`jconsole.exe`然后双击启动。
#### 连接 Jconsole
![连接 Jconsole](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/1JConsole连接.png)
如果需要使用 JConsole 连接远程进程,可以在远程 Java 程序启动时加上下面这些参数:
```properties
-Djava.rmi.server.hostname=外网访问 ip 地址
-Dcom.sun.management.jmxremote.port=60001 //监控的端口号
-Dcom.sun.management.jmxremote.authenticate=false //关闭认证
-Dcom.sun.management.jmxremote.ssl=false
```
在使用 JConsole 连接时,远程进程地址如下:
```
外网访问 ip 地址:60001
```
#### 查看 Java 程序概况
![查看 Java 程序概况 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/2查看Java程序概况.png)
#### 内存监控
JConsole 可以显示当前内存的详细信息。不仅包括堆内存/非堆内存的整体信息,还可以细化到 eden 区、survivor 区等的使用情况,如下图所示。
点击右边的“执行 GC(G)”按钮可以强制应用程序执行一个 Full GC。
> - **新生代 GCMinor GC**:指发生新生代的的垃圾收集动作Minor GC 非常频繁,回收速度一般也比较快。
> - **老年代 GCMajor GC/Full GC**:指发生在老年代的 GC出现了 Major GC 经常会伴随至少一次的 Minor GC并非绝对Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
![内存监控 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/3内存监控.png)
#### 线程监控
类似我们前面讲的 `jstack` 命令,不过这个是可视化的。
最下面有一个"检测死锁 (D)"按钮,点击这个按钮可以自动为你找到发生死锁的线程以及它们的详细信息 。
![线程监控 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/4线程监控.png)
### Visual VM:多合一故障处理工具
VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。在 VisualVM 的图形用户界面中,您可以方便、快捷地查看多个 Java 应用程序的相关信息。Visual VM 官网:<https://visualvm.github.io/> 。Visual VM 中文文档:<https://visualvm.github.io/documentation.html>
下面这段话摘自《深入理解 Java 虚拟机》。
> VisualVMAll-in-One Java Troubleshooting Tool是到目前为止随 JDK 发布的功能最强大的运行监视和故障处理程序,官方在 VisualVM 的软件说明中写上了“All-in-One”的描述字样预示着他除了运行监视、故障处理外还提供了很多其他方面的功能如性能分析Profiling。VisualVM 的性能分析功能甚至比起 JProfiler、YourKit 等专业且收费的 Profiling 工具都不会逊色多少,而且 VisualVM 还有一个很大的优点:不需要被监视的程序基于特殊 Agent 运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是 JProfiler、YourKit 等工具无法与之媲美的。
VisualVM 基于 NetBeans 平台开发因此他一开始就具备了插件扩展功能的特性通过插件扩展支持VisualVM 可以做到:
- **显示虚拟机进程以及进程的配置、环境信息jps、jinfo。**
- **监视应用程序的 CPU、GC、堆、方法区以及线程的信息jstat、jstack。**
- **dump 以及分析堆转储快照jmap、jhat。**
- **方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。**
- **离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。**
- **其他 plugins 的无限的可能性......**
这里就不具体介绍 VisualVM 的使用,如果想了解的话可以看:
- <https://visualvm.github.io/documentation.html>
- <https://www.ibm.com/developerworks/cn/java/j-lo-visualvm/index.html>
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,414 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [JVM 垃圾回收](#jvm-垃圾回收)
- [写在前面](#写在前面)
- [本节常见面试题](#本节常见面试题)
- [本文导火索](#本文导火索)
- [1 揭开 JVM 内存分配与回收的神秘面纱](#1--揭开-jvm-内存分配与回收的神秘面纱)
- [1.1 对象优先在 eden 区分配](#11-对象优先在-eden-区分配)
- [1.2 大对象直接进入老年代](#12-大对象直接进入老年代)
- [1.3 长期存活的对象将进入老年代](#13-长期存活的对象将进入老年代)
- [1.4 动态对象年龄判定](#14-动态对象年龄判定)
- [2 对象已经死亡?](#2-对象已经死亡)
- [2.1 引用计数法](#21-引用计数法)
- [2.2 可达性分析算法](#22-可达性分析算法)
- [2.3 再谈引用](#23-再谈引用)
- [2.4 不可达的对象并非“非死不可”](#24-不可达的对象并非非死不可)
- [2.5 如何判断一个常量是废弃常量](#25-如何判断一个常量是废弃常量)
- [2.6 如何判断一个类是无用的类](#26-如何判断一个类是无用的类)
- [3 垃圾收集算法](#3-垃圾收集算法)
- [3.1 标记-清除算法](#31-标记-清除算法)
- [3.2 复制算法](#32-复制算法)
- [3.3 标记-整理算法](#33-标记-整理算法)
- [3.4 分代收集算法](#34-分代收集算法)
- [4 垃圾收集器](#4-垃圾收集器)
- [4.1 Serial 收集器](#41-serial-收集器)
- [4.2 ParNew 收集器](#42-parnew-收集器)
- [4.3 Parallel Scavenge 收集器](#43-parallel-scavenge-收集器)
- [4.4.Serial Old 收集器](#44serial-old-收集器)
- [4.5 Parallel Old 收集器](#45-parallel-old-收集器)
- [4.6 CMS 收集器](#46-cms-收集器)
- [4.7 G1 收集器](#47-g1-收集器)
- [参考](#参考)
<!-- /TOC -->
# JVM 垃圾回收
## 写在前面
### 本节常见面试题
问题答案在文中都有提到
- 如何判断对象是否死亡(两种方法)。
- 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
- 如何判断一个常量是废弃常量
- 如何判断一个类是无用的类
- 垃圾收集有哪些算法,各自的特点?
- HotSpot 为什么要分为新生代和老年代?
- 常见的垃圾回收器有那些?
- 介绍一下 CMS,G1 收集器。
- Minor Gc 和 Full GC 有什么不同呢?
### 本文导火索
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg)
当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
## 1 揭开 JVM 内存分配与回收的神秘面纱
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。
Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆Garbage Collected Heap**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为新生代和老年代再细致一点有Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
**堆空间的基本结构:**
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3堆结构.png" width="400px"/>
</div>
上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。经过这次GC后Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”新的"From"就是上次GC前的"To"。不管怎样都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程直到“To”区被填满"To"区被填满之后,会将所有对象移动到年老代中。
![堆内存常见分配策略 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/堆内存.jpg)
### 1.1 对象优先在 eden 区分配
目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。
在测试之前我们先来看看 **Minor GC 和 Full GC 有什么不同呢?**
- **新生代 GCMinor GC**:指发生新生代的的垃圾收集动作Minor GC 非常频繁,回收速度一般也比较快。
- **老年代 GCMajor GC/Full GC**:指发生在老年代的 GC出现了 Major GC 经常会伴随至少一次的 Minor GC并非绝对Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
**测试:**
```java
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2;
allocation1 = new byte[30900*1024];
//allocation2 = new byte[900*1024];
}
}
```
通过以下方式运行:
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg)
添加的参数:`-XX:+PrintGCDetails`
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg)
运行结果 (红色字体描述有误,应该是对应于 JDK1.7 的永久代)
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg)
从上图我们可以看出 eden 区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用 2000 多 k 内存)。假如我们再为 allocation2 分配内存会出现什么情况呢?
```java
allocation2 = new byte[900*1024];
```
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg)
**简单解释一下为什么会出现这种情况:** 因为给 allocation2 分配内存的时候 eden 区内存几乎已经被分配完了,我们刚刚讲了当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.GC 期间虚拟机又发现 allocation1 无法存入 Survivor 空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 eden 区的话,还是会在 eden 区分配内存。可以执行如下代码验证:
```java
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
allocation1 = new byte[32000*1024];
allocation2 = new byte[1000*1024];
allocation3 = new byte[1000*1024];
allocation4 = new byte[1000*1024];
allocation5 = new byte[1000*1024];
}
}
```
### 1.2 大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
**为什么要这样呢?**
为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
### 1.3 长期存活的对象将进入老年代
既然虚拟机采用了分代收集的思想来管理内存那么内存回收时就必须能识别哪些对象应放在新生代哪些对象应放在老年代中。为了做到这一点虚拟机给每个对象一个对象年龄Age计数器。
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
### 1.4 动态对象年龄判定
为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。
## 2 对象已经死亡?
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg)
### 2.1 引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1当引用失效计数器就减 1任何时候计数器为 0 的对象就是不可能再被使用的。
**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0于是引用计数算法无法通知 GC 回收器回收他们。
```java
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
```
### 2.2 可达性分析算法
这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
![可达性分析算法 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg)
### 2.3 再谈引用
无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
JDK1.2 之前Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2 以后Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
**1强引用**
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空 间不足Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
**2软引用SoftReference**
如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列ReferenceQueue联合使用如果软引用所引用的对象被垃圾回收JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
**3弱引用WeakReference**
如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列ReferenceQueue联合使用如果弱引用所引用的对象被垃圾回收Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
**4虚引用PhantomReference**
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
**虚引用主要用来跟踪对象被垃圾回收的活动**。
**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列ReferenceQueue联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速 JVM 对垃圾内存的回收速度可以维护系统的运行安全防止内存溢出OutOfMemory等问题的产生**。
### 2.4 不可达的对象并非“非死不可”
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
### 2.5 如何判断一个常量是废弃常量
运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
注意:我们在 [可能是把 Java 内存区域讲的最清楚的一篇文章 ](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了 JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆Heap中开辟了一块区域存放运行时常量池。
### 2.6 如何判断一个类是无用的类
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 **“无用的类”**
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
## 3 垃圾收集算法
![垃圾收集算法分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集算法.jpg)
### 3.1 标记-清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
1. **效率问题**
2. **空间问题(标记清除后会产生大量不连续的碎片)**
<img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg" alt="公众号" width="500px">
### 3.2 复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
<img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg" alt="公众号" width="500px">
### 3.3 标记-整理算法
根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
![标记-整理算法 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg)
### 3.4 分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。**
**延伸面试问题:** HotSpot 为什么要分为新生代和老年代?
根据上面的对分代收集算法的介绍回答。
## 4 垃圾收集器
![垃圾收集器分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集器.jpg)
**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。
### 4.1 Serial 收集器
Serial串行收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。
**新生代采用复制算法,老年代采用标记-整理算法。**
![ Serial 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg)
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial 收集器由于没有线程交互的开销自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
### 4.2 ParNew 收集器
**ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。**
**新生代采用复制算法,老年代采用标记-整理算法。**
![ParNew 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
**并行和并发概念补充:**
- **并行Parallel** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- **并发Concurrent**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。
### 4.3 Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用复制算法的多线程收集器它看上去几乎和ParNew都一样。 **那么它有什么特别之处呢?**
```
-XX:+UseParallelGC
使用 Parallel 收集器+ 老年代串行
-XX:+UseParallelOldGC
使用 Parallel 收集器+ 老年代并行
```
**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
**新生代采用复制算法,老年代采用标记-整理算法。**
![Parallel Scavenge 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
### 4.4.Serial Old 收集器
**Serial 收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
### 4.5 Parallel Old 收集器
**Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
### 4.6 CMS 收集器
**CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。**
**CMSConcurrent Mark Sweep收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。**
从名字中的**Mark Sweep**这两个词可以看出CMS 收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- **初始标记:** 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快
- **并发标记:** 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- **并发清除:** 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。
![CMS 垃圾收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg)
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点:
- **对 CPU 资源敏感;**
- **无法处理浮动垃圾;**
- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**
### 4.7 G1 收集器
**G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.**
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
- **并行与并发**G1 能充分利用 CPU、多核环境下的硬件优势使用多个 CPUCPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- **分代收集**:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- **空间整合**:与 CMS 的“标记--清理”算法不同G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
- **可预测的停顿**:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器的运作大致分为以下几个步骤:
- **初始标记**
- **并发标记**
- **最终标记**
- **筛选回收**
**G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)**。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 GF 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
## 参考
- 《深入理解 Java 虚拟机JVM 高级特性与最佳实践(第二版》
- https://my.oschina.net/hosee/blog/644618
- <https://docs.oracle.com/javase/specs/jvms/se8/html/index.html>
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,433 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [Java 内存区域详解](#java-内存区域详解)
- [写在前面 (常见面试题)](#写在前面-常见面试题)
- [基本问题](#基本问题)
- [拓展问题](#拓展问题)
- [一 概述](#一-概述)
- [二 运行时数据区域](#二-运行时数据区域)
- [2.1 程序计数器](#21-程序计数器)
- [2.2 Java 虚拟机栈](#22-java-虚拟机栈)
- [2.3 本地方法栈](#23-本地方法栈)
- [2.4 堆](#24-堆)
- [2.5 方法区](#25-方法区)
- [2.5.1 方法区和永久代的关系](#251-方法区和永久代的关系)
- [2.5.2 常用参数](#252-常用参数)
- [2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?](#253-为什么要将永久代-permgen-替换为元空间-metaspace-呢)
- [2.6 运行时常量池](#26-运行时常量池)
- [2.7 直接内存](#27-直接内存)
- [三 HotSpot 虚拟机对象探秘](#三-hotspot-虚拟机对象探秘)
- [3.1 对象的创建](#31-对象的创建)
- [Step1:类加载检查](#step1类加载检查)
- [Step2:分配内存](#step2分配内存)
- [Step3:初始化零值](#step3初始化零值)
- [Step4:设置对象头](#step4设置对象头)
- [Step5:执行 init 方法](#step5执行-init-方法)
- [3.2 对象的内存布局](#32-对象的内存布局)
- [3.3 对象的访问定位](#33-对象的访问定位)
- [四 重点补充内容](#四--重点补充内容)
- [4.1 String 类和常量池](#41-string-类和常量池)
- [4.2 String s1 = new String("abc");这句话创建了几个字符串对象?](#42-string-s1--new-stringabc这句话创建了几个字符串对象)
- [4.3 8 种基本类型的包装类和常量池](#43-8-种基本类型的包装类和常量池)
- [参考](#参考)
<!-- /TOC -->
# Java 内存区域详解
如果没有特殊说明,都是针对的是 HotSpot 虚拟机。
## 写在前面 (常见面试题)
### 基本问题
- **介绍下 Java 内存区域(运行时数据区)**
- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)**
- **对象的访问定位的两种方式(句柄和直接指针两种方式)**
### 拓展问题
- **String 类和常量池**
- **8 种基本类型的包装类和常量池**
## 一 概述
对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
## 二 运行时数据区域
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。
**JDK 1.8 之前:**
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png" width="600px"/>
</div>
**JDK 1.8 **
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java运行时数据区域JDK1.8.png" width="600px"/>
</div>
**线程私有的:**
- 程序计数器
- 虚拟机栈
- 本地方法栈
**线程共享的:**
- 堆
- 方法区
- 直接内存 (非运行时数据区的一部分)
### 2.1 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。**
另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。**
**从上面的介绍中我们知道程序计数器主要有两个作用:**
1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。**
### 2.2 Java 虚拟机栈
**与程序计数器一样Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。**
**Java 内存可以粗糙的区分为堆内存Heap和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** 实际上Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
**局部变量表主要存放了编译器可知的各种数据类型**boolean、byte、char、short、int、float、long、double、**对象引用**reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
**Java 虚拟机栈会出现两种异常StackOverFlowError 和 OutOfMemoryError。**
- **StackOverFlowError** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。
- **OutOfMemoryError** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。
Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
**扩展:那么方法/函数如何调用?**
Java 栈可用类比数据结构中栈Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java 方法有两种返回方式:
1. return 语句。
2. 抛出异常。
不管哪种返回方式都会导致栈帧被弹出。
### 2.3 本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
### 2.4 堆
Java 虚拟机所管理的内存中最大的一块Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆Garbage Collected Heap**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为新生代和老年代再细致一点有Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3堆结构.png" width="400px"/>
</div>
上图所示的 eden 区、s0 区、s1 区都属于新生代tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
### 2.5 方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap非堆**,目的应该是与 Java 堆区分开来。
方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。
#### 2.5.1 方法区和永久代的关系
> 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 **方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
#### 2.5.2 常用参数
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
```java
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
```
相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
JDK 1.8 的时候方法区HotSpot 的永久代被彻底移除了JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
下面是一些常用参数:
```java
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
```
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
#### 2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 `-XXMaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited这意味着它只受系统内存的限制。`-XXMetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
当然这只是其中一个原因,还有很多底层的原因,这里就不提了。
### 2.6 运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
**JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆Heap中开辟了一块区域存放运行时常量池。**
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg)
——图片来源https://blog.csdn.net/wangbiao007/article/details/78545189
### 2.7 直接内存
**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。**
JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道Channel** 与**缓存区Buffer** 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。
本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
## 三 HotSpot 虚拟机对象探秘
通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
### 3.1 对象的创建
下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
![Java创建对象的过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Java创建对象的过程.png)
#### Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
#### Step2:分配内存
在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。
**内存分配的两种方式:(补充内容,需要掌握)**
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
![内存分配的两种方式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/内存分配的两种方式.png)
**内存分配并发问题(补充内容,需要掌握)**
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。**
- **TLAB** 为每一个线程预先在 Eden 区分配一块儿内存JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
#### Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
#### Step4:设置对象头
初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
#### Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`<init>` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `<init>` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
### 3.2 对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:**对象头**、**实例数据**和**对齐填充**。
**Hotspot 虚拟机的对象头包括两部分信息****第一部分用于存储对象自身的自身运行时数据**哈希码、GC 分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。
**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
### 3.3 对象的访问定位
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种:
1. **句柄:** 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
![对象的访问定位-使用句柄](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/对象的访问定位-使用句柄.png)
2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
![对象的访问定位-直接指针](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/对象的访问定位-直接指针.png)
**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。**
## 四 重点补充内容
### 4.1 String 类和常量池
**String 对象的两种创建方式:**
```java
String str1 = "abcd";//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd""
String str2 = new String("abcd");//堆中创建一个新的对象
String str3 = new String("abcd");//堆中创建一个新的对象
System.out.println(str1==str2);//false
System.out.println(str2==str3);//false
```
这两种不同的创建方法是有差别的。
- 第一种方式是在常量池中拿对象;
- 第二种方式是直接在堆内存空间创建一个新的对象。
记住一点:**只要使用 new 方法,便需要创建新的对象。**
再给大家一个图应该更容易理解,图片来源:<https://www.journaldev.com/797/what-is-java-string-pool>
![String-Pool-Java](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3String-Pool-Java1-450x249.png)
**String 类型的常量池比较特殊。它的主要使用方法有两种:**
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
```java
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false因为一个是堆内存中的 String 对象一个是常量池中的 String 对象,
System.out.println(s3 == s2);//true因为两个都是常量池中的 String 对象
```
**字符串拼接:**
```java
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
```
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/字符串拼接.png)
尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
### 4.2 String s1 = new String("abc");这句话创建了几个字符串对象?
**将创建 1 或 2 个字符串。如果池中已存在字符串文字“abc”则池中只会创建一个字符串“s1”。如果池中没有字符串文字“abc”那么它将首先在池中创建然后在堆空间中创建因此将创建总共 2 个字符串对象。**
**验证:**
```java
String s1 = new String("abc");// 堆内存的地址值
String s2 = "abc";
System.out.println(s1 == s2);// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
System.out.println(s1.equals(s2));// 输出 true
```
**结果:**
```
false
true
```
### 4.3 8 种基本类型的包装类和常量池
- **Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean这 5 种包装类默认创建了数值[-128127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。**
- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**
```java
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
```
**Integer 缓存源代码:**
```java
/**
*此方法将始终缓存-128 到 127包括端点范围内的值并可以缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
```
**应用场景:**
1. Integer i1=40Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
```java
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//输出 false
```
**Integer 比较更丰富的一个例子:**
```java
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));
```
结果:
```
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
```
解释:
语句 i4 == i5 + i6因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40最终这条语句转为 40 == 40 进行数值比较。
## 参考
- 《深入理解 Java 虚拟机JVM 高级特性与最佳实践(第二版》
- 《实战 java 虚拟机》
- <https://docs.oracle.com/javase/specs/index.html>
- <http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/>
- <https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou>
- <https://stackoverflow.com/questions/9095748/method-area-and-permgen>
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,142 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [回顾一下类加载过程](#回顾一下类加载过程)
- [类加载器总结](#类加载器总结)
- [双亲委派模型](#双亲委派模型)
- [双亲委派模型介绍](#双亲委派模型介绍)
- [双亲委派模型实现源码分析](#双亲委派模型实现源码分析)
- [双亲委派模型的好处](#双亲委派模型的好处)
- [如果我们不想要双亲委派模型怎么办?](#如果我们不想要双亲委派模型怎么办)
- [自定义类加载器](#自定义类加载器)
- [推荐](#推荐)
<!-- /TOC -->
> 公众号JavaGuide 后台回复关键字“1”免费获取JavaGuide配套的Java工程师必备学习资源(文末有公众号二维码)。
## 回顾一下类加载过程
类加载过程:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。
![类加载过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类加载过程.png)
一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。
所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。
## 类加载器总结
JVM 中内置了三个重要的 ClassLoader除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`
1. **BootstrapClassLoader(启动类加载器)** 最顶层的加载类由C++实现,负责加载 `%JAVA_HOME%/lib`目录下的jar包和类或者或被 `-Xbootclasspath`参数指定的路径中的所有类。
2. **ExtensionClassLoader(扩展类加载器)** :主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。
3. **AppClassLoader(应用程序类加载器)** :面向我们用户的加载器负责加载当前应用classpath下的所有jar包和类。
## 双亲委派模型
### 双亲委派模型介绍
每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时才由自己来处理。当父类加载器为null时会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。
![ClassLoader](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS图片.png)
每个类加载都有一个父类加载器,我们通过下面的程序来验证。
```java
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
}
}
```
Output
```
ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586
The GrandParent of ClassLodarDemo's ClassLoader is null
```
`AppClassLoader`的父类加载器为`ExtClassLoader`
`ExtClassLoader`的父类加载器为null**null并不代表`ExtClassLoader`没有父类加载器,而是 `Bootstrap ClassLoader`** 。
其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外类加载器之间的“父子”关系也不是通过继承来体现的是由“优先级”来决定。官方API文档对这部分的描述如下:
>The Java platform uses a delegation model for loading classes. **The basic idea is that every class loader has a "parent" class loader.** When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.
### 双亲委派模型实现源码分析
双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 `java.lang.ClassLoader``loadClass()` 中,相关代码如下所示。
```java
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {//父加载器不为空调用父加载器loadClass()方法处理
c = parent.loadClass(name, false);
} else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//抛出异常说明父类加载器无法完成加载请求
}
if (c == null) {
long t1 = System.nanoTime();
//自己尝试加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
```
### 双亲委派模型的好处
双亲委派模型保证了Java程序的稳定运行可以避免类的重复加载JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。
### 如果我们不想用双亲委派模型怎么办?
为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重载 `loadClass()` 即可。
## 自定义类加载器
除了 `BootstrapClassLoader` 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`。如果我们要自定义自己的类加载器,很明显需要继承 `ClassLoader`
## 推荐阅读
- <https://blog.csdn.net/xyang81/article/details/7292380>
- <https://juejin.im/post/5c04892351882516e70dcc9b>
- <http://gityuan.com/2016/01/24/java-classloader/>
### 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,91 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [类加载过程](#类加载过程)
- [加载](#加载)
- [验证](#验证)
- [准备](#准备)
- [解析](#解析)
- [初始化](#初始化)
<!-- /TOC -->
> 公众号JavaGuide 后台回复关键字“1”免费获取JavaGuide配套的Java工程师必备学习资源(文末有公众号二维码)。
# 类加载过程
Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
系统加载 Class 类型的文件主要三步:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。
![类加载过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类加载过程.png)
## 加载
类加载过程的第一步主要完成下面3件事情
1. 通过全类名获取定义此类的二进制字节流
2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口
虚拟机规范多上面这3点并不具体因此是非常灵活的。比如"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取日后出现的JAR、EAR、WAR格式的基础、其他文件生成典型应用就是JSP等等。
**一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。**
类加载器、双亲委派模型也是非常重要的知识点,这部分内容会在后面的文章中单独介绍到。
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
## 验证
![验证阶段示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/验证阶段.png)
## 准备
**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1. 这时候进行内存分配的仅包括类变量static而不包括实例变量实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
2. 这里所设置的初始值"通常情况"下是数据类型默认的零值如0、0L、null、false等比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111初始化阶段才会复制。特殊情况比如给 value 变量加上了 fianl 关键字`public static final int value=111` ,那么准备阶段 value 的值就被复制为 111。
**基本数据类型的零值:**
![基本数据类型的零值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/基本数据类型的零值.png)
## 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。**直接引用**就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时只有符号引用是不够的举个例子在程序执行方法时系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
## 初始化
初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 `<clinit> ()`方法的过程。
对于`<clinit>` 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 `<clinit>` 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
对于初始化阶段虚拟机严格规范了有且只有5中情况下必须对类进行初始化
1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
2. 使用 `java.lang.reflect` 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。
3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
5. 当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄并且这个句柄没有初始化则需要先触发器初始化。
**参考**
- 《深入理解Java虚拟机》
- 《实战Java虚拟机》
- <https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html>
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -0,0 +1,224 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- TOC -->
- [类文件结构](#类文件结构)
- [一 概述](#一-概述)
- [二 Class 文件结构总结](#二-class-文件结构总结)
- [2.1 魔数](#21-魔数)
- [2.2 Class 文件版本](#22-class-文件版本)
- [2.3 常量池](#23-常量池)
- [2.4 访问标志](#24-访问标志)
- [2.5 当前类索引,父类索引与接口索引集合](#25-当前类索引父类索引与接口索引集合)
- [2.6 字段表集合](#26-字段表集合)
- [2.7 方法表集合](#27-方法表集合)
- [2.8 属性表集合](#28-属性表集合)
- [参考](#参考)
<!-- /TOC -->
# 类文件结构
## 一 概述
在 Java 中JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件它不面向任何特定的处理器只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效而且由于字节码并不针对一种特定的机器因此Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
ClojureLisp 语言的一种方言、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了不同的语言被不同的编译器编译成`.class`文件最终运行在 Java 虚拟机之上。`.class`文件的二进制格式可以使用 [WinHex](https://www.x-ways.net/winhex/) 查看。
![Java虚拟机](https://user-gold-cdn.xitu.io/2018/5/3/16325b8e190fbabd?w=712&h=316&f=png&s=17643)
**可以说`.class`文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。**
## 二 Class 文件结构总结
根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成:
```java
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}
```
下面详细介绍一下 Class 文件结构涉及到的一些组件。
**Class文件字节码结构组织示意图** (之前在网上保存的,非常不错,原出处不明):
![类文件字节码结构组织示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类文件字节码结构组织示意图.png)
### 2.1 魔数
```java
u4 magic; //Class 文件的标志
```
每个 Class 文件的头四个字节称为魔数Magic Number,它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。
程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其它特殊的含义。
### 2.2 Class 文件版本
```java
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
```
紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是**次版本号**,第七和第八是**主版本号**。
高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。
### 2.3 常量池
```java
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
```
紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1**常量池计数器是从1开始计数的将第0项常量空出来是有特殊考虑的索引值为0代表“不引用任何一个常量池项”**)。
常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池中每一项常量都是一个表这14种表有一个共同的特点**开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.**
| 类型 | 标志tag | 描述 |
| :------------------------------: | :---------: | :--------------------: |
| CONSTANT_utf8_info | 1 | UTF-8编码的字符串 |
| CONSTANT_Integer_info | 3 | 整形字面量 |
| CONSTANT_Float_info | 4 | 浮点型字面量 |
| CONSTANT_Long_info | | 长整型字面量 |
| CONSTANT_Double_info | | 双精度浮点型字面量 |
| CONSTANT_Class_info | | 类或接口的符号引用 |
| CONSTANT_String_info | | 字符串类型字面量 |
| CONSTANT_Fieldref_info | | 字段的符号引用 |
| CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
| CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
| CONSTANT_NameAndType_info | 12 | 字段或方法的符号引用 |
| CONSTANT_MothodType_info | 16 | 标志方法类型 |
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
`.class` 文件可以通过`javap -v class类名` 指令来看一下其常量池中的信息(`javap -v class类名-> temp.txt` :将结果输出到 temp.txt 文件)。
### 2.4 访问标志
在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。
类访问和属性修饰符:
![类访问和属性修饰符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/访问标志.png)
我们定义了一个 Employee 类
```java
package top.snailclimb.bean;
public class Employee {
...
}
```
通过`javap -v class类名` 指令来看一下类的访问标志。
![查看类的访问标志](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/查看类的访问标志.png)
### 2.5 当前类索引,父类索引与接口索引集合
```java
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个雷可以实现多个接口
```
**类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 java 类都有父类,因此除了 `java.lang.Object` 外,所有 Java 类的父类索引都不为 0。**
**接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按`implents`(如果这个类本身是接口的话则是`extends`) 后的接口顺序从左到右排列在接口索引集合中。**
### 2.6 字段表集合
```java
u2 fields_count;//Class 文件的字段的个数
field_info fields[fields_count];//一个类会可以有个字段
```
字段表field info用于描述接口或类中声明的变量。字段包括类级变量以及实例变量但不包括在方法内部声明的局部变量。
**field info(字段表) 的结构:**
![字段表的结构 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/字段表的结构.png)
- **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化transient 修饰符),可变性final,可见性volatile 修饰符,是否强制从主内存读写)。
- **name_index:** 对常量池的引用,表示的字段的名称;
- **descriptor_index:** 对常量池的引用,表示字段和方法的描述符;
- **attributes_count:** 一个字段还会拥有一些额外的属性attributes_count 存放属性的个数;
- **attributes[attributes_count]:** 存放具体属性具体内容。
上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。
**字段的 access_flags 的取值:**
![字段的access_flags的取值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/字段的access_flags的取值.png)
### 2.7 方法表集合
```java
u2 methods_count;//Class 文件的方法的数量
method_info methods[methods_count];//一个类可以有个多个方法
```
methods_count 表示方法的数量,而 method_info 表示的方法表。
Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
**method_info(方法表的) 结构:**
![方法表的结构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/方法表的结构.png)
**方法表的 access_flag 取值:**
![方法表的 access_flag 取值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/方法表的access_flag的所有标志位.png)
注意:因为`volatile`修饰符和`transient`修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了`synchronized``native``abstract`等关键字修饰方法,所以也就多了这些关键字对应的标志。
### 2.8 属性表集合
```java
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
```
在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息Java 虚拟机运行时会忽略掉它不认识的属性。
## 参考
- <https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html>
- <https://coolshell.cn/articles/9229.html>
- <https://blog.csdn.net/luanlouis/article/details/39960815>
- 《实战 Java 虚拟机》
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334)

View File

@ -52,15 +52,15 @@
<font color="#999999">大写字母I开头的Internet互联网是专用名词它指全球最大的开放的由众多网络相互连接而成的特定的互联网并采用TCP/IP协议作为通信规则其前身为ARPANET。Internet的推荐译名为因特网现在一般流行称为互联网。 <font color="#999999">大写字母I开头的Internet互联网是专用名词它指全球最大的开放的由众多网络相互连接而成的特定的互联网并采用TCP/IP协议作为通信规则其前身为ARPANET。Internet的推荐译名为因特网现在一般流行称为互联网。
<font color="#999999">3路由器是实现分组交换的关键构件其任务是转发到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组有称为包。分组是在互联网中传送的数据单元,正式由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。 <font color="#999999">3路由器是实现分组交换的关键构件其任务是转发到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
<font color="#999999">4互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分其作用是进行信息处理。由大量网络和连接这些网络的路由西组成边缘部分,其作用是提供连通性和交换。 <font color="#999999">4互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分其作用是进行信息处理。由大量网络和连接这些网络的路由组成边缘部分,其作用是提供连通性和交换。
<font color="#999999">5计算机通信是计算机中进程即运行着的程序之间的通信。计算机网络采用的通信方式是客户-服务器方式C/S方式和对等连接方式P2P方式 <font color="#999999">5计算机通信是计算机中进程即运行着的程序之间的通信。计算机网络采用的通信方式是客户-服务器方式C/S方式和对等连接方式P2P方式
<font color="#999999">6客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方服务器是服务提供方。 <font color="#999999">6客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方服务器是服务提供方。
<font color="#999999">7按照作用范围的不同计算机网络分为广域网WAN城域网MAN,局域网LAN个人区域网PAN。 <font color="#999999">7按照作用范围的不同计算机网络分为广域网WAN城域网MAN局域网LAN个人区域网PAN。
<font color="#999999">8计算机网络最常用的性能指标是速率带宽吞吐量时延发送时延处理时延排队时延时延带宽积往返时间和信道利用率。 <font color="#999999">8计算机网络最常用的性能指标是速率带宽吞吐量时延发送时延处理时延排队时延时延带宽积往返时间和信道利用率。
@ -280,7 +280,7 @@
3运输层的两个重要协议是用户数据报协议UDP和传输控制协议TCP。按照OSI的术语两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元TPDUTransport Protocol Data Unit。但在TCP/IP体系中则根据所使用的协议是TCP或UDP分别称之为TCP报文段或UDP用户数据报。 3运输层的两个重要协议是用户数据报协议UDP和传输控制协议TCP。按照OSI的术语两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元TPDUTransport Protocol Data Unit。但在TCP/IP体系中则根据所使用的协议是TCP或UDP分别称之为TCP报文段或UDP用户数据报。
4UDP在传送数据之前不需要先建立连接远地主机在收到UDP报文后不需要给出任何确认。虽然UDP不提供可靠交付但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的面向连接的输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。 4UDP在传送数据之前不需要先建立连接远地主机在收到UDP报文后不需要给出任何确认。虽然UDP不提供可靠交付但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的面向连接的输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。
5硬件端口是不同硬件设备进行交互的接口而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP和TCP的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到IP层交上来的运输层报文时就能够 根据其首部中的目的端口号把数据交付应用层的目的应用层。两个进程之间进行通信不光要知道对方IP地址而且要知道对方的端口号(为了找到对方计算机中的应用进程) 5硬件端口是不同硬件设备进行交互的接口而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP和TCP的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到IP层交上来的运输层报文时就能够 根据其首部中的目的端口号把数据交付应用层的目的应用层。两个进程之间进行通信不光要知道对方IP地址而且要知道对方的端口号(为了找到对方计算机中的应用进程)

View File

@ -1,160 +1,138 @@
<!-- TOC -->
<!-- MarkdownTOC --> - [一 OSI与TCP/IP各层的结构与功能,都有哪些协议?](#一-osi与tcpip各层的结构与功能都有哪些协议)
- [1.1 应用层](#11-应用层)
- [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议) - [1.2 运输层](#12-运输层)
- [五层协议的体系结构](#五层协议的体系结构) - [1.3 网络层](#13-网络层)
- [1 应用层](#1-应用层) - [1.4 数据链路层](#14-数据链路层)
- [域名系统](#域名系统) - [1.5 物理层](#15-物理层)
- [HTTP协议](#http协议) - [1.6 总结一下](#16-总结一下)
- [2 运输层](#2-运输层) - [二 TCP 三次握手和四次挥手(面试常客)](#二-tcp-三次握手和四次挥手面试常客)
- [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) - [2.1 TCP 三次握手漫画图解](#21-tcp-三次握手漫画图解)
- [UDP 的主要特点](#udp-的主要特点) - [2.2 为什么要三次握手](#22-为什么要三次握手)
- [TCP 的主要特点](#tcp-的主要特点) - [2.3 为什么要传回 SYN](#23-为什么要传回-syn)
- [3 网络层](#3-网络层) - [2.4 传了 SYN,为啥还要传 ACK](#24-传了-syn为啥还要传-ack)
- [4 数据链路层](#4-数据链路层) - [2.5 为什么要四次挥手](#25-为什么要四次挥手)
- [5 物理层](#5-物理层) - [三 TCP,UDP 协议的区别](#三-tcpudp-协议的区别)
- [总结一下](#总结一下)
- [二 TCP 三次握手和四次挥手\(面试常客\)](#二-tcp-三次握手和四次挥手面试常客)
- [为什么要三次握手](#为什么要三次握手)
- [为什么要传回 SYN](#为什么要传回-syn)
- [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- [为什么要四次挥手](#为什么要四次挥手)
- [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别)
- [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输) - [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输)
- [停止等待协议](#停止等待协议) - [4.1 ARQ协议](#41-arq协议)
- [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) - [停止等待ARQ协议](#停止等待arq协议)
- [连续ARQ协议](#连续arq协议) - [连续ARQ协议](#连续arq协议)
- [滑动窗口](#滑动窗口) - [4.2 滑动窗口和流量控制](#42-滑动窗口和流量控制)
- [流量控制](#流量控制) - [4.3 拥塞控制](#43-拥塞控制)
- [拥塞控制](#拥塞控制) - [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五--在浏览器中输入url地址---显示主页的过程面试常客)
- [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五-在浏览器中输入url地址---显示主页的过程(面试常客))
- [六 状态码](#六-状态码) - [六 状态码](#六-状态码)
- [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系) - [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系)
- [八 HTTP长连接、短连接](#八-http长连接、短连接) - [八 HTTP长连接,短连接](#八--http长连接短连接)
- [写在最后](#写在最后) - [九 HTTP是不保存状态的协议,如何保存用户状态?](#九-http是不保存状态的协议如何保存用户状态)
- [计算机网络常见问题回顾](#计算机网络常见问题回顾) - [十 Cookie的作用是什么?和Session有什么区别](#十-cookie的作用是什么和session有什么区别)
- [建议](#建议) - [十一 HTTP 1.0和HTTP 1.1的主要区别是什么?](#十一-http-10和http-11的主要区别是什么)
- [十二 URI和URL的区别是什么?](#十二-uri和url的区别是什么)
- [十三 HTTP 和 HTTPS 的区别?](#十三-http-和-https-的区别)
- [建议](#建议)
- [参考](#参考)
<!-- /MarkdownTOC --> <!-- /TOC -->
## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议?
相对与上一个版本的计算机网路面试知识总结,这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。
## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议
### 五层协议的体系结构
学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。 学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。
![五层协议的体系结构](https://user-gold-cdn.xitu.io/2018/7/29/164e5307471e8eba?w=633&h=344&f=png&s=164623) ![五层体系结构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/五层体系结构.png)
结合互联网的情况,自上而下地,非常简要的介绍一下各层的作用。 结合互联网的情况,自上而下地,非常简要的介绍一下各层的作用。
### 1 应用层 ### 1.1 应用层
**应用层(application-layer的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统DNS**,支持万维网应用的 **HTTP协议**,支持电子邮件的 **SMTP协议**等等。我们把应用层交互的数据单元称为报文。 **应用层(application-layer的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统DNS**,支持万维网应用的 **HTTP协议**,支持电子邮件的 **SMTP协议**等等。我们把应用层交互的数据单元称为报文。
#### 域名系统 **域名系统**
> 域名系统(Domain Name System缩写 DNSDomain Name被译为域名)是因特网的一项核心服务它作为可以将域名和IP地址相互映射的一个分布式数据库能够使人更方便的访问互联网而不用去记住能够被机器直接读取的IP数串。百度百科例如一个公司的 Web 网站可看作是它在网上的门户而域名就相当于其门牌地址通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名类似的还有IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。 > 域名系统(Domain Name System缩写 DNSDomain Name被译为域名)是因特网的一项核心服务它作为可以将域名和IP地址相互映射的一个分布式数据库能够使人更方便的访问互联网而不用去记住能够被机器直接读取的IP数串。百度百科例如一个公司的 Web 网站可看作是它在网上的门户而域名就相当于其门牌地址通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名类似的还有IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。
#### HTTP协议 **HTTP协议**
> 超文本传输协议HTTPHyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW万维网 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科) > 超文本传输协议HTTPHyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW万维网 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科)
### 2 运输层 ### 1.2 运输层
**运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。 **运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
#### 运输层主要使用以下两种协议 **运输层主要使用以下两种协议:**
1. **传输控制协议 TCP**Transmisson Control Protocol--提供**面向连接**的,**可靠的**数据传输服务。 1. **传输控制协议 TCP**Transmission Control Protocol--提供**面向连接**的,**可靠的**数据传输服务。
2. **用户数据协议 UDP**User Datagram Protocol--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。 2. **用户数据协议 UDP**User Datagram Protocol--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。
#### UDP 的主要特点 **TCP 与 UDP 的对比见问题三。**
1. UDP 是无连接的;
2. UDP 使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态(这里面有许多参数);
3. UDP 是面向报文的;
4. UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 直播,实时视频会议等);
5. UDP 支持一对一、一对多、多对一和多对多的交互通信;
6. UDP 的首部开销小只有8个字节比TCP的20个字节的首部要短。
#### TCP 的主要特点
1. TCP 是面向连接的。(就好像打电话一样,通话前需要先拨号建立连接,通话结束后要挂机释放连接);
2. 每一条 TCP 连接只能有两个端点每一条TCP连接只能是点对点的一对一
3. TCP 提供可靠交付的服务。通过TCP连接传送的数据无差错、不丢失、不重复、并且按序到达
4. TCP 提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接收缓存,用来临时存放双方通信的数据;
5. 面向字节流。TCP 中的“流”Stream指的是流入进程或从进程流出的字节序列。“面向字节流”的含义是虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。
### 3 网络层 ### 1.3 网络层
**在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报** **在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**
这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。 这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。
这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称. 这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称.
互联网是由大量的异构heterogeneous网络通过路由器router相互连接起来的。互联网使用的网络层协议是无连接的网际协议Intert Prococol和许多路由选择协议因此互联网的网络层也叫做**网际层**或**IP层**。 互联网是由大量的异构heterogeneous网络通过路由器router相互连接起来的。互联网使用的网络层协议是无连接的网际协议Intert Protocol和许多路由选择协议因此互联网的网络层也叫做**网际层**或**IP层**。
### 4 数据链路层 ### 1.4 数据链路层
**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装程帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。 **数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装程帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。 在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。
控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。 控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。
### 5 物理层 ### 1.5 物理层
在物理层上所传送的数据单位是比特。 在物理层上所传送的数据单位是比特。
**物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。 **物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议而往往表示互联网所使用的整个TCP/IP协议族。 在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议而往往表示互联网所使用的整个TCP/IP协议族。
### 总结一下 ### 1.6 总结一下
上面我们对计算机网络的五层体系结构有了初步的了解下面附送一张七层体系结构图总结一下。图片来源https://blog.csdn.net/yaopeng_2005/article/details/7064869 上面我们对计算机网络的五层体系结构有了初步的了解下面附送一张七层体系结构图总结一下。图片来源https://blog.csdn.net/yaopeng_2005/article/details/7064869
![七层体系结构图](https://user-gold-cdn.xitu.io/2018/7/29/164e529309f0fa33?w=1120&h=1587&f=gif&s=225325)
![七层体系结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/七层体系结构图.png)
## 二 TCP 三次握手和四次挥手(面试常客) ## 二 TCP 三次握手和四次挥手(面试常客)
为了准确无误地把数据送达目标处TCP协议采用了三次握手策略。 为了准确无误地把数据送达目标处TCP协议采用了三次握手策略。
**漫画图解:** ### 2.1 TCP 三次握手漫画图解
图片来源《图解HTTP》 如下图所示下面的两个机器人通过3次握手确定了对方能正确接收和发送消息(图片来源《图解HTTP》)。
![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095) ![TCP三次握手](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/三次握手.png)
**简单示意图:** **简单示意图:**
![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088) ![TCP三次握手](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/三次握手2.png)
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端 - 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端 - 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端 - 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
### 为什么要三次握手 ### 2.2 为什么要三次握手
**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。** **三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
第一次握手Client 什么都不能确认Server 确认了对方发送正常 第一次握手Client 什么都不能确认Server 确认了对方发送正常,自己接收正常
第二次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:自己接收正常,对方发送正常 第二次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:对方发送正常,自己接收正常
第三次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:自己发送、接收正常,对方发送接收正常 第三次握手Client 确认了自己发送、接收正常对方发送、接收正常Server 确认了:自己发送、接收正常,对方发送接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。 所以三次握手就能确认双发收发功能都正常,缺一不可。
### 为什么要传回 SYN ### 2.3 为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。 接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接数据才可以在客户机和服务器之间传递。 > SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ]消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接数据才可以在客户机和服务器之间传递。
### 传了 SYN,为啥还要传 ACK ### 2.4 传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN证明发送方到接收方的通道没有问题但是接收方到发送方的通道还需要 ACK 信号来进行验证。 双方通信无误必须是两者互相发送信息都无误。传了 SYN证明发送方到接收方的通道没有问题但是接收方到发送方的通道还需要 ACK 信号来进行验证。
![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406) ![TCP四次挥手](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/TCP四次挥手.png)
断开一个 TCP 连接则需要“四次挥手”: 断开一个 TCP 连接则需要“四次挥手”:
@ -163,8 +141,7 @@
- 服务器-关闭与客户端的连接发送一个FIN给客户端 - 服务器-关闭与客户端的连接发送一个FIN给客户端
- 客户端-发回 ACK 报文确认并将确认序号设置为收到序号加1 - 客户端-发回 ACK 报文确认并将确认序号设置为收到序号加1
### 2.5 为什么要四次挥手
### 为什么要四次挥手
任何一方都可以在数据传送结束后发出连接释放的通知待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候则发出连接释放通知对方确认后就完全关闭了TCP连接。 任何一方都可以在数据传送结束后发出连接释放的通知待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候则发出连接释放通知对方确认后就完全关闭了TCP连接。
@ -172,12 +149,12 @@
上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891) 上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
## 三 TCPUDP 协议的区别 ## 三 TCP,UDP 协议的区别
![TCP、UDP协议的区别](https://user-gold-cdn.xitu.io/2018/4/19/162db5e97e9a9e01?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) ![TCP、UDP协议的区别](https://user-gold-cdn.xitu.io/2018/4/19/162db5e97e9a9e01?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等 UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的输服务TCP的可靠体现在TCP在传递数据之前会有三次握手来建立连接而且在数据传递时有确认、窗口、重传、拥塞控制机制在数据传完后还会断开连接用来节约系统资源这一难以避免增加了许多开销如确认流量控制计时器以及连接管理等。这不仅使协议数据单元的首部增大很多还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的输服务TCP的可靠体现在TCP在传递数据之前会有三次握手来建立连接而且在数据传递时有确认、窗口、重传、拥塞控制机制在数据传完后还会断开连接用来节约系统资源这一难以避免增加了许多开销如确认流量控制计时器以及连接管理等。这不仅使协议数据单元的首部增大很多还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
## 四 TCP 协议如何保证可靠传输 ## 四 TCP 协议如何保证可靠传输
@ -187,47 +164,35 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接,
4. TCP 的接收端会丢弃重复的数据。 4. TCP 的接收端会丢弃重复的数据。
5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据能提示发送方降低发送的速率防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 TCP 利用滑动窗口实现流量控制) 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据能提示发送方降低发送的速率防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 TCP 利用滑动窗口实现流量控制)
6. **拥塞控制:** 当网络拥塞时,减少数据的发送。 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 7. **ARQ协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
### 4.1 ARQ协议
**自动重传请求**Automatic Repeat-reQuestARQ是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
### 停止等待协议 #### 停止等待ARQ协议
- 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组; - 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认回复ACK。如果过了一段时间超时时间后还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;
- 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认; - 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认;
**优点:** 简单
**缺点:** 信道利用率低,等待时间长
**1) 无差错情况:** **1) 无差错情况:**
![](https://user-gold-cdn.xitu.io/2018/8/16/16541fa8c3816a90?w=514&h=473&f=png&s=9924)
发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。 发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。
**2) 出现差错情况(超时重传):** **2) 出现差错情况(超时重传):**
![](https://user-gold-cdn.xitu.io/2018/8/16/16541faefdf249ab?w=953&h=480&f=png&s=19163)
停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
**3) 确认丢失和确认迟到** **3) 确认丢失和确认迟到**
- **确认丢失**:确认消息在传输过程丢失 - **确认丢失** 确认消息在传输过程丢失。当A发送M1消息B收到后B向A发送了一个M1确认消息但却在传输过程中丢失。而A并不知道在超时计时过后A重传M1消息B再次收到该消息后采取以下两点措施1. 丢弃这个重复的M1消息不向上层交付。 2. 向A发送确认消息。不会认为已经发送过了就不再发送。A能重传就证明B的确认消息丢失
![](https://user-gold-cdn.xitu.io/2018/8/16/16541fb6941a7165?w=918&h=461&f=png&s=19841) - **确认迟到** 确认消息在传输过程中迟到。A发送M1消息B收到并发送确认。在超时时间内没有收到确认消息A重传M1消息B仍然收到并继续发送确认消息B收到了2份M1。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会A收到了B第一次发送的对M1的确认消息A也收到了2份确认消息。处理如下1. A收到重复的确认后直接丢弃。2. B收到重复的M1后也直接丢弃重复的M1。
当A发送M1消息B收到后B向A发送了一个M1确认消息但却在传输过程中丢失。而A并不知道在超时计时过后A重传M1消息B再次收到该消息后采取以下两点措施
1. 丢弃这个重复的M1消息不向上层交付。 #### 连续ARQ协议
2. 向A发送确认消息。不会认为已经发送过了就不再发送。A能重传就证明B的确认消息丢失
- **确认迟到** :确认消息在传输过程中迟到
![](https://user-gold-cdn.xitu.io/2018/8/16/16541fdd85929e6b?w=899&h=450&f=png&s=23165)
A发送M1消息B收到并发送确认。在超时时间内没有收到确认消息A重传M1消息B仍然收到并继续发送确认消息B收到了2份M1。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会A收到了B第一次发送的对M1的确认消息A也收到了2份确认消息。处理如下
1. A收到重复的确认后直接丢弃。
2. B收到重复的M1后也直接丢弃重复的M1。
### 自动重传请求 ARQ 协议
停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认就重传前面发送过的分组认为刚才发送过的分组丢失了。因此每发送完一个分组需要设置一个超时计时器其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。
**优点:** 简单
**缺点:** 信道利用率低
### 连续ARQ协议
连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。 连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
@ -235,19 +200,11 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接,
**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息中间第三条丢失3号这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落而只好把后三个全部重传一次。这也叫 Go-Back-N回退 N表示需要退回来重传已经发送过的 N 个消息。 **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息中间第三条丢失3号这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落而只好把后三个全部重传一次。这也叫 Go-Back-N回退 N表示需要退回来重传已经发送过的 N 个消息。
### 滑动窗口 ### 4.2 滑动窗口和流量控制
- TCP 利用滑动窗口实现流量控制的机制。 **TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0则发送方不能发送数据。
- 滑动窗口Sliding window是一种流量控制技术。早期的网络通信中通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况同时发送数据导致中间节点阻塞掉包谁也发不了数据所以就有了滑动窗口机制来解决此问题。
- TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。
### 流量控制 ### 4.3 拥塞控制
- TCP 利用滑动窗口实现流量控制。
- 流量控制是为了控制发送方发送速率,保证接收方来得及接收。
- 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0则发送方不能发送数据。
### 拥塞控制
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
@ -256,15 +213,12 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接,
TCP的拥塞控制采用了四种算法**慢开始****拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM以减少网络拥塞的发生。 TCP的拥塞控制采用了四种算法**慢开始****拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM以减少网络拥塞的发生。
- **慢开始:** 慢开始算法的思路是当主机开始发送数据时如果立即把大量数据字节注入到网络那么可能会引起网络阻塞因为现在还不知道网络的符合情况。经验表明较好的方法是先探测一下即由小到大逐渐增大发送窗口也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1每经过一个传播轮次cwnd加倍。 - **慢开始:** 慢开始算法的思路是当主机开始发送数据时如果立即把大量数据字节注入到网络那么可能会引起网络阻塞因为现在还不知道网络的符合情况。经验表明较好的方法是先探测一下即由小到大逐渐增大发送窗口也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1每经过一个传播轮次cwnd加倍。
![](https://user-gold-cdn.xitu.io/2018/8/10/1652348ada2c8fd0?w=1050&h=560&f=jpeg&s=112611)
- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大即每经过一个往返时间RTT就把发送放的cwnd加1. - **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大即每经过一个往返时间RTT就把发送放的cwnd加1.
- **快重传与快恢复:** - **快重传与快恢复:**
在 TCP/IP 中快速重传和恢复fast retransmit and recoveryFRR是一种拥塞控制算法它能快速恢复丢失的数据包。没有 FRR如果数据包丢失了TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR如果接收机接收到一个不按顺序的数据段它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认它会假定确认件指出的数据段丢失了并立即重传这些丢失的数据段。有了 FRR就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时快速重传和恢复FRR能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时它则不能很有效地工作。 在 TCP/IP 中快速重传和恢复fast retransmit and recoveryFRR是一种拥塞控制算法它能快速恢复丢失的数据包。没有 FRR如果数据包丢失了TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR如果接收机接收到一个不按顺序的数据段它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认它会假定确认件指出的数据段丢失了并立即重传这些丢失的数据段。有了 FRR就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时快速重传和恢复FRR能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时它则不能很有效地工作。
![快重传与快恢复](https://user-gold-cdn.xitu.io/2018/8/10/165234f0303d174b?w=1174&h=648&f=png&s=109568)
## 五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)
## 五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)
百度好像最喜欢问这个问题。 百度好像最喜欢问这个问题。
> 打开一个网页,整个过程会使用哪些协议 > 打开一个网页,整个过程会使用哪些协议
@ -285,12 +239,9 @@ TCP的拥塞控制采用了四种算法即 **慢开始** 、 **拥塞避免**
- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700) - [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
## 六 状态码 ## 六 状态码
![状态码](https://user-gold-cdn.xitu.io/2018/5/8/1633e19dba27ed00?w=673&h=218&f=png&s=72968) ![状态码](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/状态码.png)
## 七 各种协议与HTTP协议之间的关系 ## 七 各种协议与HTTP协议之间的关系
@ -298,9 +249,9 @@ TCP的拥塞控制采用了四种算法即 **慢开始** 、 **拥塞避免**
图片来源《图解HTTP》 图片来源《图解HTTP》
![各种协议与HTTP协议之间的关系](https://user-gold-cdn.xitu.io/2018/5/8/1633ead316d07713?w=841&h=1193&f=png&s=609513) ![各种协议与HTTP协议之间的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/各种协议与HTTP协议之间的关系.png)
## 八 HTTP长连接短连接 ## 八 HTTP长连接,短连接
在HTTP/1.0中默认使用短连接。也就是说客户端和服务器每进行一次HTTP操作就建立一次连接任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源如JavaScript文件、图像文件、CSS文件等每遇到这样一个Web资源浏览器就会重新建立一个HTTP会话。 在HTTP/1.0中默认使用短连接。也就是说客户端和服务器每进行一次HTTP操作就建立一次连接任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源如JavaScript文件、图像文件、CSS文件等每遇到这样一个Web资源浏览器就会重新建立一个HTTP会话。
@ -316,29 +267,60 @@ Connection:keep-alive
—— [《HTTP长连接、短连接究竟是什么](https://www.cnblogs.com/gotodsp/p/6366163.html) —— [《HTTP长连接、短连接究竟是什么](https://www.cnblogs.com/gotodsp/p/6366163.html)
## 九 HTTP是不保存状态的协议,如何保存用户状态?
## 写在最后 HTTP 是一种不保存状态即无状态stateless协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢Session 机制的存在就是为了解决这个问题Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session过了时间限制就会销毁这个Session
### 计算机网络常见问题回顾
- ①TCP三次握手和四次挥手、 在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。
- ②在浏览器中输入url地址->>显示主页的过程
- ③HTTP和HTTPS的区别 **Cookie 被禁用怎么办?**
- ④TCP、UDP协议的区别
- ⑤常见的状态码。 最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。
![HTTP是无状态协议](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HTTP是无状态的.png)
## 十 Cookie的作用是什么?和Session有什么区别
Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式但是两者的应用场景不太一样。
**Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
Cookie 数据保存在客户端(浏览器端)Session 数据保存在服务器端。
Cookie 存储在客户端中而Session存储在服务器上相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
## 十一 HTTP 1.0和HTTP 1.1的主要区别是什么?
> 这部分回答引用这篇文章 <https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u_4zT-A?> 的一些内容。
HTTP1.0最早在网页中使用是在1996年那个时候只是使用一些较为简单的网页上和网络请求上而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
1. **长连接** : **在HTTP/1.0中,默认使用的是短连接**也就是说每次请求都要重新建立一次连接。HTTP 是基于TCP/IP协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。**HTTP 1.1起,默认使用长连接** ,默认开启Connection keep-alive。 **HTTP/1.1的持续连接有非流水线方式和流水线方式** 。流水线方式是客户在收到HTTP的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。
1. **错误状态响应码** :在HTTP1.1中新增了24个错误状态响应码如409Conflict表示请求的资源与资源的当前状态发生冲突410Gone表示服务器上的某个资源被永久性的删除。
1. **缓存处理** :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准HTTP1.1则引入了更多的缓存控制策略例如Entity tagIf-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
1. **带宽优化及网络连接的使用** :HTTP1.0中存在一些浪费带宽的现象例如客户端只是需要某个对象的一部分而服务器却将整个对象送过来了并且不支持断点续传功能HTTP1.1则在请求头引入了range头域它允许只请求资源的某个部分即返回码是206Partial Content这样就方便了开发者自由的选择以便于充分利用带宽和连接。
## 十二 URI和URL的区别是什么?
- URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
- URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
URI的作用像身份证号一样URL的作用更像家庭住址一样。URL是一种具体的URI它不仅唯一标识资源而且还提供了定位该资源的信息。
## 十三 HTTP 和 HTTPS 的区别?
1. **端口** HTTP的URL由“http://”起始且默认使用端口80而HTTPS的URL由“https://”起始且默认使用端口443。
2. **安全性和资源消耗:** HTTP协议运行在TCP之上所有传输的内容都是明文客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议SSL/TLS 运行在TCP之上。所有传输的内容都经过加密加密采用对称加密但对称加密的密钥用服务器方的证书进行了非对称加密。所以说HTTP 安全性没有 HTTPS高但是 HTTPS 比HTTP耗费更多服务器资源。
- 对称加密密钥只有一个加密解密为同一个密码且加解密速度快典型的对称加密算法有DES、AES等
- 非对称加密密钥成对出现且根据公钥无法推知私钥根据私钥也无法推知公钥加密解密使用不同密钥公钥加密需要私钥解密私钥加密需要公钥解密相对对称加密速度较慢典型的非对称加密算法有RSA、DSA等。
## 建议
### 建议
非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。
## 参考
### 参考
- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) - [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250)
- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) - [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466)
- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) - [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641)
- <https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u_4zT-A?>

View File

@ -78,7 +78,7 @@ echo "helloworld!"
shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中除了bash shell以外还有很多版本的shell 例如zsh、dash等等...不过bash shell还是我们使用最多的。** shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中除了bash shell以外还有很多版本的shell 例如zsh、dash等等...不过bash shell还是我们使用最多的。**
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。) (4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
![shell 编程Hello World](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg) ![shell 编程Hello World](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg)
@ -210,7 +210,7 @@ expr length "$name";
expr 5+6 // 直接输出 5+6 expr 5+6 // 直接输出 5+6
expr 5 + 6 // 输出 11 expr 5 + 6 // 输出 11
``` ```
对于某些运算符,还需要我们使用符号"\"进行转义,否则就会提示语法错误。 对于某些运算符,还需要我们使用符号`\`进行转义,否则就会提示语法错误。
```shell ```shell
expr 5 * 6 // 输出错误 expr 5 * 6 // 输出错误
@ -380,10 +380,10 @@ a 不等于 b
#!/bin/bash #!/bin/bash
a=3; a=3;
b=9; b=9;
if [ $a = $b ] if [ $a -eq $b ]
then then
echo "a 等于 b" echo "a 等于 b"
elif [ $a > $b ] elif [ $a -gt $b ]
then then
echo "a 大于 b" echo "a 大于 b"
else else
@ -394,7 +394,7 @@ fi
输出结果: 输出结果:
``` ```
a 于 b a 于 b
``` ```
相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句shell if 条件语句中不能包含空语句也就是什么都不做的语句。 相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句shell if 条件语句中不能包含空语句也就是什么都不做的语句。
@ -482,16 +482,20 @@ done
```shell ```shell
#!/bin/bash #!/bin/bash
function(){ hello(){
echo "这是我的第一个 shell 函数!" echo "这是我的第一个 shell 函数!"
} }
function echo "-----函数开始执行-----"
hello
echo "-----函数执行完毕-----"
``` ```
输出结果: 输出结果:
``` ```
-----函数开始执行-----
这是我的第一个 shell 函数! 这是我的第一个 shell 函数!
-----函数执行完毕-----
``` ```

View File

@ -1,3 +1,5 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
<!-- MarkdownTOC --> <!-- MarkdownTOC -->
- [一 从认识操作系统开始](#一-从认识操作系统开始) - [一 从认识操作系统开始](#一-从认识操作系统开始)
@ -22,9 +24,12 @@
<!-- /MarkdownTOC --> <!-- /MarkdownTOC -->
推荐一个Github开源的Linux学习指南(Java工程师向)<https://github.com/judasn/Linux-Tutorial>
> 学习Linux之前我们先来简单的认识一下操作系统。 > 学习Linux之前我们先来简单的认识一下操作系统。
## 一 从认识操作系统开始 ## 一 从认识操作系统开始
### 1.1 操作系统简介 ### 1.1 操作系统简介
我通过以下四点介绍什么操作系统: 我通过以下四点介绍什么操作系统:
@ -92,7 +97,7 @@ Linux文件系统的结构层次鲜明就像一棵倒立的树最顶层是
**常见目录说明:** **常见目录说明:**
- **/bin** 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里; - **/bin** 存放二进制可执行文件(ls、cat、mkdir等),常用命令一般都在这里;
- **/etc** 存放系统管理和配置文件; - **/etc** 存放系统管理和配置文件;
- **/home** 存放所有用户文件的根目录是用户主目录的基点比如用户user的主目录就是/home/user可以用~user表示 - **/home** 存放所有用户文件的根目录是用户主目录的基点比如用户user的主目录就是/home/user可以用~user表示
- **/usr ** 用于存放系统应用程序; - **/usr ** 用于存放系统应用程序;
@ -165,7 +170,7 @@ Linux命令大全[http://man.linuxde.net/](http://man.linuxde.net/)
**在实际开发中使用vim编辑器主要作用就是修改配置文件下面是一般步骤** **在实际开发中使用vim编辑器主要作用就是修改配置文件下面是一般步骤**
vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! 输入wq代表写入内容并退出即保存输入q!代表强制退出不保存。) vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入wq/q! 输入wq代表写入内容并退出即保存输入q!代表强制退出不保存。)
4. **`rm -rf 文件`** 删除文件(删) 4. **`rm -rf 文件`** 删除文件(删)
同目录删除:熟记 `rm -rf` 文件 即可 同目录删除:熟记 `rm -rf` 文件 即可
@ -188,7 +193,7 @@ Linux中的打包文件一般是以.tar结尾的压缩的命令一般是以.g
f指定文件名 f指定文件名
比如加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或:`tar -zcvf test.tar.gz /test/`** 比如加入test目录下有三个文件分别是aaa.txt bbb.txt ccc.txt如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或:`tar -zcvf test.tar.gz /test/`**
**2解压压缩包** **2解压压缩包**
@ -201,7 +206,7 @@ Linux中的打包文件一般是以.tar结尾的压缩的命令一般是以.g
1 将/test下的test.tar.gz解压到当前目录下可以使用命令**`tar -xvf test.tar.gz`** 1 将/test下的test.tar.gz解压到当前目录下可以使用命令**`tar -xvf test.tar.gz`**
2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf xxx.tar.gz -C /usr`**- C代表指定解压的位置 2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf test.tar.gz -C /usr`**- C代表指定解压的位置
### 4.5 Linux的权限命令 ### 4.5 Linux的权限命令
@ -339,12 +344,18 @@ passwd命令用于设置用户的认证信息包括用户密码、密码过
- 查看当前系统的端口使用netstat -an - 查看当前系统的端口使用netstat -an
- **net-tools 和 iproute2 ** - **net-tools 和 iproute2 **
`net-tools`起源于BSD的TCP/IP工具箱后来成为老版本Linux内核中配置网络功能的工具。但自2001年起Linux社区已经对其停止维护。同时一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools只支持`iproute2`。linux ip命令类似于ifconfig但功能更强大旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux) `net-tools`起源于BSD的TCP/IP工具箱后来成为老版本Linux内核中配置网络功能的工具。但自2001年起Linux社区已经对其停止维护。同时一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools只支持`iproute2`。linux ip命令类似于ifconfig但功能更强大旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux)
- **`shutdown`** `shutdown -h now` 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机同时送出警告信息给登入用户。 - **`shutdown`** `shutdown -h now` 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`指定5分钟后关机同时送出警告信息给登入用户。
- **`reboot`** **`reboot`** 重开机。**`reboot -w`** 做个重开机的模拟(只有纪录并不会真的重开机)。 - **`reboot`** **`reboot`** 重开机。**`reboot -w`** 做个重开机的模拟(只有纪录并不会真的重开机)。
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)

View File

@ -0,0 +1,214 @@
本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
<!-- TOC -->
- [1 单机版消息中心](#1-%E5%8D%95%E6%9C%BA%E7%89%88%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
- [2 分布式消息中心](#2-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
- [2.1 问题与解决](#21-%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3)
- [2.1.1 消息丢失的问题](#211-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E7%9A%84%E9%97%AE%E9%A2%98)
- [2.1.2 同步落盘怎么才能快](#212-%E5%90%8C%E6%AD%A5%E8%90%BD%E7%9B%98%E6%80%8E%E4%B9%88%E6%89%8D%E8%83%BD%E5%BF%AB)
- [2.1.3 消息堆积的问题](#213-%E6%B6%88%E6%81%AF%E5%A0%86%E7%A7%AF%E7%9A%84%E9%97%AE%E9%A2%98)
- [2.1.4 定时消息的实现](#214-%E5%AE%9A%E6%97%B6%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- [2.1.5 顺序消息的实现](#215-%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- [2.1.6 分布式消息的实现](#216-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
- [2.1.7 消息的 push 实现](#217-%E6%B6%88%E6%81%AF%E7%9A%84-push-%E5%AE%9E%E7%8E%B0)
- [2.1.8 消息重复发送的避免](#218-%E6%B6%88%E6%81%AF%E9%87%8D%E5%A4%8D%E5%8F%91%E9%80%81%E7%9A%84%E9%81%BF%E5%85%8D)
- [2.1.9 广播消费与集群消费](#219-%E5%B9%BF%E6%92%AD%E6%B6%88%E8%B4%B9%E4%B8%8E%E9%9B%86%E7%BE%A4%E6%B6%88%E8%B4%B9)
- [2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?](#2110-rocketmq-%E4%B8%8D%E4%BD%BF%E7%94%A8-zookeeper-%E4%BD%9C%E4%B8%BA%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E7%9A%84%E5%8E%9F%E5%9B%A0%E4%BB%A5%E5%8F%8A%E8%87%AA%E5%88%B6%E7%9A%84-nameserver-%E4%BC%98%E7%BC%BA%E7%82%B9)
- [2.1.11 其它](#2111-%E5%85%B6%E5%AE%83)
- [3 参考](#3-%E5%8F%82%E8%80%83)
<!-- TOC -->
# 1 单机版消息中心
一个消息中心,最基本的需要支持多生产者、多消费者,例如下:
```java
class Scratch {
public static void main(String[] args) {
// 实际中会有 nameserver 服务来找到 broker 具体位置以及 broker 主从信息
Broker broker = new Broker();
Producer producer1 = new Producer();
producer1.connectBroker(broker);
Producer producer2 = new Producer();
producer2.connectBroker(broker);
Consumer consumer1 = new Consumer();
consumer1.connectBroker(broker);
Consumer consumer2 = new Consumer();
consumer2.connectBroker(broker);
for (int i = 0; i < 2; i++) {
producer1.asyncSendMsg("producer1 send msg" + i);
producer2.asyncSendMsg("producer2 send msg" + i);
}
System.out.println("broker has msg:" + broker.getAllMagByDisk());
for (int i = 0; i < 1; i++) {
System.out.println("consumer1 consume msg" + consumer1.syncPullMsg());
}
for (int i = 0; i < 3; i++) {
System.out.println("consumer2 consume msg" + consumer2.syncPullMsg());
}
}
}
class Producer {
private Broker broker;
public void connectBroker(Broker broker) {
this.broker = broker;
}
public void asyncSendMsg(String msg) {
if (broker == null) {
throw new RuntimeException("please connect broker first");
}
new Thread(() -> {
broker.sendMsg(msg);
}).start();
}
}
class Consumer {
private Broker broker;
public void connectBroker(Broker broker) {
this.broker = broker;
}
public String syncPullMsg() {
return broker.getMsg();
}
}
class Broker {
// 对应 RocketMQ 中 MessageQueue默认情况下 1 个 Topic 包含 4 个 MessageQueue
private LinkedBlockingQueue<String> messageQueue = new LinkedBlockingQueue(Integer.MAX_VALUE);
// 实际发送消息到 broker 服务器使用 Netty 发送
public void sendMsg(String msg) {
try {
messageQueue.put(msg);
// 实际会同步或异步落盘,异步落盘使用的定时任务定时扫描落盘
} catch (InterruptedException e) {
}
}
public String getMsg() {
try {
return messageQueue.take();
} catch (InterruptedException e) {
}
return null;
}
public String getAllMagByDisk() {
StringBuilder sb = new StringBuilder("\n");
messageQueue.iterator().forEachRemaining((msg) -> {
sb.append(msg + "\n");
});
return sb.toString();
}
}
```
问题:
1. 没有实现真正执行消息存储落盘
2. 没有实现 NameServer 去作为注册中心,定位服务
3. 使用 LinkedBlockingQueue 作为消息队列,注意,参数是无限大,在真正 RocketMQ 也是如此是无限大,理论上不会出现对进来的数据进行抛弃,但是会有内存泄漏问题(阿里巴巴开发手册也因为这个问题,建议我们使用自制线程池)
4. 没有使用多个队列(即多个 LinkedBlockingQueueRocketMQ 的顺序消息是通过生产者和消费者同时使用同一个 MessageQueue 来实现,但是如果我们只有一个 MessageQueue那我们天然就支持顺序消息
5. 没有使用 MappedByteBuffer 来实现文件映射从而使消息数据落盘非常的快(实际 RocketMQ 使用的是 FileChannel+DirectBuffer
# 2 分布式消息中心
## 2.1 问题与解决
### 2.1.1 消息丢失的问题
1. 当你系统需要保证百分百消息不丢失你可以使用生产者每发送一个消息Broker 同步返回一个消息发送成功的反馈消息
2. 即每发送一个消息,同步落盘后才返回生产者消息发送成功,这样只要生产者得到了消息发送生成的返回,事后除了硬盘损坏,都可以保证不会消息丢失
3. 但是这同时引入了一个问题,同步落盘怎么才能快?
### 2.1.2 同步落盘怎么才能快
1. 使用 FileChannel + DirectBuffer 池,使用堆外内存,加快内存拷贝
2. 使用数据和索引分离,当消息需要写入时,使用 commitlog 文件顺序写当需要定位某个消息时查询index 文件来定位从而减少文件IO随机读写的性能损耗
### 2.1.3 消息堆积的问题
1. 后台定时任务每隔72小时删除旧的没有使用过的消息信息
2. 根据不同的业务实现不同的丢弃任务,具体参考线程池的 AbortPolicy例如FIFO/LRU等RocketMQ没有此策略
3. 消息定时转移,或者对某些重要的 TAG 型(支付型)消息真正落库
### 2.1.4 定时消息的实现
1. 实际 RocketMQ 没有实现任意精度的定时消息,它只支持某些特定的时间精度的定时消息
2. 实现定时消息的原理是:创建特定时间精度的 MessageQueue例如生产者需要定时1s之后被消费者消费你只需要将此消息发送到特定的 Topic例如MessageQueue-1 表示这个 MessageQueue 里面的消息都会延迟一秒被消费,然后 Broker 会在 1s 后发送到消费者消费此消息,使用 newSingleThreadScheduledExecutor 实现
### 2.1.5 顺序消息的实现
1. 与定时消息同原理,生产者生产消息时指定特定的 MessageQueue ,消费者消费消息时,消费特定的 MessageQueue其实单机版的消息中心在一个 MessageQueue 就天然支持了顺序消息
2. 注意:同一个 MessageQueue 保证里面的消息是顺序消费的前提是:消费者是串行的消费该 MessageQueue因为就算 MessageQueue 是顺序的,但是当并行消费时,还是会有顺序问题,但是串行消费也同时引入了两个问题:
>1. 引入锁来实现串行
>2. 前一个消费阻塞时后面都会被阻塞
### 2.1.6 分布式消息的实现
1. 需要前置知识2PC
2. RocketMQ4.3 起支持原理为2PC即两阶段提交prepared->commit/rollback
3. 生产者发送事务消息,假设该事务消息 Topic 为 Topic1-TransBroker 得到后首先更改该消息的 Topic 为 Topic1-Prepared该 Topic1-Prepared 对消费者不可见。然后定时回调生产者的本地事务A执行状态根据本地事务A执行状态来是否将该消息修改为 Topic1-Commit 或 Topic1-Rollback消费者就可以正常找到该事务消息或者不执行等
>注意,就算是事务消息最后回滚了也不会物理删除,只会逻辑删除该消息
### 2.1.7 消息的 push 实现
1. 注意RocketMQ 已经说了自己会有低延迟问题,其中就包括这个消息的 push 延迟问题
2. 因为这并不是真正的将消息主动的推送到消费者,而是 Broker 定时任务每5s将消息推送到消费者
### 2.1.8 消息重复发送的避免
1. RocketMQ 会出现消息重复发送的问题,因为在网络延迟的情况下,这种问题不可避免的发生,如果非要实现消息不可重复发送,那基本太难,因为网络环境无法预知,还会使程序复杂度加大,因此默认允许消息重复发送
2. RocketMQ 让使用者在消费者端去解决该问题,即需要消费者端在消费消息时支持幂等性的去消费消息
3. 最简单的解决方案是每条消费记录有个消费状态字段,根据这个消费状态字段来是否消费或者使用一个集中式的表,来存储所有消息的消费状态,从而避免重复消费
4. 具体实现可以查询关于消息幂等消费的解决方案
### 2.1.9 广播消费与集群消费
1. 消息消费区别:广播消费,订阅该 Topic 的消息者们都会消费**每个**消息。集群消费,订阅该 Topic 的消息者们只会有一个去消费**某个**消息
2. 消息落盘区别:具体表现在消息消费进度的保存上。广播消费,由于每个消费者都独立的去消费每个消息,因此每个消费者各自保存自己的消息消费进度。而集群消费下,订阅了某个 Topic而旗下又有多个 MessageQueue每个消费者都可能会去消费不同的 MessageQueue因此总体的消费进度保存在 Broker 上集中的管理
### 2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?
1. ZooKeeper 作为支持顺序一致性的中间件在某些情况下它为了满足一致性会丢失一定时间内的可用性RocketMQ 需要注册中心只是为了发现组件地址在某些情况下RocketMQ 的注册中心可以出现数据不一致性,这同时也是 NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致
2. 另外当有新的服务器加入时NameServer 并不会立马通知到 Produer而是由 Produer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息(这种情况是通过 Producer 发送消息时,负载均衡解决)
### 2.1.11 其它
![][1]
加分项咯
1. 包括组件通信间使用 Netty 的自定义协议
2. 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略)
3. 消息过滤器Producer 发送消息到 BrokerBroker 存储消息信息Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤)
4. Broker 同步双写和异步双写中 Master 和 Slave 的交互
5. Broker 在 4.5.0 版本更新中引入了基于 Raft 协议的多副本选举,之前这是商业版才有的特性 [ISSUE-1046][2]
# 3 参考
1. 《RocketMQ技术内幕》https://blog.csdn.net/prestigeding/article/details/85233529
2. 关于 RocketMQ 对 MappedByteBuffer 的一点优化https://lishoubo.github.io/2017/09/27/MappedByteBuffer%E7%9A%84%E4%B8%80%E7%82%B9%E4%BC%98%E5%8C%96/
3. 阿里中间件团队博客-十分钟入门RocketMQhttp://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/
4. 分布式事务的种类以及 RocketMQ 支持的分布式消息https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
5. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践https://yq.aliyun.com/articles/664608
6. 基于《RocketMQ技术内幕》源码注释https://github.com/LiWenGu/awesome-rocketmq
[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/RocketMQ%E6%B5%81%E7%A8%8B.png
[2]: http://rocketmq.apache.org/release_notes/release-notes-4.5.0/

View File

@ -44,7 +44,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
**什么是 RPC** **什么是 RPC**
RPCRemote Procedure Call—远程过程调用它是一种通过网络从远程计算机程序上请求服务而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。 RPCRemote Procedure Call—远程过程调用它是一种通过网络从远程计算机程序上请求服务而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
**RPC原理是什么** **RPC原理是什么**
@ -81,10 +81,10 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo 我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo
1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务 1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务
2. **服务调用链路生成**——随着系统的发展服务越来越多服务间依赖关系变得错踪复杂甚至分不清哪个应用要在哪个应用之前启动架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。 2. **服务调用链路生成**——随着系统的发展服务越来越多服务间依赖关系变得错踪复杂甚至分不清哪个应用要在哪个应用之前启动架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。
4. **服务降级**——某个服务挂掉之后调用备用服务 4. **服务降级**——某个服务挂掉之后调用备用服务
另外Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。 另外Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。
@ -98,7 +98,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。 从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? 另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
## 二 Dubbo 的架构 ## 二 Dubbo 的架构
@ -108,20 +108,20 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
**上述节点简单说明:** **上述节点简单说明:**
- **Provider** 暴露服务的服务提供方 - **Provider** 暴露服务的服务提供方
- **Consumer** 调用远程服务的服务消费方 - **Consumer** 调用远程服务的服务消费方
- **Registry** 服务注册与发现的注册中心 - **Registry** 服务注册与发现的注册中心
- **Monitor** 统计服务的调用次数和调用时间的监控中心 - **Monitor** 统计服务的调用次数和调用时间的监控中心
- **Container** 服务运行容器 - **Container** 服务运行容器
**调用关系说明:** **调用关系说明:**
1. 服务容器负责启动,加载,运行服务提供者。 1. 服务容器负责启动,加载,运行服务提供者。
2. 服务提供者在启动时,向注册中心注册自己提供的服务。 2. 服务提供者在启动时,向注册中心注册自己提供的服务。
3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
**重要知识点总结:** **重要知识点总结:**
@ -156,7 +156,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
- 第七层:**protocol层**远程调用层封装rpc调用 - 第七层:**protocol层**远程调用层封装rpc调用
- 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步 - 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步
- 第九层:**transport层**网络传输层抽象mina和netty为统一接口 - 第九层:**transport层**网络传输层抽象mina和netty为统一接口
- 第十层:**serialize层**,数据序列化层。网络传输需要。 - 第十层:**serialize层**,数据序列化层,网络传输需要
## 三 Dubbo 的负载均衡策略 ## 三 Dubbo 的负载均衡策略
@ -165,7 +165,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
**先来个官方的解释。** **先来个官方的解释。**
> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件 > 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件
**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。** **上面讲的大家可能不太好理解,再用通俗的话给大家说一下。**

View File

@ -75,7 +75,7 @@
#### 4.1.1 JMS 简介 #### 4.1.1 JMS 简介
  JMSJAVA Message Service,java消息服务是java的消息服务JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMSJAVA Message Service,Java消息服务API是一个消息服务的标准或者说是规范**允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低消息服务更加可靠以及异步性。   JMSJAVA Message Service,java消息服务是java的消息服务JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMSJAVA Message ServiceJava消息服务API是一个消息服务的标准或者说是规范**允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低消息服务更加可靠以及异步性。
**ActiveMQ 就是基于 JMS 规范实现的。** **ActiveMQ 就是基于 JMS 规范实现的。**
@ -84,12 +84,16 @@
①点到点P2P模型 ①点到点P2P模型
![点到点P2P模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7185572ca37d?w=575&h=135&f=gif&s=8530) ![点到点P2P模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7185572ca37d?w=575&h=135&f=gif&s=8530)
  使用**队列Queue**作为消息通信载体;满足**生产者与消费者模式**一条消息只能被一个消费者使用未被消费的消息在队列中保留直到被消费或超时。比如我们生产者发送100条消息的话两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半也就是你一个我一个的消费。   
使用**队列Queue**作为消息通信载体;满足**生产者与消费者模式**一条消息只能被一个消费者使用未被消费的消息在队列中保留直到被消费或超时。比如我们生产者发送100条消息的话两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半也就是你一个我一个的消费。
② 发布/订阅Pub/Sub模型 ② 发布/订阅Pub/Sub模型
![发布/订阅Pub/Sub模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7187c268eaa5?w=402&h=164&f=gif&s=15492) ![发布/订阅Pub/Sub模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7187c268eaa5?w=402&h=164&f=gif&s=15492)
  发布订阅模型Pub/Sub 使用**主题Topic**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。   
发布订阅模型Pub/Sub 使用**主题Topic**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
#### 4.1.3 JMS 五种不同的消息正文格式 #### 4.1.3 JMS 五种不同的消息正文格式
@ -104,7 +108,7 @@
### 4.2 AMQP ### 4.2 AMQP
   AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息并不受客户端/中间件同产品,不同的开发语言等条件的限制。    AMQP即Advanced Message Queuing Protocol一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息并不受客户端/中间件同产品,不同的开发语言等条件的限制。
**RabbitMQ 就是基于 AMQP 协议实现的。** **RabbitMQ 就是基于 AMQP 协议实现的。**

View File

@ -123,7 +123,7 @@ direct 类型常用在处理有优先级的任务,根据任务的优先级把
- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”; - RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串; - BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“#”用于匹配一个单词,“#”用于匹配多规格单词(可以是零个)。 - BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“.”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
![topic 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/73843.jpg) ![topic 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/73843.jpg)
@ -145,6 +145,10 @@ headers 类型的交换器不依赖于路由键的匹配规则来路由消息,
前面提到了 RabbitMQ 是由 Erlang语言编写的也正因如此在安装RabbitMQ 之前需要安装 Erlang。 前面提到了 RabbitMQ 是由 Erlang语言编写的也正因如此在安装RabbitMQ 之前需要安装 Erlang。
注意:在安装 RabbitMQ 的时候需要注意 RabbitMQ 和 Erlang 的版本关系,如果不注意的话会导致出错,两者对应关系如下:
![RabbitMQ 和 Erlang 的版本关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RabbitMQ-Erlang.png)
### 2.1 安装 erlang ### 2.1 安装 erlang
**1 下载 erlang 安装包** **1 下载 erlang 安装包**

Some files were not shown because too many files have changed in this diff Show More