diff --git a/.gitattributes b/.gitattributes index 05d15409683..2ab2d3c5962 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,4 @@ -# Auto detect text files and perform LF normalization * text=auto *.js linguist-language=java *.css linguist-language=java -*.html linguist-language=java \ No newline at end of file +*.html linguist-language=java diff --git "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" "b/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" deleted file mode 100644 index 238e7c8b5df..00000000000 --- "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" +++ /dev/null @@ -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) diff --git "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index cb0bd1fe0e3..00000000000 --- "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,353 +0,0 @@ - - -1. [List,Set,Map三者的区别及总结](#list,setmap三者的区别及总结) -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. [集合的常用方法](#集合的常用方法) - - - - -## List,Set,Map三者的区别及总结 -- **List:对付顺序的好帮手** - - List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象 -- **Set:注重独一无二的性质** - - 不允许重复的集合。不会有多个元素引用相同的对象。 - -- **Map:用Key来搜索的专家** - - 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。 - - -## Arraylist 与 LinkedList 区别 -Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向链表数据结构(插入,删除效率特别高)(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 - -## ArrayList 与 Vector 区别 -Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector -,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。 - -## HashMap 和 Hashtable 的区别 -1. HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。 - -2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。 -3. HashMap允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。 - -Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧 - -## HashSet 和 HashMap 区别 -![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) - -## HashMap 和 ConcurrentHashMap 的区别 -[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都不允许。 - -## HashSet如何检查重复 -当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同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()指的是值是否相同 - -## 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 -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 arrayList = new ArrayList(); - 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() { - - @Override - public int compare(Integer o1, Integer o2) { - return o2.compareTo(o1); - } - }); - System.out.println("定制排序后:"); - System.out.println(arrayList); - } - -} - -``` -### 重写compareTo方法实现按年龄来排序 -```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 pdata = new TreeMap(); - 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 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 { - 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; - } -} -``` - -## 如何对Object的list排序 -- 对objects数组进行排序,我们可以用Arrays.sort()方法 -- 对objects的集合进行排序,需要使用Collections.sort()方法 - - -## 如何实现数组与List的相互转换 -List转数组:toArray(arraylist.size()方法;数组转List:Arrays的asList(a)方法 -```java -List arrayList = new ArrayList(); - 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 list=Arrays.asList(a); - /** - * list转Arraylist - */ - List arrayList2 = new ArrayList(); - arrayList2.addAll(list); - System.out.println(list); -``` -## 如何求ArrayList集合的交集 并集 差集 去重复并集 -需要用到List接口中定义的几个方法: - -- addAll(Collection 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 list1 = new ArrayList(); - list1.add(1); - list1.add(2); - list1.add(3); - list1.add(4); - - List list2 = new ArrayList(); - 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); - } - } - -} - -``` - -## HashMap 的工作原理及代码实现 - -[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56) - -## ConcurrentHashMap 的工作原理及代码实现 - -[ConcurrentHashMap实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html) - - -## 集合框架底层数据结构总结 -### - Collection - -#### 1. List - - Arraylist:数组(查询快,增删慢 线程不安全,效率高 ) - - Vector:数组(查询快,增删慢 线程安全,效率低 ) - - LinkedList:链表(查询慢,增删快 线程不安全,效率高 ) - -#### 2. Set - - HashSet(无序,唯一):哈希表或者叫散列集(hash table) - - LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性 - - TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。) - -### - Map - - HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对) - - LinkedHashMap:HashMap 的基础上加上了链表数据结构 - - HashTable:哈希表 - - TreeMap:红黑树(自平衡的排序二叉树) - - -## 集合的选用 -主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。 - -2018/3/11更新 -## 集合的常用方法 -今天下午无意看见一道某大厂的面试题,面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合,但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混,所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。 - -会持续更新。。。 - -**参考书籍:** - -《Head first java 》第二版 推荐阅读真心不错 (适合基础较差的) - - 《Java核心技术卷1》推荐阅读真心不错 (适合基础较好的) - - 《算法》第四版 (适合想对数据结构的Java实现感兴趣的) - diff --git "a/Java\347\233\270\345\205\263/Multithread/Atomic.md" "b/Java\347\233\270\345\205\263/Multithread/Atomic.md" deleted file mode 100644 index 33dd7ef3d9b..00000000000 --- "a/Java\347\233\270\345\205\263/Multithread/Atomic.md" +++ /dev/null @@ -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 ar = new AtomicReference(); - 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 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 -``` - diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" deleted file mode 100644 index 61d5e722174..00000000000 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ /dev/null @@ -1,342 +0,0 @@ - - -## 写在前面(常见面试题) - -### 基本问题: - -- **介绍下 Java 内存区域(运行时数据区)** -- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)** -- **对象的访问定位的两种方式(句柄和直接指针两种方式)** - -### 拓展问题: - -- **String类和常量池** -- **8种基本类型的包装类和常量池** - - -## 1 概述 - -对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 - - -## 2 运行时数据区域 -Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 -![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png) - -这些组成部分一些是线程私有的,其他的则是线程共享的。 - -**线程私有的:** - -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享的:** - -- 堆 -- 方法区 -- 直接内存 - - -### 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 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - - -### 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种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。** -- **两种浮点数类型的包装类 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=40;Java 在编译的时候会直接将代码封装成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 - - - - - diff --git "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" "b/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" deleted file mode 100644 index 819d37faca2..00000000000 --- "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" +++ /dev/null @@ -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 有什么不同呢?** - -- **新生代GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。 -- **老年代GC(Major 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收集器 - -**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。** - -**CMS(Concurrent 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、多核环境下的硬件优势,使用多个CPU(CPU或者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 - - - - - - - - - - - - - diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" deleted file mode 100644 index 18d276c4e22..00000000000 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ /dev/null @@ -1,288 +0,0 @@ - - - - -- [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) - - [推荐阅读:](#推荐阅读:) - - - -## 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 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** -- **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 - int binarySearch(List> list, T key) { - if (list instanceof RandomAccess || list.size()>>:无符号右移,忽略符号位,空位都以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(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 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的ConcurrentHashMap(TreeBin: 红黑二叉树节点 -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 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) diff --git a/README.md b/README.md index 8aef7455edf..96a459b6da8 100644 --- a/README.md +++ b/README.md @@ -1,200 +1,254 @@ -# Java 学习/面试指南 +点击订阅[Java面试进阶指南](https://xiaozhuanlan.com/javainterview?rel=javaguide)(专为Java面试方向准备)。[为什么要弄这个专栏?](https://shimo.im/docs/9BJjNsNg7S4dCnz3/) -
- -
-
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 -## 阅读之前必看 +

+ + + +

+ +

+ 阅读 + 微信群 + 公众号 + 公众号 + 投稿 +

+ +

Special Sponsors

+

+ + + 零成本开启敏捷研发 + +

-1. **加群:** 微信交流群添加 [我的微信](#联系我) 后回复关键字“加群”即可入群。 -2. **Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。 -3. **《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -4. **关于贡献者:** 对本仓库提过有价值的 issue 或 pr 的小伙伴将出现在 [Contributor](#Contributor) 这里。 -5. **欢迎投稿:** 由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。**对于不错的原创文章我根据你的选择给予现金(50-300)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 -4. **【限时福利】** 极客时间[《Java 并发编程面试必备》](#Java并发编程专栏)专栏限时特惠,购买之后的小伙伴加 [我的微信](#联系我) 报上自己的极客时间大名可以找我会把24元返现退给大家,减轻各位学习成本。 +推荐使用 https://snailclimb.top/JavaGuide/ 在线阅读(访问速度慢的话,请使用 https://snailclimb.gitee.io/javaguide ),在线阅读内容本仓库同步一致。这种方式阅读的优势在于:有侧边栏阅读体验更好,Gitee pages 的访问速度相对来说也比较快。 ## 目录 -* [:coffee: Java](#coffee-java) - * [Java/J2EE 基础](#javaj2ee-基础) - * [Java 集合框架](#java-集合框架) - * [Java 多线程](#java-多线程) - * [Java BIO,NIO,AIO](#java-bionioaio) - * [Java 虚拟机 jvm](#java-虚拟机-jvm) - * [设计模式](#设计模式) -* [:open_file_folder: 数据结构与算法](#open_file_folder-数据结构与算法) - * [数据结构](#数据结构) - * [算法](#算法) -* [:computer: 计算机网络与数据通信](#computer-计算机网络与数据通信) - * [网络相关](#网络相关) - * [数据通信\(RESTful,RPC,消息队列\)总结](#数据通信restfulrpc消息队列总结) -* [:iphone: 操作系统](#iphone-操作系统) - * [Linux相关](#linux相关) -* [:pencil2: 主流框架/软件](#pencil2-主流框架软件) - * [Spring](#spring) - * [ZooKeeper](#zookeeper) -* [:floppy_disk: 数据存储](#floppy_disk-数据存储) - * [MySQL](#mysql) - * [Redis](#redis) -* [:punch: 架构](#punch-架构) -* [:musical_note: 面试必备](#musical_note-面试必备) - * [备战面试](#备战面试) - * [最最最常见的Java面试题总结](#最最最常见的java面试题总结) - * [Java学习/面试开源仓库推荐](#java学习面试开源仓库推荐) -* [:art: 闲谈](#art-闲谈) -* [:envelope: 说明](#envelope-说明) - -## 待办 - -* [ ] Java 8 新特性总结 -* [ ] Java 多线程类别知识重构 -* [x] BIO,NIO,AIO 总结 -* [ ] Netty 总结 -* [ ] 数据结构总结重构 - -## :coffee: Java +- [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 基础知识回顾](docs/java/Java基础知识.md) +* [Java 基础知识疑难点/易错点](docs/java/Java疑难点.md) +* [一些重要的Java程序设计题](docs/java/Java程序设计题.md) +* [J2EE 基础知识回顾](docs/java/J2EE基础知识.md) + +### 容器 + +* [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 并发基础常见面试题总结](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) + +### JVM + +* [一 Java内存区域](docs/java/jvm/Java内存区域.md) +* [二 JVM垃圾回收](docs/java/jvm/JVM垃圾回收.md) +* [三 JDK 监控和故障处理工具](docs/java/jvm/JDK监控和故障处理工具总结.md) +* [四 类文件结构](docs/java/jvm/类文件结构.md) +* [五 类加载过程](docs/java/jvm/类加载过程.md) +* [六 类加载器](docs/java/jvm/类加载器.md) + +### I/O + +* [BIO,NIO,AIO 总结 ](docs/java/BIO-NIO-AIO.md) +* [Java IO 与 NIO系列文章](docs/java/Java%20IO与NIO.md) + +### Java 8 + +* [Java 8 新特性总结](docs/java/What's%20New%20in%20JDK8/Java8Tutorial.md) +* [Java 8 学习资源推荐](docs/java/What's%20New%20in%20JDK8/Java8教程推荐.md) + +### 编程规范 + +- [Java 编程规范](docs/java/Java编程规范.md) + +## 网络 + +* [计算机网络常见面试题](docs/network/计算机网络.md) +* [计算机网络基础知识总结](docs/network/干货:计算机网络知识总结.md) +* [HTTPS中的TLS](docs/network/HTTPS中的TLS.md) + +## 操作系统 -### Java/J2EE 基础 - -* [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) -* [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) -* [Java常见关键字总结:static、final、this、super](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) - - -### Java 集合框架 - -* [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) -* [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) +### Linux相关 -### Java 多线程 +* [后端程序员必备的 Linux 基础知识](docs/operating-system/后端程序员必备的Linux基础知识.md) +* [Shell 编程入门](docs/operating-system/Shell.md) -* [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) -* [并发编程面试必备:乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) -* [并发编程面试必备:JUC 中的 Atomic 原子类总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/Atomic.md) -* [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Multithread/AQS.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 +### 数据结构 -* [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) -* [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) -* [《深入理解Java虚拟机》第2版学习笔记](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机(jvm).md) +- [数据结构知识学习与面试](docs/dataStructures-algorithms/数据结构.md) -### Java BIO,NIO,AIO +### 算法 -* [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) +- [算法学习资源推荐](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) -### 设计模式 +## 数据库 -* [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) +### MySQL -## :open_file_folder: 数据结构与算法 +* [MySQL 学习与面试](docs/database/MySQL.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 -* [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md) +* [Redis 总结](docs/database/Redis/Redis.md) +* [Redlock分布式锁](docs/database/Redis/Redlock分布式锁.md) +* [如何做可靠的分布式锁,Redlock真的可行么](docs/database/Redis/如何做可靠的分布式锁,Redlock真的可行么.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: 计算机网络与数据通信 +- [设计模式系列文章](docs/system-design/设计模式.md) -### 网络相关 +### 常用框架 -* [计算机网络常见面试题](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) +#### Spring -### 数据通信(RESTful,RPC,消息队列)总结 +- [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) -* [数据通信(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) +#### ZooKeeper -## :iphone: 操作系统 +- [ZooKeeper 相关概念总结](docs/system-design/framework/ZooKeeper.md) +- [ZooKeeper 数据模型和常见命令](docs/system-design/framework/ZooKeeper数据模型和常见命令.md) -### Linux相关 +### 数据通信 -* [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) -* [Shell 编程入门](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/Shell.md) -## :pencil2: 主流框架/软件 +- [数据通信(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) -### 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) +- [一文读懂分布式应该学什么](docs/system-design/website-architecture/分布式.md) +- [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md) +- [【面试精选】关于大型网站系统架构你不得不懂的10个问题](docs/system-design/website-architecture/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md) -### ZooKeeper +## 面试指南 -* [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) -* [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper数据模型和常见命令.md) - -## :floppy_disk: 数据存储 +### 备战面试 -### MySQL +* [【备战面试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) -* [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) -* [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) +### 常见面试题总结 -### Redis +* [第一周(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) (主要内容是几道面试常问的多线程基础题。) -* [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) -* [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) -* [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md) +### 面经 -## :punch: 架构 +- [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) -* [一文读懂分布式应该学什么](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: 面试必备 +### Git -### 备战面试 +* [Git入门](docs/tools/Git.md) -* [【备战面试1】程序员的简历就该这样写](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) -* [【备战面试2】初出茅庐的程序员该如何准备面试?](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/interviewPrepare.md) -* [【备战面试3】7个大部分程序员在面试前很关心的问题](https://github.com/Snailclimb/JavaGuide/blob/master/面试必备/JavaProgrammerNeedKnow.md) -* [【备战面试4】Java程序员必备书单](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/books.md) -* [【备战面试5】美团面试常见问题总结(附详解答案)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/美团面试常见问题总结.md) +### Docker +* [Docker 入门](docs/tools/Docker.md) +* [一文搞懂 Docker 镜像的常用操作!](docs/tools/Docker-Image.md) -### 最最最常见的Java面试题总结 +## 资料 -这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 +### 书单 -* [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) -* [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) -* [第三周(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程序员必备书单](docs/data/java-recommended-books.md) -### Java学习/面试开源仓库推荐 +### Github榜单 -* [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/JavaInterviewGithub.md) +- [Java 项目月榜单](docs/github-trending/JavaGithubTrending.md) -## :art: 闲谈 +*** -* [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/闲谈/选择技术方向都要考虑哪些因素.md) -* [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/2018%20%E7%A7%8B%E6%8B%9B.md) -* [【2018总结】即使平凡,也要热爱自己的生活](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/2018%20summary.md) -* [Java项目 Github Trending 月榜](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/JavaGithubTrending.md) +## 待办 -*** +- [ ] Java 多线程类别知识重构(---正在进行中---) +- [ ] Netty 总结(---正在进行中---) +- [ ] 数据结构总结重构(---正在进行中---) -## :envelope: 说明 +## 说明 ### 介绍 @@ -202,7 +256,9 @@ 也是你学习和复习 Java 知识不错的实践; * **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。 -本文档 Markdown 格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 +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/#/) ### 关于转载 @@ -211,24 +267,22 @@ ### 如何对该开源文档进行贡献 1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。 -2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。(**对于不错的原创文章我根据你的选择给予现金奖励、付费专栏或者书籍进行奖励!快提 pr 给我投稿吧!**) +2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。 3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点的修改/补充。 ### 为什么要做这个开源文档? 初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。 -### 联系我 +### 投稿 -如果大家需要与我交流,可以扫描下方二维码添加我的微信获取关注[我的公众号](#公众号): +由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。大家也可以对自己的文章进行自荐,对于不错的文章不仅可以成功在本仓库展示出来更可以获得作者送出的 50 元左右的任意书籍进行奖励(当然你也可以直接折现50元)。 -![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg) - -### Java并发编程专栏 +### 联系我 -微信扫描下方二维码,购买之后我会将自己得到的24元返现都还给你,减轻各位的学习成本! +添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。 -![ Java并发编程专栏](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Java并发编程实战.jpg) +![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/wechat1.jpg) ### Contributor @@ -236,9 +290,13 @@ + + + + @@ -260,9 +318,25 @@ + + + + + + + + + + + + ### 公众号 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 -![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334) +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! + +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 + +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/HomePage.md b/docs/HomePage.md new file mode 100644 index 00000000000..55000c08dee --- /dev/null +++ b/docs/HomePage.md @@ -0,0 +1,242 @@ +点击订阅[Java面试进阶指南](https://xiaozhuanlan.com/javainterview?rel=javaguide)(专为Java面试方向准备)。[为什么要弄这个专栏?](https://shimo.im/./9BJjNsNg7S4dCnz3/) + +

Java 学习/面试指南

+

+ + + + +## 目录 + +- [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”** 即可免费无套路获取。 + +

+ +

diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 00000000000..612cd2160ec --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,10 @@ +

+ +

+ +

Java 学习/面试指南

+ +[常用资源](https://shimo.im/docs/MuiACIg1HlYfVxrj/) +[GitHub]() +[开始阅读](#java) + diff --git a/docs/data/java-recommended-books.md b/docs/data/java-recommended-books.md new file mode 100644 index 00000000000..7f387ce46ae --- /dev/null +++ b/docs/data/java-recommended-books.md @@ -0,0 +1,115 @@ + + + +- [Java](#java) + - [基础](#基础) + - [并发](#并发) + - [JVM](#jvm) + - [Java8 新特性](#java8-新特性) + - [代码优化](#代码优化) +- [网络](#网络) +- [操作系统](#操作系统) +- [数据结构与算法](#数据结构与算法) +- [数据库](#数据库) +- [系统设计](#系统设计) + - [设计模式](#设计模式) + - [常用框架](#常用框架) + - [网站架构](#网站架构) + - [软件底层](#软件底层) +- [其他](#其他) + + +## Java + +### 基础 + +- [《Head First Java》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.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.1,3.2K+人评价):大部分人称之为Java领域的圣经,但我不推荐初学者阅读,有点劝退的味道。稍微有点基础后阅读更好。 + +### 并发 + +- [《Java 并发编程之美》]() (推荐):2018 年 10 月出版的一本书,个人感觉非常不错,对每个知识点的讲解都很棒。 +- [《Java 并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.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.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过 JAVA 运行时区域和 JAVA 的内存模型与线程两个大模块罗列完全。 +- [《实战 JAVA 虚拟机》](https://book.douban.com/subject/26354292/)(推荐,豆瓣评分 8.0,1.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.0,1.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》](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.1,0.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.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第 2 版的简体中译本。原书曾被评为 20 世纪顶尖的 30 部计算机著作之一,作者 Mark Allen Weiss 在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界 500 余所大学用作教材。 +- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! +- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java 语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是 Java 程序员的必备书籍之一了。 + +## 数据库 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 +- [《Redis 实战》](https://book.douban.com/subject/26612779/):如果你想了解 Redis 的一些概念性知识的话,这本书真的非常不错。 +- [《Redis 设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价):也还行吧! +- [《MySQL 技术内幕-InnoDB 存储引擎》]()(推荐,豆瓣评分 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.8,92 人评价):内容很细,如果想学 Netty 的话,推荐阅读这本书! +- [《从 Paxos 到 Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K 人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解 ZooKeeper,并更好地使用和运维 ZooKeeper。 +- [《Spring 实战(第 4 版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3,0.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.4,0.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 的文集。之所以叫这个名字,是因为作者认为黑客(并非负面的那个意思)与画家有着极大的相似性,他们都是在创造,而不是完成某个任务。 + + + + + + diff --git a/docs/dataStructures-algorithms/Backtracking-NQueens.md b/docs/dataStructures-algorithms/Backtracking-NQueens.md new file mode 100644 index 00000000000..bac262d0fbb --- /dev/null +++ b/docs/dataStructures-algorithms/Backtracking-NQueens.md @@ -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> solveNQueens(int n) { + // 下标代表行,值代表列。如result[0] = 3 表示第1行的Q在第3列 + int[] result = new int[n]; + List> resultList = new LinkedList<>(); + dfs(resultList, result, 0, n); + return resultList; +} + +void dfs(List> resultList, int[] result, int row, int n) { + // 递归终止条件 + if (row == n) { + List 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 = 0,i = n时),可以加上n作为数组下标,即将row -i + n 作为数组下标。 +row - i + n 的最大值为 2n(当row = n,i = 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> resultList = new LinkedList<>(); + +public List> solveNQueens(int n) { + boolean[] cols = new boolean[n]; + boolean[] main_diag = new boolean[2 * n]; + boolean[] anti_diag = new boolean[2 * n]; + LinkedList result = new LinkedList<>(); + dfs(result, 0, cols, main_diag, anti_diag, n); + return resultList; +} + +void dfs(LinkedList result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) { + if (row == n) { + List 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; + } +} +``` \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\205\254\345\217\270\347\234\237\351\242\230/\347\275\221\346\230\2232018\346\240\241\346\213\233\347\274\226\347\250\213\351\242\2301-3.md" "b/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md" similarity index 93% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\205\254\345\217\270\347\234\237\351\242\230/\347\275\221\346\230\2232018\346\240\241\346\213\233\347\274\226\347\250\213\351\242\2301-3.md" rename to "docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md" index d929d20046c..c78ed8f3e5a 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\205\254\345\217\270\347\234\237\351\242\230/\347\275\221\346\230\2232018\346\240\241\346\213\233\347\274\226\347\250\213\351\242\2301-3.md" +++ "b/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md" @@ -1,9 +1,10 @@ +# 网易 2018 + 下面三道编程题来自网易2018校招编程题,这三道应该来说是非常简单的编程题了,这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做,然后再对照这我的答案看看,和你自己想的有什么区别?那一种方法更好? -![问题](https://user-gold-cdn.xitu.io/2018/7/7/1647557d5a1474d7?w=1024&h=1024&f=jpeg&s=638271) -> # 问题 +## 问题 -## 一 获得特定数量硬币问题 +### 一 获得特定数量硬币问题 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。 @@ -15,33 +16,30 @@ **输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。 - **输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。 **输入例子1:** 10 **输出例子1:** 122 -## 二 求“相反数”问题 +### 二 求“相反数”问题 为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1. **输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5) - **输出描述:** 输出一个整数,表示n的相反数 **输入例子1:** 1325 **输出例子1:** 6556 -## 三 字符串碎片的平均长度 +### 三 字符串碎片的平均长度 一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。 **输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z') - **输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。 **如样例所示:** s = "aaabbaaac" @@ -51,19 +49,18 @@ **输出例子1:** 2.25 -![答案](https://user-gold-cdn.xitu.io/2018/7/7/16475582faddc9b2?w=1024&h=1024&f=jpeg&s=531663) - -> # 答案 +## 答案 -## 一 获得特定数量硬币问题 +### 一 获得特定数量硬币问题 -### 分析: +#### 分析: 作为该试卷的第一题,这道题应该只要思路正确就很简单了。 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。 -### 示例代码 +#### 示例代码 + 注意:由于用户的输入不确定性,一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本,这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常,后面的题目中我给的代码没有异常处理的部分,参照下面两个示例代码,应该很容易添加。(PS:企业面试中没有明确就不用添加异常处理,当然你有的话也更好) **不带输入异常处理判断的版本:** @@ -141,32 +138,33 @@ public class Main { ``` +### 二 求“相反数”问题 -## 二 求“相反数”问题 - -### 分析: +#### 分析: 解决本道题有几种不同的方法,但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数,这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点: **1)String转int;** 在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换. + ```java String str = "123"; int a = Integer.parseInt(str); ``` + 或 + ```java String str = "123"; int a = Integer.valueOf(str).intValue(); ``` - **2)next()和nextLine()的区别** 在Java中输入字符串有两种方法,就是next()和nextLine().两者的区别就是:nextLine()的输入是碰到回车就终止输入,而next()方法是碰到空格,回车,Tab键都会被视为终止符。所以next()不会得到带空格的字符串,而nextLine()可以得到带空格的字符串。 -### 示例代码: +#### 示例代码: ```java import java.util.Scanner; @@ -192,15 +190,15 @@ public class Main { } ``` -## 三 字符串碎片的平均长度 +### 三 字符串碎片的平均长度 -### 分析: +#### 分析: 这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。 这样就很简单了,就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话,我们可以利用charAt(i)方法:取出特定位置的字符与后一个字符比较,或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。 -### 示例代码 +#### 示例代码 **利用charAt(i)方法:** diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" similarity index 97% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" rename to "docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" index 938f4a48f1a..cd2a3e31e2b 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" +++ "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -112,7 +112,7 @@ public class Main { public static String replaceSpace(String[] strs) { // 如果检查值不合法及就返回空串 - if (!chechStrs(strs)) { + if (!checkStrs(strs)) { return ""; } // 数组长度 @@ -135,21 +135,17 @@ public class Main { } - private static boolean chechStrs(String[] strs) { - boolean flag = false; - // 注意:=是赋值,==是判断 - if (strs != null) { - // 遍历strs检查元素值 - for (int i = 0; i < strs.length; i++) { - if (strs[i] != null && strs[i].length() != 0) { - flag = true; - } else { - flag = false; - } - } - } - return flag; - } + private static boolean checkStrs(String[] strs) { + if (strs != null) { + // 遍历strs检查元素值 + for (int i = 0; i < strs.length; i++) { + if (strs[i] == null || strs[i].length() == 0) { + return false; + } + } + } + return true; + } // 测试 public static void main(String[] args) { @@ -463,7 +459,7 @@ public class Main { return 0; } } - return flag == 1 ? res : -res; + return flag != 2 ? res : -res; } diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" similarity index 99% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" rename to "docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" index 79b74441deb..85e2934e407 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" +++ "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" @@ -225,7 +225,7 @@ public class Solution { while (node1 != null) { node1 = node1.next; count++; - if (k < 1 && node1 != null) { + if (k < 1) { node2 = node2.next; } k--; diff --git "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" new file mode 100644 index 00000000000..51de35eac44 --- /dev/null +++ "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" @@ -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.由a,b假设可以得出总跳法为: 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; + } +} +``` + +当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。 + +```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的元素开始把该偶数添加到新数组中。 + +#### **示例代码:** + +时间复杂度为O(n),空间复杂度为O(n)的算法 + +```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 stack1 = new Stack(); + Stack stack2 = new Stack(); + + //当执行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是某栈的压入顺序,序列4,5,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 + +此时栈顶4=4,出栈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 s = new Stack(); + //用于标识弹出序列的位置 + 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(); + } +} +``` \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" similarity index 90% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" rename to "docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" index 9889644f39c..f3696d2cd38 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -1,3 +1,4 @@ +下面只是简单地总结,给了一些参考文章,后面会对这部分内容进行重构。 - [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) ### 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) ### 推荐阅读 @@ -102,7 +104,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 (1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。 - (2)[满二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 + (2)[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。 (3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 @@ -110,9 +112,9 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科) - 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 + 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。 * ### 3 满二叉树 - + [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。 @@ -120,7 +122,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191) - 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆 + 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。 * ### 4 二叉查找树(BST) [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html) @@ -129,7 +131,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值; 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; - 3. 任意节点的左、右子树也分别为二叉查找树。 + 3. 任意节点的左、右子树也分别为二叉查找树; 4. 没有键值相等的节点(no duplicate nodes)。 * ### 5 平衡二叉树(Self-balancing binary search tree) @@ -142,16 +144,16 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 2. 根节点总是黑色的; 3. 每个叶子节点都是黑色的空节点(NIL节点); 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); - 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) + 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。 - 红黑树的应用: - + TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 - 为什么要用红黑树 - + 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - + - 推荐文章: - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错) @@ -163,19 +165,19 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186) [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) - - B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) + + B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 - 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 + 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高; * ### 8 LSM 树 [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) - + B+树最大的性能问题是会产生大量的随机IO 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。 - + [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) diff --git "a/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md" "b/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md" new file mode 100644 index 00000000000..4c5df56a862 --- /dev/null +++ "b/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md" @@ -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) + + + + + + + + + + + + + + + + + diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" b/docs/database/MySQL Index.md similarity index 86% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" rename to docs/database/MySQL Index.md index 27b82c8bc70..e2e9dc534db 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" +++ b/docs/database/MySQL Index.md @@ -28,8 +28,8 @@ MySQL的基本存储结构是页(记录都存在页里边): 所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做: -1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** -2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** +1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** +2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** 很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 @@ -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 ; // 可以命中索引 -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 )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 -MySQLS.7 版本后,可以通过查询 sys 库的 `schemal_r dundant_indexes` 表来查看冗余索引 +MySQLS.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引 ### Mysql如何为表字段添加索引??? diff --git a/docs/database/MySQL.md b/docs/database/MySQL.md new file mode 100644 index 00000000000..ba5a5fa94b2 --- /dev/null +++ b/docs/database/MySQL.md @@ -0,0 +1,306 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [书籍推荐](#书籍推荐) +- [文字教程推荐](#文字教程推荐) +- [视频教程推荐](#视频教程推荐) +- [常见问题总结](#常见问题总结) + - [什么是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语句执行得很慢的原因有哪些) + + + +## 书籍推荐 + +- 《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 lock:record+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中如何执行的]() + +### MySQL高性能优化规范建议 + +[MySQL高性能优化规范建议]() + +### 一条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) diff --git "a/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" "b/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" new file mode 100644 index 00000000000..f079ad77593 --- /dev/null +++ "b/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" @@ -0,0 +1,441 @@ +> 作者: 听风,原文地址: 。JavaGuide 已获得作者授权。 + + + +- [数据库命令规范](#数据库命令规范) +- [数据库基本设计规范](#数据库基本设计规范) + - [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-对于程序连接数据库账号遵循权限最小原则) + + + +## 数据库命令规范 + +- 所有数据库对象名称必须使用小写字母并用下划线分割 +- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) +- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符 +- 临时库表必须以 tmp_为前缀并以日期为后缀,备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀 +- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低) + +------ + +## 数据库基本设计规范 + +### 1. 所有表必须使用 Innodb 存储引擎 + +没有特殊要求(即 Innodb 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 Innodb 存储引擎(MySQL5.5 之前默认使用 Myisam,5.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 权限 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" b/docs/database/Redis/Redis.md similarity index 80% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" rename to docs/database/Redis/Redis.md index 5732c156825..559e0a5aae3 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" +++ b/docs/database/Redis/Redis.md @@ -1,32 +1,32 @@ - +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + - [redis 简介](#redis-简介) -- [为什么要用 redis /为什么要用缓存](#为什么要用-redis-为什么要用缓存) +- [为什么要用 redis/为什么要用缓存](#为什么要用-redis为什么要用缓存) - [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存) - [redis 和 memcached 的区别](#redis-和-memcached-的区别) - [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析) - - [1. String](#1-string) - - [2.Hash](#2hash) - - [3.List](#3list) - - [4.Set](#4set) - - [5.Sorted Set](#5sorted-set) + - [1.String](#1string) + - [2.Hash](#2hash) + - [3.List](#3list) + - [4.Set](#4set) + - [5.Sorted Set](#5sorted-set) - [redis 设置过期时间](#redis-设置过期时间) -- [redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制(mysql里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?)) -- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制(怎么保证-redis-挂掉之后再重启数据可以进行恢复)) +- [redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制mysql里有2000w数据redis中只存20w的数据如何保证redis中的数据都是热点数据) +- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复) - [redis 事务](#redis-事务) - [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案) - [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题) -- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性?) -- [参考:](#参考:) - - +- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性) + ### redis 简介 -简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以存写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 +简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 -### 为什么要用 redis /为什么要用缓存 +### 为什么要用 redis/为什么要用缓存 主要从“高性能”和“高并发”这两点来看待这个问题。 @@ -72,7 +72,7 @@ ### redis 常见数据结构以及使用场景分析 -#### 1. String +#### 1.String > **常用命令:** set,get,decr,incr,mget 等。 @@ -84,7 +84,7 @@ String数据结构是简单的key-value类型,value其实不仅可以是String #### 2.Hash > **常用命令:** hget,hset,hgetall 等。 -Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: +hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: ``` key=JavaUser293847 @@ -128,7 +128,7 @@ sinterstore key1 key2 key3 将交集存在key1内 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 -**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 +**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。 ### redis 设置过期时间 @@ -147,11 +147,9 @@ Redis中有个设置时间过期的功能,即对存储在 redis 数据库中 - **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! -但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? - -**redis 内存淘汰机制。** +但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致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) @@ -160,19 +158,23 @@ redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大 1. **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 2. **volatile-ttl**:从已设置过期时间的数据集(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)中任意选择数据淘汰 6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! +4.0版本后增加以下两种: + +7. **volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 +8. **allkeys-lfu**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key **备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** -### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复) +### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复) -很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 +很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 -Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 +Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 **快照(snapshotting)持久化(RDB)** @@ -182,14 +184,13 @@ Redis可以通过创建快照来获得存储在内存里面的数据在某个时 ```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命令创建快照。 ``` - **AOF(append-only file)持久化** 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: @@ -203,41 +204,37 @@ appendonly yes 在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: ```conf -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 +appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 -appendfsync no #让操作系统决定何时进行同步 +appendfsync no #让操作系统决定何时进行同步 ``` 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 - **Redis 4.0 对于持久化机制的优化** Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 - - **补充内容:AOF 重写** 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 通过 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 问题 @@ -278,9 +275,7 @@ Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。 - 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 +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 -### 参考: +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -- redis设计与实现(第二版) +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" rename to "docs/database/Redis/Redis\346\214\201\344\271\205\345\214\226.md" diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" similarity index 99% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" rename to "docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" index b1742f2fbf9..86a15ff6faf 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" +++ "b/docs/database/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -28,7 +28,7 @@ end 算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: -1. 得到当前的时间,微妙单位 +1. 得到当前的时间,微秒单位 2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" rename to "docs/database/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" diff --git "a/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" "b/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" new file mode 100644 index 00000000000..acbfda3d7ac --- /dev/null +++ "b/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" @@ -0,0 +1,973 @@ +> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。 +> 作者:格物 + +非常不错的总结,强烈建议保存下来,需要的时候看一看。 + + +- [基本操作](#基本操作) +- [数据库操作](#数据库操作) +- [表的操作](#表的操作) +- [数据操作](#数据操作) +- [字符集编码](#字符集编码) +- [数据类型(列类型)](#数据类型列类型) +- [列属性(列约束)](#列属性列约束) +- [建表规范](#建表规范) +- [SELECT](#select) +- [UNION](#union) +- [子查询](#子查询) +- [连接查询(join)](#连接查询join) +- [TRUNCATE](#truncate) +- [备份与还原](#备份与还原) +- [视图](#视图) +- [事务(transaction)](#事务transaction) +- [锁表](#锁表) +- [触发器](#触发器) +- [SQL编程](#sql编程) +- [存储过程](#存储过程) +- [用户和权限管理](#用户和权限管理) +- [表维护](#表维护) +- [杂项](#杂项) + + + +### 基本操作 + +```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 +清空数据 +删除重建表 +区别: +1,truncate 是删除表再创建,delete 是逐条删除 +2,truncate 重置auto_increment的值。而delete不会 +3,truncate 不知道删除了几条,而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 +``` + diff --git "a/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" "b/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" new file mode 100644 index 00000000000..261a0c0b975 --- /dev/null +++ "b/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" @@ -0,0 +1,149 @@ +本文来自[木木匠](https://github.com/kinglaw1204)投稿。 + + + +- [一 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-更新语句) +- [三 总结](#三-总结) +- [四 参考](#四-参考) + + + +本篇文章会分析下一个 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参考手册: diff --git "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" "b/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" new file mode 100644 index 00000000000..fd066837e01 --- /dev/null +++ "b/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" @@ -0,0 +1,148 @@ +> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [BugSpeak](https://github.com/BugSpeak) 共同完成。 + + +- [事务隔离级别(图文详解)](#事务隔离级别图文详解) + - [什么是事务?](#什么是事务) + - [事务的特性(ACID)](#事务的特性acid) + - [并发事务带来的问题](#并发事务带来的问题) + - [事务隔离级别](#事务隔离级别) + - [实际情况演示](#实际情况演示) + - [脏读(读未提交)](#脏读读未提交) + - [避免脏读(读已提交)](#避免脏读读已提交) + - [不可重复读](#不可重复读) + - [可重复读](#可重复读) + - [防止幻读(可重复读)](#防止幻读可重复读) + - [参考](#参考) + + + +## 事务隔离级别(图文详解) + +### 什么是事务? + +事务是逻辑上的一组操作,要么都执行,要么都不执行。 + +事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账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`:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。 + +#### 脏读(读未提交) + +
+ +
+ +#### 避免脏读(读已提交) + +
+ +
+ +#### 不可重复读 + +还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。 + +
+ +
+ +#### 可重复读 + +
+ +
+ +#### 防止幻读(可重复读) + +
+ +
+ +一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢? + +幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。 + +### 参考 + +- 《MySQL技术内幕:InnoDB存储引擎》 +- +- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/) +- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html) diff --git a/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md b/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md new file mode 100644 index 00000000000..183a1852a90 --- /dev/null +++ b/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md @@ -0,0 +1,294 @@ +作者: rhwayfun,原文地址:https://mp.weixin.qq.com/s/msYty4vjjC0PvrwasRH5Bw ,JavaGuide 已经获得作者授权并对原文进行了重新排版。 + + +- [写在2019年后的蚂蚁、头条、拼多多的面试总结](#写在2019年后的蚂蚁头条拼多多的面试总结) + - [准备过程](#准备过程) + - [蚂蚁金服](#蚂蚁金服) + - [一面](#一面) + - [二面](#二面) + - [三面](#三面) + - [四面](#四面) + - [五面](#五面) + - [小结](#小结) + - [拼多多](#拼多多) + - [面试前](#面试前) + - [一面](#一面-1) + - [二面](#二面-1) + - [三面](#三面-1) + - [小结](#小结-1) + - [字节跳动](#字节跳动) + - [面试前](#面试前-1) + - [一面](#一面-2) + - [二面](#二面-2) + - [小结](#小结-2) + - [总结](#总结) + + + +# 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检查是否有修改,是则重试)重试会有什么问题么?(CAS(Compare 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以及两者的区别,一个乐观锁,一个悲观锁) +- 那我们做一道题吧,数组A,2*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 + +全文完。 diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" new file mode 100644 index 00000000000..9efac14f6d4 --- /dev/null +++ "b/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" @@ -0,0 +1,96 @@ +> 作者:ppxyn。本文来自读者投稿,同时也欢迎各位投稿,**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 + +**目录** + + + +- [前言](#前言) +- [一面\(技术面\)](#一面技术面) +- [二面\(技术面\)](#二面技术面) +- [三面\(技术面\)](#三面技术面) +- [四面\(半个技术面\)](#四面半个技术面) +- [五面\(HR面\)](#五面hr面) +- [总结](#总结) + + + +### 前言 + +在接触 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。 + + + + diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md" new file mode 100644 index 00000000000..2e2df23b941 --- /dev/null +++ "b/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md" @@ -0,0 +1,251 @@ +本文来自 Anonymous 的投稿 ,JavaGuide 对原文进行了重新排版和一点完善。 + + + +- [一面 (37 分钟左右)](#一面-37-分钟左右) +- [二面 (33 分钟左右)](#二面-33-分钟左右) +- [三面 (46 分钟)](#三面-46-分钟) +- [HR 面](#hr-面) + + + +### 一面 (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 多线程的那个包啊。。。 + +**面试官:** 那你都了解里面的哪些东西呢? + +**我:** 哈哈哈!这可是我的强项,从 ConcurrentHashMap,ConcurrentLinkedQueue 说到 CountDownLatch,CyclicBarrier,又说到线程池,分别说了底层实现和项目中的应用。 + +**面试官:** 我觉得差不多了,那我再问个与技术无关的问题哈,虽然这个问题可能不应该我问,就是你是如何考虑你的项目架构的呢? + +**我:** 先用最简单的方式实现它,再去发掘系统的问题和瓶颈,于是查资料改进架构。。。 + +**面试官:** 好,那我给你介绍下我这边的情况吧 + +![chat-end](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3chat-end.jpg) + +**总结:** 一面可能是简历面吧,问的比较简单,我在讲项目中说出了我做项目时的学习历程和思考,赢得了面试官的好感,感觉他应该给我的评价很好。 + +### 二面 (33 分钟左右) + +然而开心了没一会,内推人问我面的怎么样啊?看我流程已经到大大 boss 那了。我一听二面不是主管吗???怎么直接跳了一面。于是瞬间慌了,赶紧(下床)学习准备二面。 + +隔了一天,3.14 的早上 10:56 分,杭州的大大 boss 给我打来了电话,卧槽我当时在上毛概课,万恶的毛概课每节课都点名,我还在最后一排不敢跑出去。于是接起电话来怂怂地说不好意思我在上课,晚上可以面试吗?大大 boss 看来很忙啊,跟我说晚上没时间啊,再说吧! + +于是又隔了一天,3.16 中午我收到了北京的电话,当时心里小失望,我的大大 boss 呢???接起电话来,就是一番狂轰乱炸。。。 + +第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题! + +**面试官:** 我们还是从你的项目开始吧,说说你的秒杀系统。 + +**我:** 一面时的套路。。。我考虑到秒杀地址在开始前不应暴露给用户。。。 + +**面试官:** 等下啊,为什么要这样呢?暴露给用户会怎么样? + +**我:** 用户提前知道秒杀地址就可以写脚本来抢购了,这样不公平 + +**面试官:** 那比如说啊,我现在是个黑客,我在秒杀开始时写好了脚本,运行一万个线程获取秒杀地址,这样是不是也不公平呢? + +**我:** 我考虑到了这方面,于是我自己写了个 LRU 缓存(划重点,这么多好用的缓存我为啥不用偏要自己写?就是为了让面试官上钩问我是怎么写的,这样我就可以逼逼准备好的内容了!),用这个缓存存储请求的 ip 和用户名,一个 ip 和用户名只能同时透过 3 个请求。 + +**面试官:** 那我可不可以创建一个 ip 代理池和很多用户来抢购呢?假设我有很多手机号的账户。 + +**我:** 这就是在为难我胖虎啊,我说这种情况跟真实用户操作太像了。。。我没法区别,不过我觉得可以通过地理位置信息或者机器学习算法来做吧。。。 + +**面试官:** 好的这个问题就到这吧,你接着说 + +**我:** 我把生成订单和减库存两条 sql 语句放在一个事务里,都操作成功了则认为秒杀成功。 + +**面试官:** 等等,你这个订单表和商品库存表是在一个数据库的吧,那如果在不同的数据库中呢? + +**我:** 这面试官好变态啊,我只是个本科生?!?!我觉得应该要用分布式锁来实现吧。。。 + +**面试官:** 有没有更轻量级的做法? + +**我:** 不知道了。后来查资料发现可以用消息队列来实现。使用消息队列主要能带来两个好处:(1) 通过异步处理提高系统性能(削峰、减少响应所需时间);(2) 降低系统耦合性。关于消息队列的更多内容可以查看这篇文章: + +后来发现消息队列作用好大,于是现在在学手写一个消息队列。 + +**面试官:** 好的你接着说项目吧。 + +**我:** 我考虑到了缓存雪崩问题,于是。。。 + +**面试官:** 等等,你有没有考虑到一种情况,假如说你的缓存刚刚失效,大量流量就来查缓存,你的数据库会不会炸? + +**我:** 我不知道数据库会不会炸,反正我快炸了。当时说没考虑这么高的并发量,后来发现也是可以用消息队列来解决,对流量削峰填谷。 + +**面试官:** 好项目聊(怼)完了,我们来说说别的,操作系统了解吧,你能说说 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 地址: + +**面试官:** 好,我们聊聊其他项目,说说你这个 MapReduce 项目?MapReduce 原理了解过吗? + +**我:** 我叭叭地说了一通,面试官好像觉得这个项目太简单了。要不是没项目,我会把我的实验写上吗??? + +**面试官:** 你这个手写 BP 神经网络是干了啥? + +**我:** 这是我选修机器学习课程时的一个作业,我又对它进行了扩展。 + +**面试官:** 你能说说为什么调整权值时要沿着梯度下降的方向? + +**我:** 老大,你太厉害了,怎么什么都懂。我压根没准备这个项目。。。没想到会问,做过去好几个月了,加上当时一紧张就忘了,后来想起来大概是....。 + +**面试官:** 好我们问问基础知识吧,说说什么叫 xisuo? + +**我:**???xisuo,您说什么,不好意思我没听清。(这面试官有点口音。。。)就是 xisuo 啊!xisuo 你不知道吗?。。。尴尬了十几秒后我终于意识到,他在说死锁!!! + +**面试官:** 假如 A 账户给 B 账户转钱,会发生 xisuo 吗?能具体说说吗? + +**我:** 当时答的不好,后来发现面试官又是想问分布式,具体答案参考这个: + +**面试官:** 为什么不考研? + +**我:** 不喜欢学术氛围,巴拉巴拉。 + +**面试官:** 你有什么问题吗? + +**我:** 我还有下一面吗。。。面试官说让我等,一周内答复。 + +------ + +等了十天,一度以为我凉了,内推人说我流程到 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) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" similarity index 98% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" rename to "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" index 4ca58dbfff6..595a73e28b0 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" +++ "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" @@ -139,7 +139,7 @@ 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/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" similarity index 95% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" rename to "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" index 426498cb2d9..2839aae916c 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" +++ "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" @@ -168,10 +168,10 @@ Java语言通过字节码的方式,在一定程度上解决了传统解释型 ### 接口和抽象类的区别是什么? 1. 接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法 -2. 接口中的实例变量默认是final类型的,而抽象类中则不一定 -3. 一个类可以实现多个接口,但最多只能实现一个抽象类 -4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 -5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 +2. 接口中的实例变量默认是final类型的,而抽象类中则不一定 +3. 一个类可以实现多个接口,但最多只能实现一个抽象类 +4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 +5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 注意:Java8 后接口可以有默认实现( default )。 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" similarity index 69% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" rename to "docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" index 3cb02d73d5b..82d0a02b0b1 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" +++ "b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" @@ -16,39 +16,40 @@ ## 2. 线程有哪些基本状态?这些状态是如何定义的? -1. **新建(new)**:新创建了一个线程对象。 -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()方法,则该线程结束生命周期。死亡的线程不可再次复生。 +Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。 -![](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 -2. 起床收拾好了,随时可以坐地铁出发:Runnable -3. 等地铁来:Waiting -4. 地铁来了,但要排队上地铁:I/O阻塞 -5. 上了地铁,发现暂时没座位:synchronized阻塞 -6. 地铁上找到座位:Running -7. 到达目的地:Dead +![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(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。 + ## 3. 何为多线程? 多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。 -## 4. 为什么多线程是必要的? +## 4. 为什么要使用多线程? + +先从总体上来说: + +- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。 +- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。 -1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。 -2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。 -3. 程序的运行速度可能加快。 +再深入到计算机底层来探讨: +- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。 +- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。 ## 5 使用多线程常见的三种方式 ### ①继承Thread类 @@ -186,7 +187,7 @@ Thread类中包含的成员变量代表了线程的某些优先级。如**Thread 这是另一个非常经典的java多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来! new一个Thread,线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 -start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个mian线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 +start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 **总结: 调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里执行。** diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md new file mode 100644 index 00000000000..835b6a54fa0 --- /dev/null +++ b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md @@ -0,0 +1,89 @@ +昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章:[整理了一些有助于你拿Offer的文章]() 。今天分享一下最近逛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 地址: +- 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 地址: +- 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解题之路。) \ No newline at end of file diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/JavaProgrammerNeedKnow.md" b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md similarity index 81% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/JavaProgrammerNeedKnow.md" rename to docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md index ef111f4cf15..d515693722e 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/JavaProgrammerNeedKnow.md" +++ b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md @@ -1,4 +1,4 @@ -  身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学那些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指名一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。 +  身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学哪些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。 ### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗? @@ -6,7 +6,7 @@   首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。 -  企业HR肯定是更喜欢高学历的人,毕竟985,211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢? +  企业HR肯定是更喜欢高学历的人,毕竟985、211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢?      双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。** @@ -17,7 +17,7 @@   我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。 -  建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据机构与算法!一定学好数据机构与算法!一定学好数据机构与算法!”,重要的东西说3遍。 +  建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据结构与算法!一定学好数据结构与算法!一定学好数据结构与算法!”,重要的东西说3遍。 @@ -31,12 +31,12 @@ 下面是我总结的一些准备面试的Tips以及面试必备的注意事项: -1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账); +1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账); 2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。) 3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。 -4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) 5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 -6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 +6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图);②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md similarity index 83% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" rename to docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md index c99ca1c156e..1ae36a35734 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/interviewPrepare.md" +++ b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md @@ -1,8 +1,22 @@ -这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。 - 不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。 -### 1 如何获取大厂面试机会? + + +- [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-面试之后复盘) + + + +## 1 如何获取大厂面试机会? **在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。** @@ -24,7 +38,7 @@ 除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。 -### 2 面试前的准备 +## 2 面试前的准备 ### 2.1 准备自己的自我介绍 @@ -57,11 +71,11 @@ 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图) 2. 在这个项目中你负责了什么、做了什么、担任了什么角色 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 -4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 +4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 ### 2.7 提前准备技术面试 -搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) ### 2.7 面试之前做好定向复习 @@ -69,6 +83,6 @@ 举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 -# 3 面试之后复盘 +## 3 面试之后复盘 -如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! \ No newline at end of file +如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" "b/docs/essential-content-for-interview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" new file mode 100644 index 00000000000..7a55d539d11 --- /dev/null +++ "b/docs/essential-content-for-interview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" @@ -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) \ No newline at end of file diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" similarity index 56% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" rename to "docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" index d07fa52a7e7..f0627de96e2 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" +++ "b/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" @@ -1,21 +1,43 @@ -# 程序员的简历就该这样写 + -### 1 前言 -一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。 +- [程序员简历就该这样写](#程序员简历就该这样写) + - [为什么说简历很重要?](#为什么说简历很重要) + - [先从面试前来说](#先从面试前来说) + - [再从面试中来说](#再从面试中来说) + - [下面这几点你必须知道](#下面这几点你必须知道) + - [必须了解的两大法则](#必须了解的两大法则) + - [STAR法则(Situation Task Action Result)](#star法则situation-task-action-result) + - [FAB 法则(Feature Advantage Benefit)](#fab-法则feature-advantage-benefit) + - [项目经历怎么写?](#项目经历怎么写) + - [专业技能该怎么写?](#专业技能该怎么写) + - [排版注意事项](#排版注意事项) + - [其他的一些小tips](#其他的一些小tips) + - [推荐的工具/网站](#推荐的工具网站) -### 2 为什么说简历很重要? + -#### 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 内存淘汰机制等等。 @@ -23,17 +45,16 @@ 面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。 -### 3 下面这几点你必须知道 +## 下面这几点你必须知道 -1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。 +1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。 2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作** 3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;** -4. **将自己的项目经历完美的展示出来非常重要。** - -### 4 必须了解的两大法则 +4. **将自己的项目经历完美的展示出来非常重要。** +## 必须了解的两大法则 -**①STAR法则(Situation Task Action Result):** +### STAR法则(Situation Task Action Result) - **Situation:** 事情是在什么情况下发生; - **Task::** 你是如何明确你的任务的; @@ -42,14 +63,7 @@ 简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。 -下面这段内容摘自百度百科,我觉得写的非常不错: - -> STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。 -由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容) -在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。 -但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗? - -**②FAB 法则(Feature Advantage Benefit):** +### FAB 法则(Feature Advantage Benefit) - **Feature:** 是什么; - **Advantage:** 比别人好在哪些地方; @@ -57,7 +71,7 @@ 简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。 -### 5 项目经历怎么写? +## 项目经历怎么写? 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: @@ -66,7 +80,8 @@ 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 -### 6 专业技能该怎么写? +## 专业技能该怎么写? + 先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善): - 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握 @@ -79,28 +94,28 @@ - Zookeeper: 掌握 - 常见消息队列: 掌握 - Linux:掌握 -- MySQL常见优化手段:掌握 +- MySQL常见优化手段:掌握 - Spring Boot +Spring Cloud +Docker:了解 - Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解 - Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉 -### 7 开源程序员Markdown格式简历模板分享 +## 排版注意事项 -分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 -Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) +1. 尽量简洁,不要太花里胡哨; +2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysql,Java不要写成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) +1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 +2. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 +3. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 +4. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 +5. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 +6. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 +7. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 -### 8 其他的一些小tips +## 推荐的工具/网站 -1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 -2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 -3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 -4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 -5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 -6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 -7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 -8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 +- 冷熊简历(MarkDown在线简历工具,可在线预览、编辑和生成PDF): +- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" similarity index 92% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" rename to "docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" index 422d9402f12..46996cdb3e9 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -78,7 +78,7 @@ ## 1. `System.out.println(3|9)`输出什么? -正确答案:11. +正确答案:11。 **考察知识点:&和&&;|和||** @@ -110,10 +110,10 @@ 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:低. +1. **从地址栏显示来说**:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。 +2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享request里面的数据。redirect:不能共享数据。 +3. **从运用地方来说**:forward:一般用于用户登陆的时候,根据角色转发到相应的模块。redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。 +4. **从效率来说**:forward:高。redirect:低。 ## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议 @@ -159,7 +159,7 @@ request.getRequestDispatcher("login_success.jsp").forward(request, response); 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常 -第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常 +第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常 所以三次握手就能确认双发收发功能都正常,缺一不可。 @@ -238,7 +238,7 @@ HTTP响应报文主要由状态行、响应头部、响应正文3部分组成 1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。 2. 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描 -4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 chema_unused_indexes 视图来查询哪些索引从未被使用 +4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用 5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能 **Mysql索引主要使用的哪两种数据结构?** @@ -382,7 +382,7 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量: ![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)->将得到视图对象返回给用户 +客户端发送请求-> 前端控制器 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) @@ -390,7 +390,7 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量: 过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。 -**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。 +**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。 **AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。 @@ -421,7 +421,7 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量: > **先来简单说一下分布式服务:** -目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) +目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) > **再来谈我们的分布式消息队列:** @@ -464,7 +464,7 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量: ### 1.4 关于消息队列其他一些常见的问题展望 -1. 引入消息队列之后如何保证高可用性 +1. 引入消息队列之后如何保证高可用性? 2. 如何保证消息不被重复消费呢? 3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)? 4. 我该怎么保证从消息队列里拿到的数据按顺序执行? @@ -478,7 +478,7 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量: ### 2.1 两者的对比 1. **count运算上的区别:** 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存 -2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 +2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是 InnoDB 提供事务支持,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 3. **是否支持外键:** MyISAM不支持,而InnoDB支持。 @@ -495,8 +495,8 @@ MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数 - **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) `方法)。 +- **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接口** @@ -508,7 +508,7 @@ public interface RandomAccess { 查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 -在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 +在 binarySearch() 方法中,它要判断传入的 list 是否RamdomAccess的实例,如果是,调用 indexedBinarySearch() 方法,如果不是,那么调用 iteratorBinarySearch() 方法 ```java public static @@ -520,7 +520,7 @@ public interface RandomAccess { } ``` -ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的! +ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1) ,所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n) ,所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的! @@ -614,9 +614,9 @@ TreeMap、TreeSet以及JDK1.8之后的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。 +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 没有这样的机制。 @@ -688,7 +688,7 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 #### 1.2.3 hashCode()与equals()的相关规定 1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个对象分别调用equals方法都返回true +2. 两个对象相等,对两个对象分别调用equals方法都返回true 3. 两个对象有相同的hashcode值,它们也不一定是相等的 4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** 5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) @@ -737,7 +737,7 @@ public class test1 { **说明:** -- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 +- 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 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视! @@ -749,13 +749,13 @@ public class test1 { 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,竞争会越来越激烈效率越低。 +- **实现线程安全的方式(重要):** ① **在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: +Hashtable: ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg) JDK1.7的ConcurrentHashMap: @@ -772,7 +772,7 @@ Node: 链表节点): **ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 -Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 +Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 ```java static class Segment extends ReentrantLock implements Serializable { @@ -787,29 +787,29 @@ ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证 synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 -## 3 谈谈 synchronized 和 ReenTrantLock 的区别 +## 3 谈谈 synchronized 和 ReentrantLock 的区别 **① 两者都是可重入锁** 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增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 增加了一些高级功能** -相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** +相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** -- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 +- **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是一个不错的选择。 +如果你想使用上述功能,那么选择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操作。 +在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 线程池了解吗? @@ -837,9 +837,9 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团 #### 各种线程池的适用场景介绍 - **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器; -- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。 +- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景; - **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器; -- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景, +- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景; - **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。 ### 4.3 创建的线程池的方式 @@ -903,7 +903,7 @@ Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(I 谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了 -- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。 +- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN 了。 - **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。 通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/): @@ -920,7 +920,7 @@ Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(I Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。 -负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。 +负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。 #### 动静分离 @@ -945,6 +945,6 @@ Nginx 有以下5个优点: > 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。 - Nginx 二进制可执行文件:由各模块源码编译出一个文件 -- Nginx.conf 配置文件:控制Nginx 行为 +- nginx.conf 配置文件:控制Nginx 行为 - acess.log 访问日志: 记录每一条HTTP请求信息 -- error.log 错误日志:定位问题 +- error.log 错误日志:定位问题 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" "b/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" similarity index 100% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" rename to "docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\256\200\345\216\206\346\250\241\346\235\277.md" "b/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md" similarity index 100% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\347\256\200\345\216\206\346\250\241\346\235\277.md" rename to "docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md" diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" "b/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" similarity index 84% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" rename to "docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" index b6f40fbc31b..00aaecd8c5b 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" +++ "b/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" @@ -1,3 +1,22 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [何谓悲观锁与乐观锁](#何谓悲观锁与乐观锁) + - [悲观锁](#悲观锁) + - [乐观锁](#乐观锁) + - [两种锁的使用场景](#两种锁的使用场景) +- [乐观锁常见的两种实现方式](#乐观锁常见的两种实现方式) + - [1. 版本号机制](#1-版本号机制) + - [2. CAS算法](#2-cas算法) +- [乐观锁的缺点](#乐观锁的缺点) + - [1 ABA 问题](#1-aba-问题) + - [2 循环时间长开销大](#2-循环时间长开销大) + - [3 只能保证一个共享变量的原子操作](#3-只能保证一个共享变量的原子操作) +- [CAS与synchronized的使用情景](#cas与synchronized的使用情景) + + + ### 何谓悲观锁与乐观锁 > 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 @@ -64,8 +83,6 @@ JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中 CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。 - - ### CAS与synchronized的使用情景 > **简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)** @@ -73,10 +90,17 @@ CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。 - 补充: 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) diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" b/docs/github-trending/2018-12.md similarity index 78% rename from "\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" rename to docs/github-trending/2018-12.md index fc433c64f8e..3637e93ea0c 100644 --- "a/\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" +++ b/docs/github-trending/2018-12.md @@ -6,60 +6,36 @@ - **star**: 18.2k - **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 -![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/96151465.jpg) - -概览: - - ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) - ### 2. mall - **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall) - **star**: 3.3k - **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 -![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/11382760.jpg) - -概览: - -![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/99819963.jpg) - ### 3. advanced-java - **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) - **star**: 3.3k - **介绍**: 互联网 Java 工程师进阶知识完全扫盲 -![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/95209476.jpg) - -概览: - -![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/18005792.jpg) - ### 4. matrix - **Github地址**:[https://github.com/Tencent/matrix](https://github.com/Tencent/matrix) - **star**: 2.5k - **介绍**: Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。 -![matrix](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/8991895.jpg) - ### 5. miaosha - **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha) - **star**: 2.4k - **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。 -![miaosha](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/79382302.jpg) - ### 6. arthas - **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas) - **star**: 8.2k - **介绍**: Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。 -![arthas](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/73394671.jpg) - ### 7 spring-boot - **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot) @@ -70,23 +46,18 @@ > 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项目只需要少量的配置即可) -![spring-boot](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/29649807.jpg) - ### 8. tutorials - **Github地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials) - **star**: 10k - **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然,它们的重点是Spring Framework - Spring,Spring Boot和Spring Securiyt。 除了Spring之外,还有以下技术:核心Java,Jackson,HttpClient,Guava。 -![tutorials](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/94425339.jpg) - ### 9. qmq - **Github地址**:[https://github.com/qunarcorp/qmq](https://github.com/qunarcorp/qmq) - **star**: 1.1k - **介绍**: QMQ是去哪儿网内部广泛使用的消息中间件,自2012年诞生以来在去哪儿网所有业务场景中广泛的应用,包括跟交易息息相关的订单场景; 也包括报价搜索等高吞吐量场景。 -![arthas](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/73394671.jpg) ### 10. symphony @@ -94,20 +65,14 @@ - **star**: 9k - **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。 -![symphony](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/11577890.jpg) - ### 11. incubator-dubbo - **Github地址**:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) - **star**: 23.6k - **介绍**: 阿里开源的一个基于Java的高性能开源RPC框架。 -![incubator-dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/53068060.jpg) - ### 12. apollo - **Github地址**:[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo) - **star**: 10k - **介绍**: Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 - -![apollo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/89386993.jpg) \ No newline at end of file diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" b/docs/github-trending/2019-1.md similarity index 68% rename from "\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" rename to docs/github-trending/2019-1.md index eecc4e2860e..aa1de92f623 100644 --- "a/\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" +++ b/docs/github-trending/2019-1.md @@ -4,31 +4,17 @@ - **star**: 22.8k - **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 -![JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/JavaGuide.png) - -**概览:** - - ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) - ### 2. advanced-java - **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) - **star**: 7.9k - **介绍**: 互联网 Java 工程师进阶知识完全扫盲 -![advanced-java](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/advanced-java.png) - -**概览:** - -![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/18005792.jpg) - ### 3. fescar - **Github地址**:[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar) - **star**: 4.6k -- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。)关于 fescar 的更详细介绍可以查看:[Github 上日获 800多 star 的阿里微服务架构分布式事务解决方案 FESCAR开源啦](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484888&idx=2&sn=ff9fe077c95959ec777c866a425bddbe&chksm=fd9853b9caefdaaf52a1b7caecc697938c2c1c5f6916527d1309ef01aba70c6546bdba6a9657&token=96044853&lang=zh_CN#rd) - -![fescar](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/fescar.png) +- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。 ### 4. mall @@ -36,50 +22,30 @@ - **star**: 5.6 k - **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 -![mall](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/mall.png) - -**概览:** - -![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/99819963.jpg) - ### 5. miaosha - **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha) - **star**: 4.4k - **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。 -![miaosha](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/miaosha.png) - ### 6. flink - **Github地址**:[https://github.com/apache/flink](https://github.com/apache/flink) - **star**: 7.1 k - **介绍**: Apache Flink是一个开源流处理框架,具有强大的流和批处理功能。 -![flink](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/flink.png) - -关于Flink 更加详细的介绍可以查看这篇文章:https://www.cnblogs.com/feiyudemeng/p/8998772.html - ### 7. cim - **Github地址**:[https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim) - **star**: 1.8 k - **介绍**: cim(cross IM) 适用于开发者的即时通讯系统。 -![cim](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/cim.png) - -**系统架构:** - -![cim系统架构](https://camo.githubusercontent.com/16f644ac7e2ab8cf8b8784408b1c70baf15634f4/68747470733a2f2f7773312e73696e61696d672e636e2f6c617267652f303036744e6252776c793166796c646769697a68756a3331356f3072346e306b2e6a7067) - ### 8. symphony - **Github地址**:[https://github.com/b3log/symphony](https://github.com/b3log/symphony) - **star**: 10k - **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。 -![symphony](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/symphony.png) - ### 9. spring-boot - **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot) @@ -90,16 +56,12 @@ > 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项目只需要少量的配置即可) -![spring-boot](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/spring-boot.png) - ### 10. arthas - **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas) - **star**: 9.5k - **介绍**: Arthas 是Alibaba开源的Java诊断工具。 -![arthas](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/arthas.png) - **概览:** 当你遇到以下类似问题而束手无策时,`Arthas`可以帮助你解决: @@ -111,4 +73,4 @@ 4. 是否有一个全局视角来查看系统的运行状况? 5. 有什么办法可以监控到JVM的实时运行状态? -`Arthas`支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 \ No newline at end of file +`Arthas`支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2019-2.md" b/docs/github-trending/2019-2.md similarity index 100% rename from "\351\227\262\350\260\210/JavaGithubTrending/2019-2.md" rename to docs/github-trending/2019-2.md diff --git a/docs/github-trending/2019-3.md b/docs/github-trending/2019-3.md new file mode 100644 index 00000000000..eaed4a5d483 --- /dev/null +++ b/docs/github-trending/2019-3.md @@ -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 地址**: +- **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 - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。 + diff --git a/docs/github-trending/2019-4.md b/docs/github-trending/2019-4.md new file mode 100644 index 00000000000..713a76da642 --- /dev/null +++ b/docs/github-trending/2019-4.md @@ -0,0 +1,98 @@ +以下涉及到的数据统计与 2019 年 5 月 1 日 12 点,数据来源: 。 + +下面的内容从 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 地址**: +- **Star**: 59.2k (4,012 stars this month) +- **介绍**: 技术面试必备基础知识。 + +### 4. ghidra + +- **Github 地址**: +- **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 地址**: +- **star**: 2.1 k (2,086 stars this month) +- **介绍**: 记录各种学习笔记(算法、Java、数据库、并发......)。 + +### 7.DoraemonKit + +- **Github地址**: +- **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 地址**: +- **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地址**: +- **star**: 4.5 k (1,031 stars this month) +- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。 \ No newline at end of file diff --git a/docs/github-trending/2019-5.md b/docs/github-trending/2019-5.md new file mode 100644 index 00000000000..1ac28b22cd7 --- /dev/null +++ b/docs/github-trending/2019-5.md @@ -0,0 +1,125 @@ +以下涉及到的数据统计与 2019 年 6 月 1 日 18 点,数据来源: 。下面推荐的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,建议收藏+在看! + +### 1.LeetCodeAnimation + +- **Github 地址**: +- **Star**: 29.0k (11,492 stars this month) +- **介绍**: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。 + +### 2.CS-Notes + +- **Github 地址**: +- **Star**: 64.4k (5513 stars this month) +- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。 + +### 3.JavaGuide + +- **Github 地址**: +- **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 地址**: +- **Star**:14.3k (1,334 stars this month) +- **介绍**: All Algorithms implemented in Java。 + +### 8.server + +- **Github 地址**: +- **star**: 2.2 k (1,275 stars this month) +- **介绍**: 全开源即时通讯(IM)系统。 + +### 9.litemall + +- **Github 地址**: +- **Star**: 7.1k (1,114 stars this month) +- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。 + +### 10.Linkage-RecyclerView + +- **Github 地址**: +- **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 地址** : +- **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 地址** : +- **Star**: 41.5k (955 stars this month) +- **介绍**: 开源,分布式,RESTful 搜索引擎。 + +### 14.apollo + +- **Github 地址** : +- **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地址**: +- **star**: 26.9 k (769 stars this month) +- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。 + +### 17.DoraemonKit + +- **Github地址**: +- **Star**: 8.5k (909 stars this month) +- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。 + +### 18.halo + +- **Github地址**: +- **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地址**: +- **star**: 5,3 k (812 stars this month) +- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。 \ No newline at end of file diff --git a/docs/github-trending/2019-6.md b/docs/github-trending/2019-6.md new file mode 100644 index 00000000000..2a395e160d6 --- /dev/null +++ b/docs/github-trending/2019-6.md @@ -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地址**: +- **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.x,Ant Design&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任何代码,绝对是全栈开发福音!! 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.3,Shiro1.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 地址**: +- **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 - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。 + +### 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的设备信息。 \ No newline at end of file diff --git a/docs/github-trending/JavaGithubTrending.md b/docs/github-trending/JavaGithubTrending.md new file mode 100644 index 00000000000..91d544ed37e --- /dev/null +++ b/docs/github-trending/JavaGithubTrending.md @@ -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) + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000000..3d8c75e550b --- /dev/null +++ b/docs/index.html @@ -0,0 +1,45 @@ + + + + + JavaGuide + + + + + + + +
+ + + + + + + + + diff --git "a/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" b/docs/java/BIO-NIO-AIO.md similarity index 99% rename from "Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" rename to docs/java/BIO-NIO-AIO.md index c5ec6dddd04..b1e101d0539 100644 --- "a/Java\347\233\270\345\205\263/BIO,NIO,AIO summary.md" +++ b/docs/java/BIO-NIO-AIO.md @@ -42,7 +42,7 @@ - **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 - **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 -举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 +举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 ## 1. BIO (Blocking I/O) @@ -73,7 +73,7 @@ BIO通信(一请求一应答)模型图如下(图源网络,原出处不明) 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 -伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。 +伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。 ### 1.3 代码示例 diff --git a/docs/java/Basis/Arrays,CollectionsCommonMethods.md b/docs/java/Basis/Arrays,CollectionsCommonMethods.md new file mode 100644 index 00000000000..0710de44a95 --- /dev/null +++ b/docs/java/Basis/Arrays,CollectionsCommonMethods.md @@ -0,0 +1,383 @@ + + +- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法) + - [Collections](#collections) + - [排序操作](#排序操作) + - [查找,替换操作](#查找替换操作) + - [同步控制](#同步控制) + - [Arrays类的常见操作](#arrays类的常见操作) + - [排序 : `sort()`](#排序--sort) + - [查找 : `binarySearch()`](#查找--binarysearch) + - [比较: `equals()`](#比较-equals) + - [填充 : `fill()`](#填充--fill) + - [转列表 `asList()`](#转列表-aslist) + - [转字符串 `toString()`](#转字符串-tostring) + - [复制 `copyOf()`](#复制-copyof) + + +# 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 arrayList = new ArrayList(); + 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() { + + @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 arrayList = new ArrayList(); + 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 arrayList2 = new ArrayList(); + 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()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。 + +我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。 + +**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。** + +方法如下: + +```java +synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。 +synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。 +synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。 +synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。 +``` + +### Collections还可以设置不可变集合,提供了如下三类方法: + +```java +emptyXxx(): 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。 +singletonXxx(): 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。 +unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。 +上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。 +``` + +**示例代码:** + +```java + ArrayList arrayList = new ArrayList(); + 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 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 list = Collections.emptyList(); + System.out.println(list);//[] + Set objects = Collections.emptySet(); + System.out.println(objects);//[] + Map objectObjectMap = Collections.emptyMap(); + System.out.println(objectObjectMap);//{} + + //Collections.singletonXXX(); + List> arrayLists = Collections.singletonList(arrayList); + System.out.println(arrayLists);//[[-1, 3, 3, -5, 7, 4, -9, -7]] + //创建一个只有一个元素,且不可改变的Set对象 + Set> singleton = Collections.singleton(arrayList); + System.out.println(singleton);//[[-1, 3, 3, -5, 7, 4, -9, -7]] + Map nihao = Collections.singletonMap("1", "nihao"); + System.out.println(nihao);//{1=nihao} + + //unmodifiableXXX();创建普通XXX对象对应的不可变版本 + List integers = Collections.unmodifiableList(arrayList); + System.out.println(integers);//[-1, 3, 3, -5, 7, 4, -9, -7] + Set integers2 = Collections.unmodifiableSet(integers1); + System.out.println(integers2);//[1, 2, 3] + Map 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 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(); +``` diff --git "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" "b/docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" similarity index 98% rename from "Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" rename to "docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" index 2c8a917f710..e9008064af7 100644 --- "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" +++ "b/docs/java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" @@ -92,15 +92,13 @@ public class Sub extends Super { **使用 this 和 super 要注意的问题:** -- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 +- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 - this、super不能用在static方法中。 **简单解释一下:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 - - ## 参考 - https://www.codejava.net/java-core/the-java-language/java-keywords diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" similarity index 78% rename from "Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" rename to "docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index ced017ab47b..fc9fac22c0b 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,3 +1,5 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + - [Servlet总结](#servlet总结) @@ -26,7 +28,7 @@ ## 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的区别? @@ -55,11 +57,11 @@ ## Servlet接口中有哪些方法及Servlet生命周期探秘 Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期相关**: -- **void init(ServletConfig config) throws ServletException** -- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException** -- **void destory()** -- java.lang.String getServletInfo() -- ServletConfig getServletConfig() +- `void init(ServletConfig config) throws ServletException` +- `void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException` +- `void destory()` +- `java.lang.String getServletInfo()` +- `ServletConfig getServletConfig()` **生命周期:** **Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。 @@ -93,7 +95,7 @@ Form标签里的method的属性为get时调用doGet(),为post时调用doPost() **转发是服务器行为,重定向是客户端行为。** -**转发(Forword)** +**转发(Forward)** 通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。 ```java 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的一个子类)。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。 工程JspLoginDemo下有一个名为login.jsp的Jsp文件,把工程第一次部署到服务器上后访问这个Jsp文件,我们发现这个目录下多了下图这两个东东。 .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。 开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP,而不需要重启Tomcat。这种自动检测功能是默认开启的,检测改动会消耗少量的时间,在部署Web应用的时候可以在web.xml中将它关掉。 - - 参考:《javaweb整合开发王者归来》P97 ## JSP有哪些内置对象、作用分别是什么 @@ -195,31 +195,31 @@ JSP有9个内置对象: ## 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 传过来的值。 -另外,可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。 -setAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。 +另外,可以用 `setAttribute()`,`getAttribute()` 发送接收对象.而 `getParameter()` 显然只能传字符串。 +`setAttribute()` 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样`getAttribute()`就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。`getParameter()`只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。 **总结:** -getParameter 返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等) +`getParameter()`返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等) -getAttribute 返回的是Object,需进行转换,可用setAttribute 设置成任意对象,使用很灵活,可随时用 +`getAttribute()`返回的是Object,需进行转换,可用`setAttribute()`设置成任意对象,使用很灵活,可随时用 ## include指令include的行为的区别 **include指令:** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分,会被同时编译执行。 语法格式如下: <%@ include file="文件相对 url 地址" %> -i**nclude动作:** 动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下: +i**nclude动作:** ``动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下: ## JSP九大内置对象,七大动作,三大指令 @@ -232,11 +232,9 @@ JSP中的四种作用域包括page、request、session和application,具体来 - **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 - **application**代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。 - - ## 如何实现JSP或Servlet的单线程模式 对于JSP页面,可以通过page指令进行设置。 -<%@page isThreadSafe=”false”%> +`<%@page isThreadSafe=”false”%>` 对于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容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。 ## Cookie和Session的的区别 -1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 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 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。 + + **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。 + +Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。 + +Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。 + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 -参考: +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -https://www.zhihu.com/question/19786827/answer/28752144 +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 -《javaweb整合开发王者归来》P158 Cookie和Session的比较 +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" "b/docs/java/Java IO\344\270\216NIO.md" similarity index 97% rename from "Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" rename to "docs/java/Java IO\344\270\216NIO.md" index 905df527c40..74bd850e696 100644 --- "a/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" +++ "b/docs/java/Java IO\344\270\216NIO.md" @@ -27,12 +27,12 @@ **(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)按操作对象分类结构图** -![按操作对象分类结构图](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) 1. **IO流的分类:** @@ -92,7 +92,7 @@ - 写入数据到缓冲区(Writing Data to a Buffer) **写数据到Buffer有两种方法:** - + 1.从Channel中写数据到Buffer ```java int bytesRead = inChannel.read(buf); //read into buffer. @@ -103,7 +103,7 @@ ``` 4. **Buffer常用方法测试** - + 说实话,NIO编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的Buffer方法的作用。 diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" similarity index 59% rename from "Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" rename to "docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 8b438eb18f8..6ec548e5eb4 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,75 +1,70 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 - - + - [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - - [面向过程](#面向过程) - - [面向对象](#面向对象) -- [2. Java 语言有哪些特点](#2-java-语言有哪些特点) + - [面向过程](#面向过程) + - [面向对象](#面向对象) +- [2. Java 语言有哪些特点?](#2-java-语言有哪些特点) - [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答) - - [JVM](#jvm) - - [JDK 和 JRE](#jdk-和-jre) + - [JVM](#jvm) + - [JDK 和 JRE](#jdk-和-jre) - [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比) -- [5. Java和C++的区别](#5-java和c的区别) -- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) -- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) -- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) -- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) +- [5. Java和C++的区别?](#5-java和c的区别) +- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) +- [7. Java 应用程序与小程序之间有那些差别?](#7-java-应用程序与小程序之间有那些差别) +- [8. 字符型常量和字符串常量的区别?](#8-字符型常量和字符串常量的区别) +- [9. 构造器 Constructor 是否可被 override?](#9-构造器-constructor-是否可被-override) - [10. 重载和重写的区别](#10-重载和重写的区别) - [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-自动装箱与拆箱) -- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) +- [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的) - [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) -- [16. import java和javax有什么区别](#16-import-java和javax有什么区别) -- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) -- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) +- [16. import java和javax有什么区别?](#16-import-java和javax有什么区别) +- [17. 接口和抽象类的区别是什么?](#17-接口和抽象类的区别是什么) +- [18. 成员变量与局部变量的区别有那些?](#18-成员变量与局部变量的区别有那些) - [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同) - [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么) -- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) -- [22. 构造方法有哪些特性](#22-构造方法有哪些特性) +- [21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) +- [22. 构造方法有哪些特性?](#22-构造方法有哪些特性) - [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) -- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) -- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) -- [26. == 与 equals\(重要\)](#26--与-equals重要) -- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要) - - [hashCode()介绍](#hashcode()介绍) - - [为什么要有 hashCode](#为什么要有-hashcode) - - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) -- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) -- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么) +- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) +- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) +- [26. == 与 equals(重要)](#26--与-equals重要) +- [27. hashCode 与 equals (重要)](#27-hashcode-与-equals-重要) + - [hashCode()介绍](#hashcode介绍) + - [为什么要有 hashCode](#为什么要有-hashcode) + - [hashCode()与equals()的相关规定](#hashcode与equals的相关规定) +- [28. 为什么Java中只有值传递?](#28-为什么java中只有值传递) +- [29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程程序进程的基本概念以及他们之间关系是什么) - [30. 线程有哪些基本状态?](#30-线程有哪些基本状态) - [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) - [32 Java 中的异常处理](#32-java-中的异常处理) - - [Java异常类层次结构图](#java异常类层次结构图) - - [Throwable类常用方法](#throwable类常用方法) - - [异常处理总结](#异常处理总结) -- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办) + - [Java异常类层次结构图](#java异常类层次结构图) + - [Throwable类常用方法](#throwable类常用方法) + - [异常处理总结](#异常处理总结) +- [33 Java序列化中如果有些字段不想进行序列化,怎么办?](#33-java序列化中如果有些字段不想进行序列化怎么办) - [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-工具类常见方法总结) - [参考](#参考) +- [公众号](#公众号) - - - + ## 1. 面向对象和面向过程的区别 -### 面向过程 - -**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 - -**缺点:** 没有面向对象易维护、易复用、易扩展 - -### 面向对象 +- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。** +- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。 -**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 - -**缺点:** 性能比面向过程低 - -## 2. Java 语言有哪些特点 +## 2. Java 语言有哪些特点? 1. 简单易学; 2. 面向对象(封装,继承,多态); @@ -88,17 +83,19 @@ Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同 **什么是字节码?采用字节码的好处是什么?** -> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 +> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。 **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) -我们需要格外注意的是 .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 编译器的。 -总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 +**总结:** + +Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 ### JDK 和 JRE @@ -110,7 +107,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有 ## 4. Oracle JDK 和 OpenJDK 的对比 -可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面通过我通过我收集到一些资料对你解答这个被很多人忽视的问题。 +可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。 对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案: @@ -118,7 +115,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有 > > 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。 -总结: +**总结:** 1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次; 2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的; @@ -127,7 +124,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有 5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本; 6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 -## 5. Java和C++的区别 +## 5. Java和C++的区别? 我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来! @@ -137,32 +134,31 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有 - Java 有自动内存管理机制,不需要程序员手动释放无用内存 -## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同 +## 6. 什么是 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. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 -2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) -3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) +1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符 +2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置) +3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) > java编程思想第四版:2.2.2节 ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) -## 9. 构造器 Constructor 是否可被 override +## 9. 构造器 Constructor 是否可被 override? 在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 ## 10. 重载和重写的区别 -**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    - -**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 +- **重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    +- **重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 ## 11. Java 面向对象编程三大特性: 封装 继承 多态 @@ -176,7 +172,7 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有 **关于继承如下 3 点请记住:** -1. 子类拥有父类非 private 的属性和方法。 +1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。 2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 @@ -186,12 +182,11 @@ JRE 是 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 实现的,大家可以自行查阅源码。 @@ -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% 左右的性能提升,但却要冒多线程不安全的风险。 **对于三者使用的总结:** -1. 操作少量的数据 = String -2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder -3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer + +1. 操作少量的数据: 适用String +2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder +3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer ## 13. 自动装箱与拆箱 -**装箱**:将基本类型用它们对应的引用类型包装起来; -**拆箱**:将包装类型转换为基本数据类型; +- **装箱**:将基本类型用它们对应的引用类型包装起来; +- **拆箱**:将包装类型转换为基本数据类型; -## 14. 在一个静态方法内调用一个非静态成员为什么是非法的 +## 14. 在一个静态方法内调用一个非静态成员为什么是非法的? 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 ## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用 - Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 + +Java 程序在执行子类的构造方法之前,如果没有用 `super() `来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 `super() `来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。   -## 16. import java和javax有什么区别 +## 16. import java和javax有什么区别? -刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 +刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 所以,实际上java和javax没有区别。这都是一个名字。 -## 17. 接口和抽象类的区别是什么 +## 17. 接口和抽象类的区别是什么? -1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 -2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 -3. 一个类可以实现多个接口,但最多只能实现一个抽象类 -4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 -5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 +1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。 +2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 +3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。 +4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 +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 所修饰; -2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 +1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。 +2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 -4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。 +4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。 ## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? @@ -266,21 +262,21 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! -## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么? +## 21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么? 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 -## 22. 构造方法有哪些特性 +## 22. 构造方法有哪些特性? -1. 名字与类名相同; -2. 没有返回值,但不能用void声明构造函数; +1. 名字与类名相同。 +2. 没有返回值,但不能用void声明构造函数。 3. 生成类的对象时自动执行,无需调用。 ## 23. 静态方法和实例方法有何不同 1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 -2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. +2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。 ## 24. 对象的相等与指向他们的引用相等,两者有什么不同? @@ -292,11 +288,11 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象 ## 26. == 与 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 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - - ## 27. hashCode 与 equals (重要) 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” @@ -338,12 +333,9 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 ### 为什么要有 hashCode +**我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 -**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** - -当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - - +通过我们可以看出:`hashCode()` 的作用就是**获取哈希码**,也称为散列码;它实际上是返回一个int整数。这个**哈希码的作用**是确定该对象在哈希表中的索引位置。**`hashCode() `在散列表中才有用,在其它情况下没用。**在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。 ### hashCode()与equals()的相关规定 @@ -353,13 +345,15 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** 5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) +推荐阅读:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html) + -## 28. 为什么Java中只有值传递 +## 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) + [为什么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. 简述线程,程序,进程的基本概念.以及他们之间关系是什么? +## 29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么? **线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 @@ -403,7 +397,10 @@ final关键字主要用在三个地方:变量、方法、类。 ### Java异常类层次结构图 ![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(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 @@ -435,17 +432,23 @@ final关键字主要用在三个地方:变量、方法、类。 下面这部分内容来自issue:。 -**关于返回值:** +**注意:** 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下: -如果try语句里有return,返回的是try语句块中变量值。 -详细执行过程如下: +```java + public static int f(int value) { + try { + return value * value; + } finally { + if (value == 2) { + return 0; + } + } + } +``` -1. 如果有返回值,就把返回值保存到局部变量中; -2. 执行jsr指令跳到finally语句里执行; -3. 执行完finally语句后,返回之前保存在局部变量表里的值。 -4. 如果try,finally语句里均有return,忽略try的return,而使用finally的return. +如果调用 `f(2)`,返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。 -## 33 Java序列化中如果有些字段不想进行序列化 怎么办 +## 33 Java序列化中如果有些字段不想进行序列化,怎么办? 对于不想进行序列化的变量,使用transient关键字修饰。 @@ -468,8 +471,54 @@ BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); 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 , Selector,Buffer等抽象。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 + +详见笔主的这篇文章: + +## 37. Collections 工具类和 Arrays 工具类常见方法总结 + +详见笔主的这篇文章: + ## 参考 - https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre - https://www.educba.com/oracle-vs-openjdk/ - 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) diff --git "a/docs/java/Java\347\226\221\351\232\276\347\202\271.md" "b/docs/java/Java\347\226\221\351\232\276\347\202\271.md" new file mode 100644 index 00000000000..8abad01b9ea --- /dev/null +++ "b/docs/java/Java\347\226\221\351\232\276\347\202\271.md" @@ -0,0 +1,374 @@ + + +- [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-操作) + + + +# 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默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 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 myList = Arrays.asList(myArray); +//上面两个语句等价于下面一条语句 +List myList = Arrays.asList("Apple","Banana", "Orange"); +``` + +JDK 源码对于这个方法的说明: + +```java +/** + *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 + */ +public static List 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 extends AbstractList + 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 action) { + ... + } + + @Override + public void replaceAll(UnaryOperator operator) { + ... + } + + @Override + public void sort(Comparator c) { + ... + } + } +``` + +我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 + +```java +public E remove(int index) { + throw new UnsupportedOperationException(); +} +``` + +### 2.1.4. 如何正确的将数组转换为ArrayList? + +stackoverflow:https://dwz.cn/vcBkTiTW + +**1. 自己动手实现(教育目的)** + +```java +//JDK1.5+ +static List arrayToList(final T[] array) { + final List l = new ArrayList(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 il = ImmutableList.of("string", "elements"); // from varargs +List 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 l1 = Lists.newArrayList(anotherListOrCollection); // from collection +List l2 = Lists.newArrayList(aStringArray); // from array +List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs +``` + +**5. 使用 Apache Commons Collections** + +```java +List list = new ArrayList(); +CollectionUtils.addAll(list, str); +``` + +## 2.2. Collection.toArray()方法使用的坑&如何反转数组 + +该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。 + +```java +String [] s= new String[]{ + "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A" +}; +List list = Arrays.asList(s); +Collections.reverse(list); +s=list.toArray(new String[0]);//没有指定类型的话会报错 +``` + +由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见: + +## 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) + + + diff --git "a/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md" "b/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md" new file mode 100644 index 00000000000..46c9c16994b --- /dev/null +++ "b/docs/java/Java\347\250\213\345\272\217\350\256\276\350\256\241\351\242\230.md" @@ -0,0 +1,125 @@ +## 泛型的实际应用 + +### 实现最小值函数 + +自己设计一个泛型的获取数组最小值的函数.并且这个方法只能接受Number的子类并且实现了Comparable接口。 + +```java +//注意:Number并没有实现Comparable +private static > 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. +``` \ No newline at end of file diff --git "a/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" "b/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" new file mode 100644 index 00000000000..11b7410e1c8 --- /dev/null +++ "b/docs/java/Java\347\274\226\347\250\213\350\247\204\350\214\203.md" @@ -0,0 +1,10 @@ +根据各位建议加上了这部分内容,我暂时只是给出了两个资源,后续可能会对重要的点进行总结,然后更新在这里,如果你总结过这类东西,欢迎与我联系! + +### 团队 + +- **阿里巴巴Java开发手册(详尽版)** +- **Google Java编程风格指南:** + +### 个人 + +- **程序员你为什么这么累:** diff --git "a/docs/java/Multithread/1\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Multithread/1\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 00000000000..66c26fd7d01 --- /dev/null +++ "b/docs/java/Multithread/1\345\271\266\345\217\221\347\274\226\347\250\213\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -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 内存区域讲的最清楚的一篇文章》]() + +
+ +
+ + +从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (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(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long 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/ + + \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/Multithread/AQS.md" b/docs/java/Multithread/AQS.md similarity index 96% rename from "Java\347\233\270\345\205\263/Multithread/AQS.md" rename to docs/java/Multithread/AQS.md index f405db1f17d..8a82051bc46 100644 --- "a/Java\347\233\270\345\205\263/Multithread/AQS.md" +++ b/docs/java/Multithread/AQS.md @@ -1,5 +1,5 @@ -**目录:** +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 - [1 AQS 简单介绍](#1-aqs-简单介绍) @@ -23,10 +23,6 @@ > 常见问题:AQS原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗? -**本节思维导图:** - -![并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-31/61115865.jpg) - ### 1 AQS 简单介绍 AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 @@ -128,7 +124,7 @@ tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true ### 3 Semaphore(信号量)-允许多个线程同时访问 -**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下: +**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** 示例代码如下: ```java /** @@ -471,6 +467,12 @@ CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文 ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。 -由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。 +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取! + +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 -- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd) +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file diff --git a/docs/java/Multithread/Atomic.md b/docs/java/Multithread/Atomic.md new file mode 100644 index 00000000000..05f4f2c9199 --- /dev/null +++ b/docs/java/Multithread/Atomic.md @@ -0,0 +1,556 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + +> 个人觉得这一节掌握基本的使用即可! + + + +- [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-类使用示例) + + + +### 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 ar = new AtomicReference(); + 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 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 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 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) \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" b/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md similarity index 51% rename from "Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" rename to docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md index 10d6177b040..16ccbe19add 100644 --- "a/Java\347\233\270\345\205\263/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" +++ b/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md @@ -1,28 +1,67 @@ - - - -# 一 面试中关于 synchronized 关键字的 5 连击 - -### 1.1 说一说自己对于 synchronized 关键字的了解 +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [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) + + + +# Java 并发进阶常见面试题总结 + +## 1. synchronized 关键字 + +### 1.1. 说一说自己对于 synchronized 关键字的了解 synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 -### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 +### 1.2. 说说自己是怎么使用 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 关键字的具体使用。 +- **修饰实例方法:** 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 +- **修饰静态方法:** :也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 +- **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” +**总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能! +下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” **双重校验锁实现对象单例(线程安全)** @@ -60,7 +99,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueIns 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 -### 1.3 讲一下 synchronized 关键字的底层原理 +### 1.3. 讲一下 synchronized 关键字的底层原理 **synchronized 关键字底层原理属于 JVM 层面。** @@ -79,11 +118,11 @@ public class SynchronizedDemo { 通过 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 修饰方法的的情况** @@ -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 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 -### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 +### 1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 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) +关于这几种优化的详细信息可以查看笔主的这篇文章: -### 1.5 谈谈 synchronized和ReenTrantLock 的区别 +### 1.5. 谈谈 synchronized和ReentrantLock 的区别 **① 两者都是可重入锁** 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增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 增加了一些高级功能** -相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** +相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** -- **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提供了一种能够中断等待锁的线程的机制**,通过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是一个不错的选择。 +如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。 **④ 性能已不是选择标准** -# 二 面试中关于线程池的 4 连击 +## 2. volatile关键字 -### 2.1 讲一下Java内存模型 +### 2.1. 讲一下Java内存模型 -在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。 +在 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) -要解决这个问题,就需要把变量声明为 **volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 +要解决这个问题,就需要把变量声明为**volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 -说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 +说白了, **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关键字比较 @@ -157,11 +196,164 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团 - **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。** - **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。** +## 3. ThreadLocal + +### 3.1. ThreadLocal简介 + +通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。** + +**如果你创建了一个`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 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默认格式化程序与初始化值相同,其他线程也一样。 -# 三 面试中关于 线程池的 2 连击 +上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。 +```java + private static final ThreadLocal formatter = new ThreadLocal(){ + @Override + protected SimpleDateFormat initialValue() + { + return new SimpleDateFormat("yyyyMMdd HHmm"); + } + }; +``` -### 3.1 为什么要用线程池? +### 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> { + /** 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 实现的,前面我们也讲到了 虚拟机团 - **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 - **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -### 3.2 实现Runnable接口和Callable接口的区别 +### 4.2. 实现Runnable接口和Callable接口的区别 如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。 **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callable(Runnable task)`或`Executors.callable(Runnable task,Object resule)`)。 -### 3.3 执行execute()方法和submit()方法的区别是什么呢? +### 4.3. 执行execute()方法和submit()方法的区别是什么呢? 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** - - 2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 - -### 3.4 如何创建线程池 + 2)**`submit()` 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 + +### 4.4. 如何创建线程池 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 > Executors 返回线程池对象的弊端如下: -> -> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 +> +> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 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来实现** 我们可以创建三种类型的ThreadPoolExecutor: @@ -204,12 +394,11 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团 - **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 对应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 连击 - -### 4.1 介绍一下Atomic 原子类 +### 5.1. 介绍一下Atomic 原子类 Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 @@ -218,9 +407,9 @@ 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:整形原子类 - AtomicLong:长整型原子类 -- AtomicBoolean :布尔型原子类 +- AtomicBoolean:布尔型原子类 **数组类型** @@ -237,25 +426,25 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是 - AtomicIntegerArray:整形数组原子类 - AtomicLongArray:长整形数组原子类 -- AtomicReferenceArray :引用类型数组原子类 +- AtomicReferenceArray:引用类型数组原子类 **引用类型** - AtomicReference:引用类型原子类 -- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicStampedReference:原子更新引用类型里的字段原子类 - AtomicMarkableReference :原子更新带有标记位的引用类型 **对象的属性修改类型** -- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 - AtomicLongFieldUpdater:原子更新长整形字段的更新器 -- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 +- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 -### 4.3 讲讲 AtomicInteger 的使用 +### 5.3. 讲讲 AtomicInteger 的使用 **AtomicInteger 类常用方法** - + ```java public final int get() //获取当前的值 public final int getAndSet(int newValue)//获取当前的值,并设置新的值 @@ -284,7 +473,7 @@ class AtomicIntegerTest { ``` -### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理 +### 5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理 AtomicInteger 线程安全原理简单分析 @@ -303,7 +492,7 @@ AtomicInteger 类的部分源码: } private volatile int value; -``` +``` AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 @@ -311,17 +500,17 @@ CAS的原理是拿期望的值和原本的一个值作比较,如果相同则 关于 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包下面。 -![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能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 -### 5.2 AQS 原理分析 +### 6.2. AQS 原理分析 AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 @@ -329,9 +518,7 @@ AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 -#### 5.2.1 AQS 原理概览 - - +#### 6.2.1. AQS 原理概览 **AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** @@ -340,7 +527,7 @@ AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 看个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对该同步状态进行原子操作实现对其值的修改。 @@ -348,7 +535,7 @@ AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列 private volatile int state;//共享变量,使用volatile修饰保证线程可见性 ``` -状态信息通过procted类型的getState,setState,compareAndSetState进行操作 +状态信息通过protected类型的getState,setState,compareAndSetState进行操作 ```java @@ -366,20 +553,20 @@ protected final boolean compareAndSetState(int expect, int update) { } ``` -#### 5.2.2 AQS 对资源的共享方式 +#### 6.2.2. AQS 对资源的共享方式 **AQS定义两种资源共享方式** - **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 -- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 +- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 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 - https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html -### 5.3 AQS 组件总结 +### 6.3. AQS 组件总结 - **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 - **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) - -# Reference +## 7 Reference - 《深入理解 Java 虚拟机》 - 《实战 Java 高并发程序设计》 - 《Java并发编程的艺术》 - http://www.cnblogs.com/waterystone/p/4920797.html - https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html +- + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取! + +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 + +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file diff --git a/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md b/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md new file mode 100644 index 00000000000..f7962cb6706 --- /dev/null +++ b/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md @@ -0,0 +1,316 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [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-方法) + + + +# 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 内存区域讲的最清楚的一篇文章》]() + +
+ +
+ +从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (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(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long 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) \ No newline at end of file diff --git a/docs/java/Multithread/ThredLocal.md b/docs/java/Multithread/ThredLocal.md new file mode 100644 index 00000000000..590cf754a2e --- /dev/null +++ b/docs/java/Multithread/ThredLocal.md @@ -0,0 +1,158 @@ +[ThreadLocal造成OOM内存溢出案例演示与原理分析](https://blog.csdn.net/xlgen157387/article/details/78298840) + +[深入理解 Java 之 ThreadLocal 工作原理]() + +## 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 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 formatter = new ThreadLocal(){ + @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> { + /** The value associated with this ThreadLocal. */ + Object value; + + Entry(ThreadLocal k, Object v) { + super(k); + value = v; + } + } +``` + +**弱引用介绍:** + +> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 +> +> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 \ No newline at end of file diff --git "a/Java\347\233\270\345\205\263/synchronized.md" b/docs/java/Multithread/synchronized.md similarity index 95% rename from "Java\347\233\270\345\205\263/synchronized.md" rename to docs/java/Multithread/synchronized.md index b0b0700ad8e..0a1f4f2b073 100644 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ b/docs/java/Multithread/synchronized.md @@ -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) @@ -12,7 +10,7 @@ Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/ 下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!” +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” @@ -48,7 +46,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueIns 2. 初始化 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 的指令重排,保证在多线程环境下也能正常运行。 @@ -141,7 +139,7 @@ JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、 **⑤ 锁粗化** -原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 +原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 diff --git "a/Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" "b/docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" similarity index 90% rename from "Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" rename to "docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" index 96a9fe23176..645b279b121 100644 --- "a/Java\347\233\270\345\205\263/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" +++ "b/docs/java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" @@ -1,3 +1,4 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 @@ -37,10 +38,10 @@ JDK提供的这些容器大部分在 `java.util.concurrent` 包中。 所以就有了 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线程安全的具体实现方式/底层具体实现](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 和 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/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。 - - ## 七 参考 - 《实战Java高并发程序设计》 - https://javadoop.com/post/java-concurrent-queue - 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) \ No newline at end of file diff --git "a/docs/java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" "b/docs/java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..ee1dd8c2448 --- /dev/null +++ "b/docs/java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" @@ -0,0 +1,163 @@ +JDK8接口规范 +=== +在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。。 +--- +## 1.JDK8以前的接口规范 +- JDK8以前接口可以定义的变量和方法 + - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。 + - 所有方法(Method)不论是否显示 的声明为```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修饰的实例方法)的。 +从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。 +
下面是一个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方法,也可以覆盖该方法。 +这有点像子类继承父类实例方法。 +
+接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用** +```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 +``` +出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。 +
+那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?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 +``` diff --git a/docs/java/What's New in JDK8/Java8Tutorial.md b/docs/java/What's New in JDK8/Java8Tutorial.md new file mode 100644 index 00000000000..9b473ea7d03 --- /dev/null +++ b/docs/java/What's New in JDK8/Java8Tutorial.md @@ -0,0 +1,933 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + +随着 Java 8 的普及度越来越高,很多人都提到面试中关于Java 8 也是非常常问的知识点。应各位要求和需要,我打算对这部分知识做一个总结。本来准备自己总结的,后面看到Github 上有一个相关的仓库,地址: +[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的,我对其进行了翻译并添加和修改了部分内容,下面是正文了。 + + + +- [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) + + + + +# 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 names = Arrays.asList("peter", "anna", "mike", "xenia"); + +Collections.sort(names, new Comparator() { + @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 { + T convert(F from); +} +``` + +```java + // TODO 将数字字符串转换为整数类型 + Converter 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 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 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 create(String firstName, String lastName); +} +``` + +这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂: + +```java +PersonFactory 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 stringConverter = + (from) -> String.valueOf(from + num); + +stringConverter.convert(2); // 3 +``` + +但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确: + +```java +int num = 1; +Converter stringConverter = + (from) -> String.valueOf(from + num); + +stringConverter.convert(2); // 3 +``` + +不过这里的 num 必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译: + +```java +int num = 1; +Converter stringConverter = + (from) -> String.valueOf(from + num); +num = 3;//在lambda表达式中试图修改num同样是不允许的。 +``` + +#### 访问字段和静态变量 + +与局部变量相比,我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。 + +```java +class Lambda4 { + static int outerStaticNum; + int outerNum; + + void testScopes() { + Converter stringConverter1 = (from) -> { + outerNum = 23; + return String.valueOf(from); + }; + + Converter 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 { + + // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断. + boolean test(T t); + + //and方法与关系型运算符"&&"相似,两边都成立才返回true + default Predicate and(Predicate other) { + Objects.requireNonNull(other); + return (t) -> test(t) && other.test(t); + } + // 与关系运算符"!"相似,对判断进行取反 + default Predicate negate() { + return (t) -> !test(t); + } + //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true + default Predicate or(Predicate other) { + Objects.requireNonNull(other); + return (t) -> test(t) || other.test(t); + } + // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal). + static Predicate isEqual(Object targetRef) { + return (null == targetRef) + ? Objects::isNull + : object -> targetRef.equals(object); + } +``` + +示例: + +```java +Predicate predicate = (s) -> s.length() > 0; + +predicate.test("foo"); // true +predicate.negate().test("foo"); // false + +Predicate nonNull = Objects::nonNull; +Predicate isNull = Objects::isNull; + +Predicate isEmpty = String::isEmpty; +Predicate isNotEmpty = isEmpty.negate(); +``` + +#### Functions + +Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen): + +**译者注:** Function 接口源码如下 + +```java + +package java.util.function; + +import java.util.Objects; + +@FunctionalInterface +public interface Function { + + //将Function对象应用到输入的参数上,然后返回计算结果。 + R apply(T t); + //将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。 + default Function compose(Function before) { + Objects.requireNonNull(before); + return (V v) -> apply(before.apply(v)); + } + // + default Function andThen(Function after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } + + static Function identity() { + return t -> t; + } +} +``` + + + +```java +Function toInteger = Integer::valueOf; +Function backToString = toInteger.andThen(String::valueOf); +backToString.apply("123"); // "123" +``` + +#### Suppliers + +Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。 + +```java +Supplier personSupplier = Person::new; +personSupplier.get(); // new Person +``` + +#### Consumers + +Consumer 接口表示要对单个输入参数执行的操作。 + +```java +Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); +greeter.accept(new Person("Luke", "Skywalker")); +``` + +#### Comparators + +Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: + +```java +Comparator 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 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 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 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,请留意这个区别。更多内容查看: [IBM:Java 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 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 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代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 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) \ No newline at end of file diff --git "a/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md" "b/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md" new file mode 100644 index 00000000000..7de58352a5f --- /dev/null +++ "b/docs/java/What's New in JDK8/Java8\346\225\231\347\250\213\346\216\250\350\215\220.md" @@ -0,0 +1,18 @@ +### 书籍 + +- **《Java8 In Action》** +- **《写给大忙人看的Java SE 8》** + +上述书籍的PDF版本见 https://shimo.im/docs/CPB0PK05rP4CFmI2/ 中的 “Java 书籍推荐”。 + +### 开源文档 + +- **【译】Java 8 简明教程**: +- **30 seconds of java8:** + +### 视频 + +- **尚硅谷 Java 8 新特性** + +视频资源见: https://shimo.im/docs/CPB0PK05rP4CFmI2/ 。 + diff --git "a/docs/java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" "b/docs/java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 00000000000..359c4714473 --- /dev/null +++ "b/docs/java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,235 @@ +JDK8--Lambda表达式 +=== +## 1.什么是Lambda表达式 +**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递** + +## 2.Lambda表达式语法 +```(参数列表) -> {执行代码块}``` +
参数列表可以为空```()->{}``` +
可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值. +
在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}``` +
(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言) + +## 3.函数式接口 +在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
+**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
+当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子: +
+```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表达式就是一个函数,只不过它的返回值、参数列表都 +由编译器帮我们推断,因此可以减少很多代码量)。 +
Lambda也可以这样用 : +```java + Runnable runnable = () -> {...}; +``` +其实这和上面的用法没有什么本质上的区别。 +
至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范, +目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了, +因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]() +
+除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口 +比如(当然不止这些): +- ```java.util.concurrent.Callable``` +- ```java.util.Comparator``` +- ```java.io.FileFilter``` +
**关于JDK中的预定义的函数式接口** + +- JDK在```java.util.function```下预定义了很多函数式接口 + - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。 + - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。 + - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。 + - ```Supplier {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表达式捕获的。 +
lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值, +同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该 +不难理解。 +## 5.方法引用 +**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。** +
下面的例子会打印list中的每个元素: +```java +List 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);}``` +
我们看一下List#forEach方法 ```default void forEach(Consumer action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口 +```java +@FunctionalInterface +public interface Consumer { + void accept(T t); +``` +大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。 +
我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用 +```java +public class MethodReference { + public static void main(String[] args) { + List 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, +``` +可以看到,我们自己定义的方法也可以当做方法引用。 +
到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。 +- 方法引用 + - 类名::静态方法名 + - 类名::实例方法名 + - 类名::new (构造方法引用) + - 实例名::实例方法名 +可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种 +方法引用的使用方法。
+**类名::实例方法名**
+先来看一段代码 +```java + String[] strings = new String[10]; + Arrays.sort(strings, String::compareToIgnoreCase); +``` +**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
+我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator c)`, +可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下 +`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它 +的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样? +
是的,确实不一样但是别忘了,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里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。 +
**构造器引用**
+构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口 +```java + //compile error : String is not a functional interface + String str = String::new; +``` +下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。 +```java + interface IFunctional { + T func(); +} + +public class ConstructorReference { + + public ConstructorReference() { + } + + public static void main(String[] args) { + Supplier supplier0 = () -> new ConstructorReference(); + Supplier supplier1 = ConstructorReference::new; + IFunctional functional = () -> new ConstructorReference(); + IFunctional functional1 = ConstructorReference::new; + } +} +``` +下面是一个JDK官方的例子 +```java + public static , DEST extends Collection> + DEST transferElements( + SOURCE sourceCollection, + Supplier collectionFactory) { + + DEST result = collectionFactory.get(); + for (T t : sourceCollection) { + result.add(t); + } + return result; + } + + ... + + Set rosterSet = transferElements( + roster, HashSet::new); +``` + +**实例::实例方法** +
+其实开始那个例子就是一个实例::实例方法的引用 +```java +List 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类型推断,同样对于这个特性需要斟酌使用。 diff --git a/docs/java/What's New in JDK8/README.md b/docs/java/What's New in JDK8/README.md new file mode 100644 index 00000000000..fa71e907410 --- /dev/null +++ b/docs/java/What's New in JDK8/README.md @@ -0,0 +1,556 @@ +JDK8新特性总结 +====== +总结了部分JDK8新特性,另外一些新特性可以通过Oracle的官方文档查看,毕竟是官方文档,各种新特性都会介绍,有兴趣的可以去看。
+[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表达式 +### 1.什么是Lambda表达式 +**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递** + +### 2.Lambda表达式语法 +```(参数列表) -> {执行代码块}``` +
参数列表可以为空```()->{}``` +
可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值. +
在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}``` +
(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言) + +### 3.函数式接口 +在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
+**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
+当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子: +
+```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表达式就是一个函数,只不过它的返回值、参数列表都 +由编译器帮我们推断,因此可以减少很多代码量)。 +
Lambda也可以这样用 : +```java + Runnable runnable = () -> {...}; +``` +其实这和上面的用法没有什么本质上的区别。 +
至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范, +目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了, +因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]() +
+除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口 +比如(当然不止这些): +- ```java.util.concurrent.Callable``` +- ```java.util.Comparator``` +- ```java.io.FileFilter``` +
**关于JDK中的预定义的函数式接口** + +- JDK在```java.util.function```下预定义了很多函数式接口 + - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。 + - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。 + - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。 + - ```Supplier {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表达式捕获的。 +
lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。 + +### 5.方法引用 +**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。** +
下面的例子会打印list中的每个元素: +```java +List 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);}``` +
我们看一下List#forEach方法 ```default void forEach(Consumer action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口 +```java +@FunctionalInterface +public interface Consumer { + void accept(T t); +``` +大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。 +
我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用 +```java +public class MethodReference { + public static void main(String[] args) { + List 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, +``` +可以看到,我们自己定义的方法也可以当做方法引用。 +
到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。 +- 方法引用 + - 类名::静态方法名 + - 类名::实例方法名 + - 类名::new (构造方法引用) + - 实例名::实例方法名 +可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种 +方法引用的使用方法。
+**类名::实例方法名**
+先来看一段代码 +```java + String[] strings = new String[10]; + Arrays.sort(strings, String::compareToIgnoreCase); +``` +**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
+我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator c)`, +可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下 +`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它 +的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样? +
是的,确实不一样但是别忘了,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里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。 +
**构造器引用**
+构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口 +```java + //compile error : String is not a functional interface + String str = String::new; +``` +下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。 +```java + interface IFunctional { + T func(); +} + +public class ConstructorReference { + + public ConstructorReference() { + } + + public static void main(String[] args) { + Supplier supplier0 = () -> new ConstructorReference(); + Supplier supplier1 = ConstructorReference::new; + IFunctional functional = () -> new ConstructorReference(); + IFunctional functional1 = ConstructorReference::new; + } +} +``` +下面是一个JDK官方的例子 +```java + public static , DEST extends Collection> + DEST transferElements( + SOURCE sourceCollection, + Supplier collectionFactory) { + + DEST result = collectionFactory.get(); + for (T t : sourceCollection) { + result.add(t); + } + return result; + } + + ... + + Set rosterSet = transferElements( + roster, HashSet::new); +``` + +**实例::实例方法** +
+其实开始那个例子就是一个实例::实例方法的引用 +```java +List 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类型推断,同样对于这个特性需要斟酌使用。 + + +___ + + +##              JDK8接口规范 +### 在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法,否则会报错。为了保持兼容性而做出妥协的特性还有泛型,泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。 +### 1.JDK8以前的接口规范 +- JDK8以前接口可以定义的变量和方法 + - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。 + - 所有方法(Method)不论是否显示 的声明为```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修饰的实例方法)的。 +从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。 +
下面是一个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方法,也可以选择覆盖该方法。 +这有点像子类继承父类实例方法。 +
+接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用** +```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 +``` +出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。 +
+那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?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 +``` + + +___ + + +##              改进的类型推断 +### 1.什么是类型推断 +类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。 +看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的: +```java + Map map = new Map(); +``` +而我们平常写的都是这样的: +```java + Map map = new Map<>(); +``` +这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题 +(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。 +(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽) +
+在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译 +错误 +```java + List stringList = new ArrayList<>(); + stringList.add("A"); + //error : addAll(java.util.Collection)in List cannot be applied to (java.util.List) + stringList.addAll(Arrays.asList()); +``` +但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码 +会更少。加强的类型推断还有一个就是用于Lambda表达式了。 +
+大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息, +相信随着Java的进化,这些便利的功能会越来越强大。 + + +____ + + +##              通过反射获得方法的参数信息 +JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里 +也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外 +的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如 +LocalVariableTable,javac就不会默认生成,需要你加上 -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信息,这就是参数的名字,可以看到默认是不保存的。 +
下面看一下在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) +
+## 总结与补充 +在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework +里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。 + +____ + + + + +___ diff --git a/docs/java/What's New in JDK8/Stream.md b/docs/java/What's New in JDK8/Stream.md new file mode 100644 index 00000000000..de7c86e3f2e --- /dev/null +++ b/docs/java/What's New in JDK8/Stream.md @@ -0,0 +1,75 @@ +Stream API 旨在让编码更高效率、干净、简洁。 + +### 从迭代器到Stream操作 + +当使用 `Stream` 时,我们一般会通过三个阶段建立一个流水线: + +1. 创建一个 `Stream`; +2. 进行一个或多个中间操作; +3. 使用终止操作产生一个结果,`Stream` 就不会再被使用了。 + +**案例1:统计 List 中的单词长度大于6的个数** + +```java +/** +* 案例1:统计 List 中的单词长度大于6的个数 +*/ +ArrayList wordsList = new ArrayList(); +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 list = list.stream() + .distinct() + .collect(Collectors.toList()); +``` + +### `map` + +map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数: + +```java +List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); +// 获取 List 中每个元素对应的平方数并去重 +List squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); +System.out.println(squaresList.toString());//[9, 4, 49, 25] +``` + diff --git "a/docs/java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" "b/docs/java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" new file mode 100644 index 00000000000..b5cff7bb0c0 --- /dev/null +++ "b/docs/java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" @@ -0,0 +1,30 @@ +##              改进的类型推断 +### 1.什么是类型推断 +类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。 +看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的: +```java + Map map = new Map(); +``` +而我们平常写的都是这样的: +```java + Map map = new Map<>(); +``` +这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题 +(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。 +(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽) +
+在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译 +错误 +```java + List stringList = new ArrayList<>(); + stringList.add("A"); + //error : addAll(java.util.Collection)in List cannot be applied to (java.util.List) + stringList.addAll(Arrays.asList()); +``` +但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码 +会更少。加强的类型推断还有一个就是用于Lambda表达式了。 +
+大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息, +相信随着Java的进化,这些便利的功能会越来越强大。 + + diff --git "a/docs/java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" "b/docs/java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..a1d91c4b2fe --- /dev/null +++ "b/docs/java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" @@ -0,0 +1,79 @@ +##              通过反射获得方法的参数信息 +JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里 +也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外 +的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如 +LocalVariableTable,javac就不会默认生成,需要你加上 -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信息,这就是参数的名字,可以看到默认是不保存的。 +
下面看一下在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) +
+## 总结与补充 +在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework +里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。 diff --git "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" b/docs/java/collection/ArrayList-Grow.md similarity index 98% rename from "Java\347\233\270\345\205\263/ArrayList-Grow.md" rename to docs/java/collection/ArrayList-Grow.md index d763cb83492..06fa5388d76 100644 --- "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" +++ b/docs/java/collection/ArrayList-Grow.md @@ -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进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   @@ -270,7 +270,6 @@ public class ArrayscopyOfTest { 10 ``` - ### 3.3 两者联系和区别 **联系:** @@ -281,8 +280,6 @@ public class ArrayscopyOfTest { `arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 - - ## 四 `ensureCapacity`方法 ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? @@ -341,7 +338,6 @@ public class EnsureCapacityTest { ``` 使用ensureCapacity方法前:4637 使用ensureCapacity方法后:241 - ``` 通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数 diff --git "a/Java\347\233\270\345\205\263/ArrayList.md" b/docs/java/collection/ArrayList.md similarity index 98% rename from "Java\347\233\270\345\205\263/ArrayList.md" rename to docs/java/collection/ArrayList.md index c3e8dd47896..f6578a7a784 100644 --- "a/Java\347\233\270\345\205\263/ArrayList.md" +++ b/docs/java/collection/ArrayList.md @@ -660,7 +660,7 @@ public class ArrayList extends AbstractList (3)private class SubList extends AbstractList implements RandomAccess (4)static final class ArrayListSpliterator implements Spliterator ``` -  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不能实现的。 ### ArrayList经典Demo ```java diff --git "a/Java\347\233\270\345\205\263/HashMap.md" b/docs/java/collection/HashMap.md similarity index 99% rename from "Java\347\233\270\345\205\263/HashMap.md" rename to docs/java/collection/HashMap.md index 45fad50cdb3..716bb1f34a5 100644 --- "a/Java\347\233\270\345\205\263/HashMap.md" +++ b/docs/java/collection/HashMap.md @@ -235,7 +235,7 @@ HashMap只提供了put用于添加元素,putVal方法只是给put方法调用 **对putVal方法添加元素的分析如下:** - ①如果定位到的数组位置没有元素 就直接插入。 -- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入。 +- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。 diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 00000000000..031f82e80d7 --- /dev/null +++ "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,456 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [剖析面试最常见问题之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) + - [如何选用集合?](#如何选用集合) + + + +# 剖析面试最常见问题之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` 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 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 + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() 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值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同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》 : + +## 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 。 + +详情请查看: + +## 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,竞争会越来越激烈效率越低。 + +**两者的对比图:** + +图片来源: + +**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的ConcurrentHashMap(TreeBin: 红黑二叉树节点 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 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 arrayList = new ArrayList(); + 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() { + + @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 { + 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 pdata = new TreeMap(); + 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 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) diff --git "a/Java\347\233\270\345\205\263/LinkedList.md" b/docs/java/collection/LinkedList.md similarity index 98% rename from "Java\347\233\270\345\205\263/LinkedList.md" rename to docs/java/collection/LinkedList.md index 31f5f4838e9..d26bc752267 100644 --- "a/Java\347\233\270\345\205\263/LinkedList.md" +++ b/docs/java/collection/LinkedList.md @@ -186,7 +186,7 @@ public void addLast(E e) { } ``` ### 根据位置取数据的方法 -**get(int index):**:根据指定索引返回数据 +**get(int index):** 根据指定索引返回数据 ```java public E get(int index) { //检查index范围是否在size之内 @@ -220,7 +220,7 @@ public E peekFirst() { getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中**getFirst()** 和**element()** 方法将会在链表为空时,抛出异常 -element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException +element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException **获取尾节点(index=-1)数据方法:** ```java public E getLast() { @@ -359,7 +359,7 @@ E unlink(Node x) { //删除前驱指针 if (prev == null) { - first = next;如果删除的节点是头节点,令头节点指向该节点的后继节点 + first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 } else { prev.next = next;//将前驱节点的后继节点指向后继节点 x.prev = null; @@ -458,7 +458,7 @@ public class LinkedListDemo { linkedList.add(3); linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表) System.out.println("After removeFirstOccurrence(3):" + linkedList); - linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表) + linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表) System.out.println("After removeFirstOccurrence(3):" + linkedList); /************************** 遍历操作 ************************/ diff --git "a/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" "b/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..ad0a6545287 --- /dev/null +++ "b/docs/java/jvm/JDK\347\233\221\346\216\247\345\222\214\346\225\205\351\232\234\345\244\204\347\220\206\345\267\245\345\205\267\346\200\273\347\273\223.md" @@ -0,0 +1,337 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [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多合一故障处理工具) + + + +# 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`:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID(Local 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`: 监视虚拟机各种运行状态信息 + +jstat(JVM Statistics Monitoring Tool) 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程(需要远程主机提供 RMI 支持)虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。 + +**`jstat` 命令使用格式:** + +```powershell +jstat -

+ +
+ + +上图所示的 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 有什么不同呢?** + +- **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。 +- **老年代 GC(Major 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. **空间问题(标记清除后会产生大量不连续的碎片)** + +公众号 + +### 3.2 复制算法 + +为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 + +公众号 + +### 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 收集器 + +**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。** + +**CMS(Concurrent 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、多核环境下的硬件优势,使用多个 CPU(CPU 或者 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 +- + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《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) + + + + + + + + + + + diff --git "a/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" new file mode 100644 index 00000000000..449898843c9 --- /dev/null +++ "b/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" @@ -0,0 +1,433 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [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-种基本类型的包装类和常量池) + - [参考](#参考) + + + +# Java 内存区域详解 + +如果没有特殊说明,都是针对的是 HotSpot 虚拟机。 + +## 写在前面 (常见面试题) + +### 基本问题 + +- **介绍下 Java 内存区域(运行时数据区)** +- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)** +- **对象的访问定位的两种方式(句柄和直接指针两种方式)** + +### 拓展问题 + +- **String 类和常量池** +- **8 种基本类型的包装类和常量池** + +## 一 概述 + +对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 + +## 二 运行时数据区域 +Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。 + +**JDK 1.8 之前:** + +
+ +
+ +**JDK 1.8 :** + +
+ +
+ +**线程私有的:** + +- 程序计数器 +- 虚拟机栈 +- 本地方法栈 + +**线程共享的:** + +- 堆 +- 方法区 +- 直接内存 (非运行时数据区的一部分) + +### 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 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** + +
+ +
+ +上图所示的 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。你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 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 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 + + +### 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 方法,便需要创建新的对象。** + +再给大家一个图应该更容易理解,图片来源:: + +![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 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。** +- **两种浮点数类型的包装类 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=40;Java 在编译的时候会直接将代码封装成 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 虚拟机》 +- +- +- +- + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《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) diff --git "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" new file mode 100644 index 00000000000..b380716c603 --- /dev/null +++ "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" @@ -0,0 +1,142 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [回顾一下类加载过程](#回顾一下类加载过程) +- [类加载器总结](#类加载器总结) +- [双亲委派模型](#双亲委派模型) + - [双亲委派模型介绍](#双亲委派模型介绍) + - [双亲委派模型实现源码分析](#双亲委派模型实现源码分析) + - [双亲委派模型的好处](#双亲委派模型的好处) + - [如果我们不想要双亲委派模型怎么办?](#如果我们不想要双亲委派模型怎么办) +- [自定义类加载器](#自定义类加载器) +- [推荐](#推荐) + + + +> 公众号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`。 + +## 推荐阅读 + +- +- +- + +### 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《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) + diff --git "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" new file mode 100644 index 00000000000..97385002eee --- /dev/null +++ "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" @@ -0,0 +1,91 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [类加载过程](#类加载过程) + - [加载](#加载) + - [验证](#验证) + - [准备](#准备) + - [解析](#解析) + - [初始化](#初始化) + + + +> 公众号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 程序代码(字节码),初始化阶段是执行类构造器 ` ()`方法的过程。 + +对于`()` 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 `()` 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。 + +对于初始化阶段,虚拟机严格规范了有且只有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虚拟机》 +- + +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《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) diff --git "a/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..8ff20a5d571 --- /dev/null +++ "b/docs/java/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" @@ -0,0 +1,224 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + + + +- [类文件结构](#类文件结构) + - [一 概述](#一-概述) + - [二 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-属性表集合) + - [参考](#参考) + + + +# 类文件结构 + +## 一 概述 + +在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。 + +Clojure(Lisp 语言的一种方言)、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 | 5 | 长整型字面量 | +| CONSTANT_Double_info | 6 | 双精度浮点型字面量 | +| CONSTANT_Class_info | 7 | 类或接口的符号引用 | +| CONSTANT_String_info | 8 | 字符串类型字面量 | +| CONSTANT_Fieldref_info | 9 | 字段的符号引用 | +| 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 虚拟机运行时会忽略掉它不认识的属性。 + +## 参考 + +- +- +- +- 《实战 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) diff --git "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" "b/docs/java/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" similarity index 100% rename from "Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" rename to "docs/java/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" "b/docs/network/HTTPS\344\270\255\347\232\204TLS.md" similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" rename to "docs/network/HTTPS\344\270\255\347\232\204TLS.md" diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" "b/docs/network/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" similarity index 98% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" rename to "docs/network/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" index a5a50f10cea..eb4a501f008 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" +++ "b/docs/network/\345\271\262\350\264\247\357\274\232\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" @@ -38,7 +38,7 @@ #### 分组(packet ): 因特网中传送的数据单元。由首部header和数据段组成。分组又称为包,首部可称为包头。 #### 存储转发(store and forward ): - 路由器收到一个分组,先存储下来,再检查气首部,查找转发表,按照首部中的目的地址,找到合适的接口转发出去。 + 路由器收到一个分组,先存储下来,再检查其首部,查找转发表,按照首部中的目的地址,找到合适的接口转发出去。 #### 带宽(bandwidth): 在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为b/s。 #### 吞吐量(throughput ): @@ -52,15 +52,15 @@ 大写字母I开头的Internet(互联网)是专用名词,它指全球最大的,开放的,由众多网络相互连接而成的特定的互联网,并采用TCP/IP协议作为通信规则,其前身为ARPANET。Internet的推荐译名为因特网,现在一般流行称为互联网。 - 3,路由器是实现分组交换的关键构件,其任务是转发受到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后在进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组有称为包。分组是在互联网中传送的数据单元,正式由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。 + 3,路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。 -4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由西组成边缘部分,其作用是提供连通性和交换。 +4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成边缘部分,其作用是提供连通性和交换。 5,计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S方式)和对等连接方式(P2P方式)。 6,客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。 -7,按照作用范围的不同,计算机网络分为广域网WAN,城域网MAN,局域网LAN,个人区域网PAN。 +7,按照作用范围的不同,计算机网络分为广域网WAN,城域网MAN,局域网LAN,个人区域网PAN。 8,计算机网络最常用的性能指标是:速率,带宽,吞吐量,时延(发送时延,处理时延,排队时延),时延带宽积,往返时间和信道利用率。 @@ -193,7 +193,7 @@ 12,以太网的适配器具有过滤功能,它只接收单播帧,广播帧和多播帧。 -13,使用集线器可以在物理层扩展以太网(扩展后的以太网任然是一个网络) +13,使用集线器可以在物理层扩展以太网(扩展后的以太网仍然是一个网络) ### (3),最重要的知识点 #### ① 数据链路层的点对点信道和广播信道的特点,以及这两种信道所使用的协议(PPP协议以及CSMA/CD协议)的特点 #### ② 数据链路层的三个基本问题:**封装成帧**,**透明传输**,**差错检测** @@ -280,7 +280,7 @@ 3,运输层的两个重要协议是用户数据报协议UDP和传输控制协议TCP。按照OSI的术语,两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元TPDU(Transport Protocol Data Unit)。但在TCP/IP体系中,则根据所使用的协议是TCP或UDP,分别称之为TCP报文段或UDP用户数据报。 -4,UDP在传送数据之前不需要先建立连接,远地主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的,面向连接的运输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。 +4,UDP在传送数据之前不需要先建立连接,远地主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的,面向连接的传输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。 5,硬件端口是不同硬件设备进行交互的接口,而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP和TCP的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到IP层交上来的运输层报文时,就能够 根据其首部中的目的端口号把数据交付应用层的目的应用层。(两个进程之间进行通信不光要知道对方IP地址而且要知道对方的端口号(为了找到对方计算机中的应用进程)) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" similarity index 59% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" rename to "docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index ba5fb91ecc9..aa378af9234 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -1,160 +1,138 @@ - - - -- [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议) - - [五层协议的体系结构](#五层协议的体系结构) - - [1 应用层](#1-应用层) - - [域名系统](#域名系统) - - [HTTP协议](#http协议) - - [2 运输层](#2-运输层) - - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) - - [UDP 的主要特点](#udp-的主要特点) - - [TCP 的主要特点](#tcp-的主要特点) - - [3 网络层](#3-网络层) - - [4 数据链路层](#4-数据链路层) - - [5 物理层](#5-物理层) - - [总结一下](#总结一下) -- [二 TCP 三次握手和四次挥手\(面试常客\)](#二-tcp-三次握手和四次挥手面试常客) - - [为什么要三次握手](#为什么要三次握手) - - [为什么要传回 SYN](#为什么要传回-syn) - - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) - - [为什么要四次挥手](#为什么要四次挥手) -- [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别) + + +- [一 OSI与TCP/IP各层的结构与功能,都有哪些协议?](#一-osi与tcpip各层的结构与功能都有哪些协议) + - [1.1 应用层](#11-应用层) + - [1.2 运输层](#12-运输层) + - [1.3 网络层](#13-网络层) + - [1.4 数据链路层](#14-数据链路层) + - [1.5 物理层](#15-物理层) + - [1.6 总结一下](#16-总结一下) +- [二 TCP 三次握手和四次挥手(面试常客)](#二-tcp-三次握手和四次挥手面试常客) + - [2.1 TCP 三次握手漫画图解](#21-tcp-三次握手漫画图解) + - [2.2 为什么要三次握手](#22-为什么要三次握手) + - [2.3 为什么要传回 SYN](#23-为什么要传回-syn) + - [2.4 传了 SYN,为啥还要传 ACK](#24-传了-syn为啥还要传-ack) + - [2.5 为什么要四次挥手](#25-为什么要四次挥手) +- [三 TCP,UDP 协议的区别](#三-tcpudp-协议的区别) - [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输) - - [停止等待协议](#停止等待协议) - - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) - - [连续ARQ协议](#连续arq协议) - - [滑动窗口](#滑动窗口) - - [流量控制](#流量控制) - - [拥塞控制](#拥塞控制) -- [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五-在浏览器中输入url地址---显示主页的过程(面试常客)) + - [4.1 ARQ协议](#41-arq协议) + - [停止等待ARQ协议](#停止等待arq协议) + - [连续ARQ协议](#连续arq协议) + - [4.2 滑动窗口和流量控制](#42-滑动窗口和流量控制) + - [4.3 拥塞控制](#43-拥塞控制) +- [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五--在浏览器中输入url地址---显示主页的过程面试常客) - [六 状态码](#六-状态码) - [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系) -- [八 HTTP长连接、短连接](#八-http长连接、短连接) -- [写在最后](#写在最后) - - [计算机网络常见问题回顾](#计算机网络常见问题回顾) - - [建议](#建议) - - - - -相对与上一个版本的计算机网路面试知识总结,这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。 - - -## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议 +- [八 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-的区别) +- [建议](#建议) +- [参考](#参考) + -### 五层协议的体系结构 +## 一 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协议**等等。我们把应用层交互的数据单元称为报文。 -#### 域名系统 +**域名系统** > 域名系统(Domain Name System缩写 DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。 -#### HTTP协议 +**HTTP协议** + > 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科) -### 2 运输层 +### 1.2 运输层 **运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。 -#### 运输层主要使用以下两种协议 +**运输层主要使用以下两种协议:** -1. **传输控制协议 TCP**(Transmisson Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。 +1. **传输控制协议 TCP**(Transmission Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。 2. **用户数据协议 UDP**(User Datagram Protocol)--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。 -#### 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 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。 +**TCP 与 UDP 的对比见问题三。** -### 3 网络层 +### 1.3 网络层 **在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**。 这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。 - 这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称. -互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。 +互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。 -### 4 数据链路层 +### 1.4 数据链路层 **数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装程帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。 在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。 控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。 -### 5 物理层 +### 1.5 物理层 在物理层上所传送的数据单位是比特。 **物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。 在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议,而往往表示互联网所使用的整个TCP/IP协议族。 -### 总结一下 +### 1.6 总结一下 上面我们对计算机网络的五层体系结构有了初步的了解,下面附送一张七层体系结构图总结一下。图片来源: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协议采用了三次握手策略。 -**漫画图解:** +### 2.1 TCP 三次握手漫画图解 -图片来源:《图解HTTP》 -![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095) +如下图所示,下面的两个机器人通过3次握手确定了对方能正确接收和发送消息(图片来源:《图解HTTP》)。 +![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/ACK 标志的数据包–二次握手–客户端 - 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端 -### 为什么要三次握手 +### 2.2 为什么要三次握手 **三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。** -第一次握手:Client 什么都不能确认;Server 确认了对方发送正常 +第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常 -第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常 +第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常 -第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常 +第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常 所以三次握手就能确认双发收发功能都正常,缺一不可。 -### 为什么要传回 SYN +### 2.3 为什么要传回 SYN 接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。 > SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。 -### 传了 SYN,为啥还要传 ACK +### 2.4 传了 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 连接则需要“四次挥手”: @@ -163,8 +141,7 @@ - 服务器-关闭与客户端的连接,发送一个FIN给客户端 - 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1 - -### 为什么要四次挥手 +### 2.5 为什么要四次挥手 任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。 @@ -172,12 +149,12 @@ 上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891) -## 三 TCP、UDP 协议的区别 +## 三 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) UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等 -TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 +TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 ## 四 TCP 协议如何保证可靠传输 @@ -187,67 +164,47 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, 4. TCP 的接收端会丢弃重复的数据。 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制) 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。 -7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 +7. **ARQ协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 +8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 +### 4.1 ARQ协议 +**自动重传请求**(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。 -### 停止等待协议 -- 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组; +#### 停止等待ARQ协议 +- 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组; - 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认; +**优点:** 简单 -**1) 无差错情况:** +**缺点:** 信道利用率低,等待时间长 -![](https://user-gold-cdn.xitu.io/2018/8/16/16541fa8c3816a90?w=514&h=473&f=png&s=9924) +**1) 无差错情况:** 发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。 **2) 出现差错情况(超时重传):** -![](https://user-gold-cdn.xitu.io/2018/8/16/16541faefdf249ab?w=953&h=480&f=png&s=19163) -停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。 - -**3) 确认丢失和确认迟到** - -- **确认丢失**:确认消息在传输过程丢失 - ![](https://user-gold-cdn.xitu.io/2018/8/16/16541fb6941a7165?w=918&h=461&f=png&s=19841) - 当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/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。 - -**优点:** 简单 +**3) 确认丢失和确认迟到** -**缺点:** 信道利用率低 +- **确认丢失** :确认消息在传输过程丢失。当A发送M1消息,B收到后,B向A发送了一个M1确认消息,但却在传输过程中丢失。而A并不知道,在超时计时过后,A重传M1消息,B再次收到该消息后采取以下两点措施:1. 丢弃这个重复的M1消息,不向上层交付。 2. 向A发送确认消息。(不会认为已经发送过了,就不再发送。A能重传,就证明B的确认消息丢失)。 +- **确认迟到** :确认消息在传输过程中迟到。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 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。 **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。 - -**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。 - -### 滑动窗口 -- TCP 利用滑动窗口实现流量控制的机制。 -- 滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。 -- TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。 +**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。 -### 流量控制 +### 4.2 滑动窗口和流量控制 -- TCP 利用滑动窗口实现流量控制。 -- 流量控制是为了控制发送方发送速率,保证接收方来得及接收。 -- 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。 +**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。 -### 拥塞控制 +### 4.3 拥塞控制 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。 @@ -256,15 +213,12 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 - **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。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. - **快重传与快恢复:** 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 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://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协议之间的关系 @@ -298,9 +249,9 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 图片来源:《图解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会话。 @@ -316,29 +267,60 @@ Connection:keep-alive —— [《HTTP长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html) +## 九 HTTP是不保存状态的协议,如何保存用户状态? -## 写在最后 -### 计算机网络常见问题回顾 +HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个Session)。 -- ①TCP三次握手和四次挥手、 -- ②在浏览器中输入url地址->>显示主页的过程 -- ③HTTP和HTTPS的区别 -- ④TCP、UDP协议的区别 -- ⑤常见的状态码。 +在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。 -### 建议 -非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 +**Cookie 被禁用怎么办?** +最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。 +![HTTP是无状态协议](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HTTP是无状态的.png) -### 参考 +## 十 Cookie的作用是什么?和Session有什么区别? -- [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/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) +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的主要区别是什么? +> 这部分回答引用这篇文章 的一些内容。 +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个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 +1. **缓存处理** :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。 +1. **带宽优化及网络连接的使用** :HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial 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》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 + +## 参考 + +- [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/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) +- diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" b/docs/operating-system/Shell.md similarity index 95% rename from "\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" rename to docs/operating-system/Shell.md index 4179f2702bb..e58f5c409e4 100644 --- "a/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" +++ b/docs/operating-system/Shell.md @@ -78,7 +78,7 @@ echo "helloworld!" 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) @@ -210,7 +210,7 @@ expr length "$name"; expr 5+6 // 直接输出 5+6 expr 5 + 6 // 输出 11 ``` -对于某些运算符,还需要我们使用符号"\"进行转义,否则就会提示语法错误。 +对于某些运算符,还需要我们使用符号`\`进行转义,否则就会提示语法错误。 ```shell expr 5 * 6 // 输出错误 @@ -260,7 +260,7 @@ echo $length #输出:5 echo $length2 #输出:5 # 输出数组第三个元素 echo ${array[2]} #输出:3 -unset array[1]# 删除下表为1的元素也就是删除第二个元素 +unset array[1]# 删除下标为1的元素也就是删除第二个元素 for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5 unset arr_number; # 删除数组中的所有元素 for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容 @@ -272,7 +272,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没 > 说明:图片来自《菜鸟教程》 Shell 编程支持下面几种运算符 - + - 算数运算符 - 关系运算符 - 布尔运算符 @@ -283,7 +283,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没 ![算数运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/4937342.jpg) -我以加法运算符做一个简单的示例: +我以加法运算符做一个简单的示例(注意:不是单引号,是反引号): ```shell #!/bin/bash @@ -380,10 +380,10 @@ a 不等于 b #!/bin/bash a=3; b=9; -if [ $a = $b ] +if [ $a -eq $b ] then echo "a 等于 b" -elif [ $a > $b ] +elif [ $a -gt $b ] then echo "a 大于 b" else @@ -394,7 +394,7 @@ fi 输出结果: ``` -a 大于 b +a 小于 b ``` 相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。 @@ -482,16 +482,20 @@ done ```shell #!/bin/bash -function(){ +hello(){ echo "这是我的第一个 shell 函数!" } -function +echo "-----函数开始执行-----" +hello +echo "-----函数执行完毕-----" ``` 输出结果: ``` +-----函数开始执行----- 这是我的第一个 shell 函数! +-----函数执行完毕----- ``` diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/operating-system/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" similarity index 91% rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" rename to "docs/operating-system/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" index 65cc9eaec0e..199bfc9b35a 100644 --- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/operating-system/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,3 +1,5 @@ +点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 + - [一 从认识操作系统开始](#一-从认识操作系统开始) @@ -22,12 +24,15 @@ +推荐一个Github开源的Linux学习指南(Java工程师向): + > 学习Linux之前,我们先来简单的认识一下操作系统。 ## 一 从认识操作系统开始 + ### 1.1 操作系统简介 -我通过以下四点介绍什么操作系统: +我通过以下四点介绍什么是操作系统: - **操作系统(Operation System,简称OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石;** - **操作系统本质上是运行在计算机上的软件程序 ;** @@ -92,7 +97,7 @@ Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是 **常见目录说明:** -- **/bin:** 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里; +- **/bin:** 存放二进制可执行文件(ls、cat、mkdir等),常用命令一般都在这里; - **/etc:** 存放系统管理和配置文件; - **/home:** 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示; - **/usr :** 用于存放系统应用程序; @@ -160,12 +165,12 @@ Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 3. **`vim 文件`:** 修改文件的内容(改) - + vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。 **在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤:** - vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) + vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) 4. **`rm -rf 文件`:** 删除文件(删) 同目录删除:熟记 `rm -rf` 文件 即可 @@ -181,14 +186,14 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g 其中: z:调用gzip压缩命令进行压缩 - + c:打包文件 - + v:显示运行过程 - + 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)解压压缩包:** @@ -201,7 +206,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g 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的权限命令 @@ -235,21 +240,21 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g **文件和目录权限的区别:** 对文件和目录而言,读写执行表示不同的意义。 - + 对于文件: -| 权限名称 | 可执行操作 | +| 权限名称 | 可执行操作 | | :-------- | --------:| -| r | 可以使用cat查看文件的内容 | -|w | 可以修改文件的内容 | +| r | 可以使用cat查看文件的内容 | +|w | 可以修改文件的内容 | | x | 可以将其运行为二进制文件 | 对于目录: -| 权限名称 | 可执行操作 | +| 权限名称 | 可执行操作 | | :-------- | --------:| -| r | 可以查看目录下列表 | -|w | 可以创建和删除目录下文件 | +| r | 可以查看目录下列表 | +|w | 可以创建和删除目录下文件 | | x | 可以使用cd进入目录 | @@ -260,7 +265,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g 一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用ls ‐ahl命令可以看到文件的所有者 也可以使用chown 用户名 文件名来修改文件的所有者 。 - **文件所在组** - + 当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组 用ls ‐ahl命令可以看到文件的所有组 也可以使用chgrp 组名 文件名来修改文件所在的组。 - **其它组** @@ -339,12 +344,18 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过 - 查看当前系统的端口使用:netstat -an - **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) -- **`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`:** 做个重开机的模拟(只有纪录并不会真的重开机)。 - +## 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! +**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) diff --git a/docs/system-design/data-communication/RocketMQ-Questions.md b/docs/system-design/data-communication/RocketMQ-Questions.md new file mode 100644 index 00000000000..a41a4035b7b --- /dev/null +++ b/docs/system-design/data-communication/RocketMQ-Questions.md @@ -0,0 +1,214 @@ +本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。 + + +- [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) + + + +# 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 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. 没有使用多个队列(即多个 LinkedBlockingQueue),RocketMQ 的顺序消息是通过生产者和消费者同时使用同一个 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-Trans,Broker 得到后首先更改该消息的 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 发送消息到 Broker,Broker 存储消息信息,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. 阿里中间件团队博客-十分钟入门RocketMQ:http://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/ diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" b/docs/system-design/data-communication/dubbo.md similarity index 91% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" rename to docs/system-design/data-communication/dubbo.md index 5cc6dc1b14a..17a08d8853a 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" +++ b/docs/system-design/data-communication/dubbo.md @@ -44,7 +44,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出 **什么是 RPC?** -RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。 +RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。 **RPC原理是什么?** @@ -81,10 +81,10 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面 我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo: -1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务 +1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务。 2. **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。 3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 -4. **服务降级**——某个服务挂掉之后调用备用服务 +4. **服务降级**——某个服务挂掉之后调用备用服务。 另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。 @@ -98,7 +98,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面 从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。 -另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? +另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? ## 二 Dubbo 的架构 @@ -108,20 +108,20 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面 **上述节点简单说明:** -- **Provider:** 暴露服务的服务提供方 -- **Consumer:** 调用远程服务的服务消费方 -- **Registry:** 服务注册与发现的注册中心 -- **Monitor:** 统计服务的调用次数和调用时间的监控中心 +- **Provider:** 暴露服务的服务提供方 +- **Consumer:** 调用远程服务的服务消费方 +- **Registry:** 服务注册与发现的注册中心 +- **Monitor:** 统计服务的调用次数和调用时间的监控中心 - **Container:** 服务运行容器 **调用关系说明:** 1. 服务容器负责启动,加载,运行服务提供者。 -2. 服务提供者在启动时,向注册中心注册自己提供的服务。 -3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 -4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 -5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 -6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 +2. 服务提供者在启动时,向注册中心注册自己提供的服务。 +3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 +4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 +5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 +6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 **重要知识点总结:** @@ -156,7 +156,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面 - 第七层:**protocol层**,远程调用层,封装rpc调用 - 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步 - 第九层:**transport层**,网络传输层,抽象mina和netty为统一接口 -- 第十层:**serialize层**,数据序列化层。网络传输需要。 +- 第十层:**serialize层**,数据序列化层,网络传输需要 ## 三 Dubbo 的负载均衡策略 @@ -165,7 +165,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面 **先来个官方的解释。** -> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件 +> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。 **上面讲的大家可能不太好理解,再用通俗的话给大家说一下。** diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" b/docs/system-design/data-communication/message-queue.md similarity index 90% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" rename to docs/system-design/data-communication/message-queue.md index e90a129a9a3..2d9bed8bdaa 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" +++ b/docs/system-design/data-communication/message-queue.md @@ -75,7 +75,7 @@ #### 4.1.1 JMS 简介 -  JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 +  JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 **ActiveMQ 就是基于 JMS 规范实现的。** @@ -84,12 +84,16 @@ ①点到点(P2P)模型 ![点到点(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)模型](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 五种不同的消息正文格式 @@ -104,7 +108,7 @@ ### 4.2 AMQP -  ​ AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。 +  ​ AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。 **RabbitMQ 就是基于 AMQP 协议实现的。** diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" b/docs/system-design/data-communication/rabbitmq.md similarity index 97% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" rename to docs/system-design/data-communication/rabbitmq.md index 7b1508f456c..825f71239c6 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" +++ b/docs/system-design/data-communication/rabbitmq.md @@ -123,7 +123,7 @@ direct 类型常用在处理有优先级的任务,根据任务的优先级把 - RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”; - BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串; -- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“#”用于匹配一个单词,“#”用于匹配多规格单词(可以是零个)。 +- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“.”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。 ![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 的时候需要注意 RabbitMQ 和 Erlang 的版本关系,如果不注意的话会导致出错,两者对应关系如下: + +![RabbitMQ 和 Erlang 的版本关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RabbitMQ-Erlang.png) + ### 2.1 安装 erlang **1 下载 erlang 安装包** diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" b/docs/system-design/data-communication/summary.md similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" rename to docs/system-design/data-communication/summary.md diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" b/docs/system-design/framework/ZooKeeper.md similarity index 99% rename from "\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" rename to docs/system-design/framework/ZooKeeper.md index fa6d2b6568f..4b10ca0c71d 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" +++ b/docs/system-design/framework/ZooKeeper.md @@ -47,8 +47,6 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 综上,何必增加那一个不必要的zookeeper呢? - - ## 二 关于 ZooKeeper 的一些重要概念 ### 2.1 重要概念总结 @@ -165,8 +163,8 @@ ZAB协议包括两种基本的模式,分别是 **崩溃恢复和消息广播** 关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章: -- [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/) -- [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/) +- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/) +- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html) 关于如何使用 zookeeper 实现分布式锁,可以查看下面这篇文章: diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" "b/docs/system-design/framework/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" similarity index 98% rename from "\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" rename to "docs/system-design/framework/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" index 401c752f049..eceaf2293c7 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" +++ "b/docs/system-design/framework/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" @@ -26,9 +26,7 @@ ZNode(数据节点)是 ZooKeeper 中数据的最小单元,每个ZNode上 提到 ZooKeeper 数据模型,还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID(Atomic:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)四大特性我在这里就不多说了,相信大家也已经挺腻了。 -在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,**ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。 - - +在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。**对于每一个事务请求,ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。 ### ZNode(数据节点)的结构 diff --git a/docs/system-design/framework/spring/Spring-Design-Patterns.md b/docs/system-design/framework/spring/Spring-Design-Patterns.md new file mode 100644 index 00000000000..51ab91e8406 --- /dev/null +++ b/docs/system-design/framework/spring/Spring-Design-Patterns.md @@ -0,0 +1,352 @@ + + +- [控制反转(IoC)和依赖注入(DI)](#控制反转ioc和依赖注入di) +- [工厂设计模式](#工厂设计模式) +- [单例设计模式](#单例设计模式) +- [代理设计模式](#代理设计模式) + - [代理模式在 AOP 中的应用](#代理模式在-aop-中的应用) + - [Spring AOP 和 AspectJ AOP 有什么区别?](#spring-aop-和-aspectj-aop-有什么区别) +- [模板方法](#模板方法) +- [观察者模式](#观察者模式) + - [Spring 事件驱动模型中的三种角色](#spring-事件驱动模型中的三种角色) + - [事件角色](#事件角色) + - [事件监听者角色](#事件监听者角色) + - [事件发布者角色](#事件发布者角色) + - [Spring 的事件流程总结](#spring-的事件流程总结) +- [适配器模式](#适配器模式) + - [spring AOP中的适配器模式](#spring-aop中的适配器模式) + - [spring MVC中的适配器模式](#spring-mvc中的适配器模式) +- [装饰者模式](#装饰者模式) +- [总结](#总结) +- [参考](#参考) + + + +JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见。我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下,由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。 + +Design Patterns(设计模式) 表示面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看到底有哪些设计模式? + +## 控制反转(IoC)和依赖注入(DI) + +**IoC(Inversion of Control,控制翻转)** 是Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容易管理对象,你只管使用即可),从而降低代码之间的耦合度。**IOC 是一个原则,而不是一个模式,以下模式(但不限于)实现了IoC原则。** + +![ioc-patterns](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ioc-patterns.png) + +**Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。 + +在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。关于Spring IOC 的理解,推荐看这一下知乎的一个回答: ,非常不错。 + +**控制翻转怎么理解呢?** 举个例子:"对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权翻转,这就是控制反转名字的由来。 + +**DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。** + +## 工厂设计模式 + +Spring使用工厂模式可以通过 `BeanFactory` 或 `ApplicationContext` 创建 bean 对象。 + +**两者对比:** + +- `BeanFactory` :延迟注入(使用到某个 bean 的时候才会注入),相比于`BeanFactory` 来说会占用更少的内存,程序启动速度更快。 +- `ApplicationContext` :容器启动的时候,不管你用没用到,一次性创建所有 bean 。`BeanFactory` 仅提供了最基本的依赖注入支持,` ApplicationContext` 扩展了 `BeanFactory` ,除了有`BeanFactory`的功能还有额外更多功能,所以一般开发人员使用` ApplicationContext`会更多。 + +ApplicationContext的三个实现类: + +1. `ClassPathXmlApplication`:把上下文文件当成类路径资源。 +2. `FileSystemXmlApplication`:从文件系统中的 XML 文件载入上下文定义信息。 +3. `XmlWebApplicationContext`:从Web系统中的XML文件载入上下文定义信息。 + +Example: + +```java +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; + +public class App { + public static void main(String[] args) { + ApplicationContext context = new FileSystemXmlApplicationContext( + "C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml"); + + HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext"); + obj.getMsg(); + } +} +``` + +## 单例设计模式 + +在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。 + +**使用单例模式的好处:** + +- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销; +- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。 + +**Spring 中 bean 的默认作用域就是 singleton(单例)的。** 除了 singleton 作用域,Spring 中 bean 还有下面几种作用域: + +- prototype : 每次请求都会创建一个新的 bean 实例。 +- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 +- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。 +- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话 + +**Spring 实现单例的方式:** + +- xml : `` +- 注解:`@Scope(value = "singleton")` + +**Spring 通过 `ConcurrentHashMap` 实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下** + +```java +// 通过 ConcurrentHashMap(线程安全) 实现单例注册表 +private final Map singletonObjects = new ConcurrentHashMap(64); + +public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(beanName, "'beanName' must not be null"); + synchronized (this.singletonObjects) { + // 检查缓存中是否存在实例 + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + //...省略了很多代码 + try { + singletonObject = singletonFactory.getObject(); + } + //...省略了很多代码 + // 如果实例对象在不存在,我们注册到单例注册表中。 + addSingleton(beanName, singletonObject); + } + return (singletonObject != NULL_OBJECT ? singletonObject : null); + } + } + //将对象添加到单例注册表 + protected void addSingleton(String beanName, Object singletonObject) { + synchronized (this.singletonObjects) { + this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); + + } + } +} +``` + +## 代理设计模式 + +### 代理模式在 AOP 中的应用 + +AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。 + +**Spring AOP 就是基于动态代理的**,如果要代理的对象,实现了某个接口,那么Spring AOP会使用**JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用**Cglib** ,这时候Spring AOP会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示: + +![SpringAOPProcess](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/SpringAOPProcess.jpg) + +当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。 + +使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。 + +### Spring AOP 和 AspectJ AOP 有什么区别? + +**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。 + + Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单, + +如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。 + +## 模板方法 + +模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。 + +![模板方法UML图](https://ws1.sinaimg.cn/large/006rNwoDgy1g3a73vdbojj30vo0iwdgc.jpg) + +```java +public abstract class Template { + //这是我们的模板方法 + public final void TemplateMethod(){ + PrimitiveOperation1(); + PrimitiveOperation2(); + PrimitiveOperation3(); + } + + protected void PrimitiveOperation1(){ + //当前类实现 + } + + //被子类实现的方法 + protected abstract void PrimitiveOperation2(); + protected abstract void PrimitiveOperation3(); + +} +public class TemplateImpl extends Template { + + @Override + public void PrimitiveOperation2() { + //当前类实现 + } + + @Override + public void PrimitiveOperation3() { + //当前类实现 + } +} + +``` + +Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。 + +## 观察者模式 + +观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。 + +### Spring 事件驱动模型中的三种角色 + +#### 事件角色 + + `ApplicationEvent` (`org.springframework.context`包下)充当事件的角色,这是一个抽象类,它继承了`java.util.EventObject`并实现了 `java.io.Serializable`接口。 + +Spring 中默认存在以下事件,他们都是对 `ApplicationContextEvent` 的实现(继承自`ApplicationContextEvent`): + +- `ContextStartedEvent`:`ApplicationContext` 启动后触发的事件; +- `ContextStoppedEvent`:`ApplicationContext` 停止后触发的事件; +- `ContextRefreshedEvent`:`ApplicationContext` 初始化或刷新完成后触发的事件; +- `ContextClosedEvent`:`ApplicationContext` 关闭后触发的事件。 + +![ApplicationEvent-Subclass](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ApplicationEvent-Subclass.png) + +#### 事件监听者角色 + +`ApplicationListener` 充当了事件监听者角色,它是一个接口,里面只定义了一个 `onApplicationEvent()`方法来处理`ApplicationEvent`。`ApplicationListener`接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 `ApplicationEvent`就可以了。所以,在 Spring中我们只要实现 `ApplicationListener` 接口实现 `onApplicationEvent()` 方法即可完成监听事件 + +```java +package org.springframework.context; +import java.util.EventListener; +@FunctionalInterface +public interface ApplicationListener extends EventListener { + void onApplicationEvent(E var1); +} +``` + +#### 事件发布者角色 + +`ApplicationEventPublisher` 充当了事件的发布者,它也是一个接口。 + +```java +@FunctionalInterface +public interface ApplicationEventPublisher { + default void publishEvent(ApplicationEvent event) { + this.publishEvent((Object)event); + } + + void publishEvent(Object var1); +} + +``` + +`ApplicationEventPublisher` 接口的`publishEvent()`这个方法在`AbstractApplicationContext`类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过`ApplicationEventMulticaster`来广播出去的。具体内容过多,就不在这里分析了,后面可能会单独写一篇文章提到。 + +### Spring 的事件流程总结 + +1. 定义一个事件: 实现一个继承自 `ApplicationEvent`,并且写相应的构造函数; +2. 定义一个事件监听者:实现 `ApplicationListener` 接口,重写 `onApplicationEvent()` 方法; +3. 使用事件发布者发布消息: 可以通过 `ApplicationEventPublisher ` 的 `publishEvent()` 方法发布消息。 + +Example: + +```java +// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数 +public class DemoEvent extends ApplicationEvent{ + private static final long serialVersionUID = 1L; + + private String message; + + public DemoEvent(Object source,String message){ + super(source); + this.message = message; + } + + public String getMessage() { + return message; + } + + +// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法; +@Component +public class DemoListener implements ApplicationListener{ + + //使用onApplicationEvent接收消息 + @Override + public void onApplicationEvent(DemoEvent event) { + String msg = event.getMessage(); + System.out.println("接收到的信息是:"+msg); + } + +} +// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。 +@Component +public class DemoPublisher { + + @Autowired + ApplicationContext applicationContext; + + public void publish(String message){ + //发布事件 + applicationContext.publishEvent(new DemoEvent(this, message)); + } +} + +``` + +当调用 `DemoPublisher ` 的 `publish()` 方法的时候,比如 `demoPublisher.publish("你好")` ,控制台就会打印出:`接收到的信息是:你好` 。 + +## 适配器模式 + +适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。 + +### spring AOP中的适配器模式 + +我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是`AdvisorAdapter ` 。Advice 常用的类型有:`BeforeAdvice`(目标方法调用前,前置通知)、`AfterAdvice`(目标方法调用后,后置通知)、`AfterReturningAdvice`(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:`MethodBeforeAdviceInterceptor`、`AfterReturningAdviceAdapter`、`AfterReturningAdviceInterceptor`。Spring预定义的通知要通过对应的适配器,适配成 `MethodInterceptor`接口(方法拦截器)类型的对象(如:`MethodBeforeAdviceInterceptor` 负责适配 `MethodBeforeAdvice`)。 + +### spring MVC中的适配器模式 + +在Spring MVC中,`DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由`HandlerAdapter` 适配器处理。`HandlerAdapter` 作为期望接口,具体的适配器实现类用于对目标类进行适配,`Controller` 作为需要适配的类。 + +**为什么要在 Spring MVC 中使用适配器模式?** Spring MVC 中的 `Controller` 种类众多,不同类型的 `Controller` 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,`DispatcherServlet` 直接获取对应类型的 `Controller`,需要的自行来判断,像下面这段代码一样: + +```java +if(mappedHandler.getHandler() instanceof MultiActionController){ + ((MultiActionController)mappedHandler.getHandler()).xxx +}else if(mappedHandler.getHandler() instanceof XXX){ + ... +}else if(...){ + ... +} +``` + +假如我们再增加一个 `Controller`类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。 + +## 装饰者模式 + +装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 `InputStream`家族,`InputStream` 类下有 `FileInputStream` (读取文件)、`BufferedInputStream` (增加缓存,使读取文件速度大大提升)等子类都在不修改`InputStream` 代码的情况下扩展了它的功能。 + +![装饰者模式示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Decorator.jpg) + +Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式(这一点我自己还没太理解具体原理)。Spring 中用到的包装器模式在类名上含有 `Wrapper`或者 `Decorator`。这些类基本上都是动态地给一个对象添加一些额外的职责 + +## 总结 + +Spring 框架中用到了哪些设计模式? + +- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。 +- **代理设计模式** : Spring AOP 功能的实现。 +- **单例设计模式** : Spring 中的 Bean 默认都是单例的。 +- **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 +- **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 +- **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。 +- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。 +- ...... + +## 参考 + +- 《Spring技术内幕》 +- +- +- +- +- +- + diff --git a/docs/system-design/framework/spring/Spring.md b/docs/system-design/framework/spring/Spring.md new file mode 100644 index 00000000000..d6d073c2f59 --- /dev/null +++ b/docs/system-design/framework/spring/Spring.md @@ -0,0 +1,82 @@ + + +## Spring相关教程/资料 + +### 官网相关 + +- [Spring官网](https://spring.io/)、[Spring系列主要项目](https://spring.io/projects)、[Spring官网指南](https://spring.io/guides)、[官方文档](https://spring.io/docs/reference) +- [spring-framework-reference](https://docs.spring.io/spring/docs/5.0.14.RELEASE/spring-framework-reference/index.html) +- [Spring Framework 4.3.17.RELEASE API](https://docs.spring.io/spring/docs/4.3.17.RELEASE/javadoc-api/) + +## 系统学习教程 + +### 文档 + +- [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html) +- [Spring W3Cschool教程 ](https://www.w3cschool.cn/wkspring/f6pk1ic8.html) + +### 视频 + +- [网易云课堂——58集精通java教程Spring框架开发](http://study.163.com/course/courseMain.htm?courseId=1004475015#/courseDetail?tab=1&35) +- [慕课网相关视频](https://www.imooc.com/) + +- **黑马视频和尚硅谷视频(非常推荐):** 微信公众号:“**JavaGuide**”后台回复关键字 “**1**” 免费领取。 + + +## 面试必备知识点 + +### SpringAOP,IOC实现原理 + +AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器?这些东西都是经常会被问到的。 + +推荐阅读: + +- [自己动手实现的 Spring IOC 和 AOP - 上篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/) + +- [自己动手实现的 Spring IOC 和 AOP - 下篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-下篇/) + +### AOP + +AOP思想的实现一般都是基于 **代理模式** ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,**JDK动态代理模式只能代理接口而不能代理类**。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。 + +- 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类; +- 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。 + +推荐阅读: + +- [静态代理、JDK动态代理、CGLIB动态代理讲解](http://www.cnblogs.com/puyangsky/p/6218925.html) :我们知道AOP思想的实现一般都是基于 **代理模式** ,所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。 +- [Spring AOP 入门](https://juejin.im/post/5aa7818af265da23844040c6) :带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念:5种类型的通知(Before,After,After-returning,After-throwing,Around);Spring中对AOP的支持:AOP思想的实现一般都是基于代理模式,在Java中一般采用JDK动态代理模式,Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理, +- [Spring AOP 基于AspectJ注解如何实现AOP](https://juejin.im/post/5a55af9e518825734d14813f) : **AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)**,可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易。Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。**Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器**。 Spring AOP虽然是使用了那一套注解,其实实现AOP的底层是使用了动态代理(JDK或者CGLib)来动态植入。至于AspectJ的静态植入,不是本文重点,所以只提一提。 +- [探秘Spring AOP(慕课网视频,很不错)](https://www.imooc.com/learn/869):慕课网视频,讲解的很不错,详细且深入 +- [spring源码剖析(六)AOP实现原理剖析](https://blog.csdn.net/fighterandknight/article/details/51209822) :通过源码分析Spring AOP的原理 + +### IOC + +Spring IOC的初始化过程: +![Spring IOC的初始化过程](https://user-gold-cdn.xitu.io/2018/5/22/16387903ee72c831?w=709&h=56&f=png&s=4673) + +- [[Spring框架]Spring IOC的原理及详解。](https://www.cnblogs.com/wang-meng/p/5597490.html) + +- [Spring IOC核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/) :比较简短,推荐阅读。 +- [Spring IOC 容器源码分析](https://javadoop.com/post/spring-ioc) :强烈推荐,内容详尽,而且便于阅读。 + +## Spring事务管理 + +- [可能是最漂亮的Spring事务管理详解](https://juejin.im/post/5b00c52ef265da0b95276091) +- [Spring编程式和声明式事务实例讲解](https://juejin.im/post/5b010f27518825426539ba38) + +### Spring单例与线程安全 + +- [Spring框架中的单例模式(源码解读)](http://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html):单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时,使用了 多重判断加锁 的单例模式。 + +### Spring源码阅读 + +阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 + + - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md) +- [spring-aop](https://github.com/seaswalker/Spring/blob/master/note/spring-aop.md) +- [spring-context](https://github.com/seaswalker/Spring/blob/master/note/spring-context.md) +- [spring-task](https://github.com/seaswalker/Spring/blob/master/note/spring-task.md) +- [spring-transaction](https://github.com/seaswalker/Spring/blob/master/note/spring-transaction.md) +- [spring-mvc](https://github.com/seaswalker/Spring/blob/master/note/spring-mvc.md) +- [guava-cache](https://github.com/seaswalker/Spring/blob/master/note/guava-cache.md) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" b/docs/system-design/framework/spring/SpringBean.md similarity index 100% rename from "\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" rename to docs/system-design/framework/spring/SpringBean.md diff --git a/docs/system-design/framework/spring/SpringInterviewQuestions.md b/docs/system-design/framework/spring/SpringInterviewQuestions.md new file mode 100644 index 00000000000..3f97330b012 --- /dev/null +++ b/docs/system-design/framework/spring/SpringInterviewQuestions.md @@ -0,0 +1,291 @@ + + +- [什么是 Spring 框架?](#什么是-spring-框架) +- [列举一些重要的Spring模块?](#列举一些重要的spring模块) +- [谈谈自己对于 Spring IoC 和 AOP 的理解](#谈谈自己对于-spring-ioc-和-aop-的理解) +- [Spring AOP 和 AspectJ AOP 有什么区别?](#spring-aop-和-aspectj-aop-有什么区别) +- [Spring 中的 bean 的作用域有哪些?](#spring-中的-bean-的作用域有哪些) +- [Spring 中的单例 bean 的线程安全问题了解吗?](#spring-中的单例-bean-的线程安全问题了解吗) +- [Spring 中的 bean 生命周期?](#spring-中的-bean-生命周期) +- [说说自己对于 Spring MVC 了解?](#说说自己对于-spring-mvc-了解) +- [SpringMVC 工作原理了解吗?](#springmvc-工作原理了解吗) +- [Spring 框架中用到了哪些设计模式?](#spring-框架中用到了哪些设计模式) +- [@Component 和 @Bean 的区别是什么?](#component-和-bean-的区别是什么) +- [将一个类声明为Spring的 bean 的注解有哪些?](#将一个类声明为spring的-bean-的注解有哪些) +- [Spring 管理事务的方式有几种?](#spring-管理事务的方式有几种) +- [Spring 事务中的隔离级别有哪几种?](#spring-事务中的隔离级别有哪几种) +- [Spring 事务中哪几种事务传播行为?](#spring-事务中哪几种事务传播行为) +- [参考](#参考) + + + +这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!这篇文章整理了挺长时间,下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很汗,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。 + +## 什么是 Spring 框架? + +Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:。 + +我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。 + +Spring 官网列出的 Spring 的 6 个特征: + +- **核心技术** :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。 +- **测试** :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。 +- **数据访问** :事务,DAO支持,JDBC,ORM,编组XML。 +- **Web支持** : Spring MVC和Spring WebFlux Web框架。 +- **集成** :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 +- **语言** :Kotlin,Groovy,动态语言。 + +## 列举一些重要的Spring模块? + +下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。 + +![Spring主要模块](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Spring主要模块.png) + +- **Spring Core:** 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。 +- **Spring Aspects** : 该模块为与AspectJ的集成提供支持。 +- **Spring AOP** :提供了面向切面的编程实现。 +- **Spring JDBC** : Java数据库连接。 +- **Spring JMS** :Java消息服务。 +- **Spring ORM** : 用于支持Hibernate等ORM工具。 +- **Spring Web** : 为创建Web应用程序提供支持。 +- **Spring Test** : 提供了对 JUnit 和 TestNG 测试的支持。 + +## 谈谈自己对于 Spring IoC 和 AOP 的理解 + +### IoC + +IoC(Inverse of Control:控制反转)是一种**设计思想**,就是 **将原本在程序中手动创建对象的控制权,交由Spring框架来管理。** IoC 在其他语言中也有应用,并非 Spirng 特有。 **IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。** + +将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 **IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 + +Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。 + +推荐阅读:https://www.zhihu.com/question/23277575/answer/169698662 + +**Spring IoC的初始化过程:** +![Spring IoC的初始化过程](https://user-gold-cdn.xitu.io/2018/9/22/165fea36b569d4f4?w=709&h=56&f=png&s=4673) + +IoC源码阅读 + +- https://javadoop.com/post/spring-ioc + +### AOP + +AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。 + +**Spring AOP就是基于动态代理的**,如果要代理的对象,实现了某个接口,那么Spring AOP会使用**JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用**Cglib** ,这时候Spring AOP会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示: + +![SpringAOPProcess](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/SpringAOPProcess.jpg) + +当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。 + +使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。 + +## Spring AOP 和 AspectJ AOP 有什么区别? + +**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。 + + Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单, + +如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。 + +## Spring 中的 bean 的作用域有哪些? + +- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。 +- prototype : 每次请求都会创建一个新的 bean 实例。 +- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 +- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。 +- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话 + +## Spring 中的单例 bean 的线程安全问题了解吗? + +大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 + +常见的有两种解决办法: + +1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。 + +2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。 + +## Spring 中的 bean 生命周期? + +这部分网上有很多文章都讲到了,下面的内容整理自: ,除了这篇文章,再推荐一篇很不错的文章 : 。 + +- Bean 容器找到配置文件中 Spring Bean 的定义。 +- Bean 容器利用 Java Reflection API 创建一个Bean的实例。 +- 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。 +- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。 +- 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。 +- 如果Bean实现了 `BeanFactoryAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoade` r对象的实例。 +- 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。 +- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法 +- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。 +- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。 +- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法 +- 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。 +- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。 + +图示: + +![Spring Bean 生命周期](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg) + +与之比较类似的中文版本: + +![Spring Bean 生命周期](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg) + +## 说说自己对于 Spring MVC 了解? + +谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。 + +- **Model1 时代** : 很多学 Java 后端比较晚的朋友可能并没有接触过 Model1 模式下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。这个模式下 JSP 即是控制层又是表现层。显而易见,这种模式存在很多问题。比如①将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;②前端和后端相互依赖,难以进行测试并且开发效率极低; +- **Model2 时代** :学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View,)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。Model:系统涉及的数据,也就是 dao 和 bean。View:展示模型中的数据,只是用来展示。Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。 + +Model2 模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。于是很多JavaWeb开发相关的 MVC 框架营运而生比如Struts2,但是 Struts2 比较笨重。随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。 + +MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。 + +**Spring MVC 的简单原理图如下:** + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/60679444.jpg) + +## SpringMVC 工作原理了解吗? + +**原理如下图所示:** +![SpringMVC运行原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) + +上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 `DispatcherServlet` 的作用是接收请求,响应结果。 + +**流程说明(重要):** + +1. 客户端(浏览器)发送请求,直接请求到 `DispatcherServlet`。 +2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。 +3. 解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由 `HandlerAdapter` 适配器处理。 +4. `HandlerAdapter` 会根据 `Handler `来调用真正的处理器开处理请求,并处理相应的业务逻辑。 +5. 处理器处理完业务后,会返回一个 `ModelAndView` 对象,`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。 +6. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。 +7. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。 +8. 把 `View` 返回给请求者(浏览器) + +## Spring 框架中用到了哪些设计模式? + +关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章[《面试官:“谈谈Spring中都用到了那些设计模式?”。》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=255050878&lang=zh_CN#rd) 。 + +- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。 +- **代理设计模式** : Spring AOP 功能的实现。 +- **单例设计模式** : Spring 中的 Bean 默认都是单例的。 +- **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 +- **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 +- **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。 +- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。 +- ...... + +## @Component 和 @Bean 的区别是什么? + +1. 作用对象不同: `@Component` 注解作用于类,而`@Bean`注解作用于方法。 +2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了Spring这是某个类的示例,当我需要用它的时候还给我。 +3. `@Bean` 注解比 `Component` 注解的自定义性更强,而且很多地方我们只能通过 `@Bean` 注解来注册bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean`来实现。 + +`@Bean`注解使用示例: + +```java +@Configuration +public class AppConfig { + @Bean + public TransferService transferService() { + return new TransferServiceImpl(); + } + +} +``` + + 上面的代码相当于下面的 xml 配置 + +```xml + + + +``` + +下面这个例子是通过 `@Component` 无法实现的。 + +```java +@Bean +public OneService getService(status) { + case (status) { + when 1: + return new serviceImpl1(); + when 2: + return new serviceImpl2(); + when 3: + return new serviceImpl3(); + } +} +``` + +## 将一个类声明为Spring的 bean 的注解有哪些? + +我们一般使用 `@Autowired` 注解自动装配 bean,要想把类标识成可用于 `@Autowired` 注解自动装配的 bean 的类,采用以下注解可实现: + +- `@Component` :通用的注解,可标注任意类为 `Spring` 组件。如果一个Bean不知道属于哪个层,可以使用`@Component` 注解标注。 +- `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。 +- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。 +- `@Controller` : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。 + +## Spring 管理事务的方式有几种? + +1. 编程式事务,在代码中硬编码。(不推荐使用) +2. 声明式事务,在配置文件中配置(推荐使用) + +**声明式事务又分为两种:** + +1. 基于XML的声明式事务 +2. 基于注解的声明式事务 + +## Spring 事务中的隔离级别有哪几种? + +**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的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 + +## Spring 事务中哪几种事务传播行为? + +**支持当前事务的情况:** + +- **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。 + +## 参考 + +- 《Spring 技术内幕》 +- +- +- +- +- +- + +### 公众号 + +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 + +**《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) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" b/docs/system-design/framework/spring/SpringMVC-Principle.md similarity index 100% rename from "\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" rename to docs/system-design/framework/spring/SpringMVC-Principle.md diff --git "a/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" "b/docs/system-design/website-architecture/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" similarity index 100% rename from "\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" rename to "docs/system-design/website-architecture/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" diff --git "a/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" "b/docs/system-design/website-architecture/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" similarity index 97% rename from "\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" rename to "docs/system-design/website-architecture/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" index 415661fdeaa..c5d585c7275 100644 --- "a/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" +++ "b/docs/system-design/website-architecture/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" @@ -24,7 +24,7 @@ ### 1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量 1. **提高硬件能力、增加系统服务器**。(当服务器增加到某个程度的时候系统所能提供的并发访问量几乎不变,所以不能根本解决问题) -2. **使用缓存**(本地缓存:本地可以使用JDK自带的 Map、Guava Cache.分布式缓存:Redis、Memcache.本地缓存不适用于提高系统并发量,一般是用处用在程序中。比如Spring是如何实现单例的呢?大家如果看过源码的话,应该知道,Spiring把已经初始过的变量放在一个Map中,下次再要使用这个变量的时候,先判断Map中有没有,这也就是系统中常见的单例模式的实现。) +2. **使用缓存**(本地缓存:本地可以使用JDK自带的 Map、Guava Cache.分布式缓存:Redis、Memcache.本地缓存不适用于提高系统并发量,一般是用处用在程序中。比如Spring是如何实现单例的呢?大家如果看过源码的话,应该知道,S把已经初始过的变量放在一个Map中,下次再要使用这个变量的时候,先判断Map中有没有,这也就是系统中常见的单例模式的实现。) 3. **消息队列** (解耦+削峰+异步) 4. **采用分布式开发** (不同的服务部署在不同的机器节点上,并且一个服务也可以部署在多台机器上,然后利用 Nginx 负载均衡访问。这样就解决了单点部署(All In)的缺点,大大提高的系统并发量) 5. **数据库分库(读写分离)、分表(水平分表、垂直分表)** @@ -77,7 +77,7 @@ Dubbo 与 Spring Cloud 并不是竞争关系,Dubbo 作为成熟的 RPC 框架 性能测试指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。性能测试是总称,通常细分为: 1. **基准测试:** 在给系统施加较低压力时,查看系统的运行状况并记录相关数做为基础参考 -2. **负载测试:**是指对系统不断地增加压力或增加一定压力下的持续时间,直到系统的某项或多项性能指标达到安全临界值,例如某种资源已经达到饱和状态等 。此时继续加压,系统处理能力会下降。 +2. **负载测试:** 是指对系统不断地增加压力或增加一定压力下的持续时间,直到系统的某项或多项性能指标达到安全临界值,例如某种资源已经达到饱和状态等 。此时继续加压,系统处理能力会下降。 3. **压力测试:** 超过安全负载情况下,不断施加压力(增加并发请求),直到系统崩溃或无法处理任何请求,依此获得系统最大压力承受能力。 4. **稳定性测试:** 被测试系统在特定硬件、软件、网络环境下,加载一定业务压力(模拟生产环境不同时间点、不均匀请求,呈波浪特性)运行一段较长时间,以此检测系统是否稳定。 @@ -100,7 +100,7 @@ Dubbo 与 Spring Cloud 并不是竞争关系,Dubbo 作为成熟的 RPC 框架 当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: -1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; +1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内; 2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; 3. **垂直分区:** **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。**简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015)**垂直拆分的优点:** 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。**垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; 4. **水平分区:** **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119)水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217.md" "b/docs/system-design/website-architecture/\345\210\206\345\270\203\345\274\217.md" similarity index 100% rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217.md" rename to "docs/system-design/website-architecture/\345\210\206\345\270\203\345\274\217.md" diff --git "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/system-design/\350\256\276\350\256\241\346\250\241\345\274\217.md" similarity index 97% rename from "Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" rename to "docs/system-design/\350\256\276\350\256\241\346\250\241\345\274\217.md" index e3e95860529..6af52e416f3 100644 --- "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/docs/system-design/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -67,11 +67,11 @@ - [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) - [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) - **命令模式:** 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。 -- **解释器模式:** +- **解释器模式:** - **迭代器模式:** -- **中介者模式:** +- **中介者模式:** - **备忘录模式:** -- **观察者模式:** +- **观察者模式:** - **状态模式:** - **策略模式:** diff --git a/docs/tools/Docker-Image.md b/docs/tools/Docker-Image.md new file mode 100644 index 00000000000..c69931acb0b --- /dev/null +++ b/docs/tools/Docker-Image.md @@ -0,0 +1,561 @@ +镜像作为 Docker 三大核心概念中,最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌握的。本文将带您一步一步,图文并重,上手操作来学习它。 + +### 目录 + + + +- [一 Docker 下载镜像](#一-docker-下载镜像) + - [1.1 下载镜像](#11-下载镜像) + - [1.2 验证](#12-验证) + - [1.3 下载镜像相关细节](#13-下载镜像相关细节) + - [1.4 PULL 子命令](#14-pull-子命令) +- [二 Docker 查看镜像信息](#二-docker-查看镜像信息) + - [2.1 images 命令列出镜像](#21-images-命令列出镜像) + - [2.2 使用 tag 命令为镜像添加标签](#22-使用-tag-命令为镜像添加标签) + - [2.3 使用 inspect 命令查看镜像详细信息](#23-使用-inspect-命令查看镜像详细信息) + - [2.4 使用 history 命令查看镜像历史](#24-使用-history-命令查看镜像历史) +- [三 Docker 搜索镜像](#三-docker-搜索镜像) + - [3.1 search 命令](#31-search-命令) + - [3.2 search 子命令](#32-search-子命令) +- [四 Docker 删除镜像](#四-docker-删除镜像) + - [4.1 通过标签删除镜像](#41-通过标签删除镜像) + - [4.2 通过 ID 删除镜像](#42-通过-id-删除镜像) + - [4.3 删除镜像的限制](#43-删除镜像的限制) + - [4.4 清理镜像](#44-清理镜像) +- [五 Docker 创建镜像](#五-docker-创建镜像) + - [5.1 基于已有的镜像创建](#51-基于已有的镜像创建) + - [5.2 基于 Dockerfile 创建](#52-基于-dockerfile-创建) +- [六 Docker 导出&加载镜像](#六-docker-导出加载镜像) + - [6.1 导出镜像](#61-导出镜像) + - [6.2 加载镜像](#62-加载镜像) +- [七 Docker 上传镜像](#七-docker-上传镜像) + - [7.1 获取 Docker ID](#71-获取-docker-id) + - [7.2 创建镜像仓库](#72-创建镜像仓库) + - [7.3 上传镜像](#73-上传镜像) +- [八 总结](#八-总结) + + + +## 一 Docker 下载镜像 + +如果我们想要在本地运行容器,就必须保证本地存在对应的镜像。所以,第一步,我们需要下载镜像。当我们尝试下载镜像时,Docker 会尝试先从默认的镜像仓库(默认使用 Docker Hub 公共仓库)去下载,当然了,用户也可以自定义配置想要下载的镜像仓库。 + +### 1.1 下载镜像 + +镜像是运行容器的前提,我们可以使用 `docker pull[IMAGE_NAME]:[TAG]`命令来下载镜像,其中 `IMAGE_NAME` 表示的是镜像的名称,而 `TAG` 是镜像的标签,也就是说我们需要通过 “**镜像 + 标签**” 的方式来下载镜像。 + +**注意:** 您也可以不显式地指定 TAG, 它会默认下载 latest 标签,也就是下载仓库中最新版本的镜像。这里并不推荐您下载 latest 标签,因为该镜像的内容会跟踪镜像的最新版本,并随之变化,所以它是不稳定的。在生产环境中,可能会出现莫名其妙的 bug, 推荐您最好还是显示的指定具体的 TAG。 + +举个例子,如我们想要下载一个 Mysql 5.7 镜像,可以通过命令来下载: + +``` +docker pull mysql:5.7 +``` + +会看到控制台输出内容如下: + +![Docker 下载镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPaaGfBRjupMm7dg74uP4a4jx1QeMY7PAHmqjFRYMkbsiaY0hZV3371uw/?wx_fmt=jpeg) + +**注意:** 由于官方 DockerHub 仓库服务器在国外,下载速度较慢,所以我将仓库的地址更改成了国内的 `docker.io` 的镜像仓库,所以在上图中,镜像前面会有 `docker.io` 出现。 + +当有 **Downloaded** 字符串输出的时候,说明下载成功了!! + +### 1.2 验证 + +让我们来验证一下,本地是否存在 Mysql5.7 的镜像,运行命令: + +``` +docker images +``` + +![验证本地镜像是否存在](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTP0QbqA4STORwmkmv1OcZM8n8BiarCrWxGiayXFdMVGlvGjv7NUFNDWpHQ/?wx_fmt=jpeg) + +可以看到本地的确存在该镜像,确实是下载成功了! + +### 1.3 下载镜像相关细节 + +再说说上面下载镜像的过程: + +![Docker 镜像下载](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPKgsXuhayxmKjVSb5kkEB0ffKjUq2TB0FLA0Tqu7kIibRiawRP0afUcVA/?wx_fmt=jpeg) + +通过下载过程,可以看到,一个镜像一般是由多个层( `layer`) 组成,类似 `f7e2b70d04ae`这样的串表示层的唯一 ID(实际上完整的 ID 包括了 256 个 bit, 64 个十六进制字符组成)。 + +**您可能会想,如果多个不同的镜像中,同时包含了同一个层( layer),这样重复下载,岂不是导致了存储空间的浪费么?** + +实际上,Docker 并不会这么傻会去下载重复的层( `layer`),Docker 在下载之前,会去检测本地是否会有同样 ID 的层,如果本地已经存在了,就直接使用本地的就好了。 + +**另一个问题,不同仓库中,可能也会存在镜像重名的情况发生, 这种情况咋办?** + +严格意义上,我们在使用 `docker pull` 命令时,还需要在镜像前面指定仓库地址( `Registry`), 如果不指定,则 Docker 会使用您默认配置的仓库地址。例如上面,由于我配置的是国内 `docker.io` 的仓库地址,我在 `pull` 的时候,docker 会默认为我加上 `docker.io/library` 的前缀。 + +如:当我执行 `docker pull mysql:5.7` 命令时,实际上相当于 `docker pull docker.io/mysql:5.7`,如果您未自定义配置仓库,则默认在下载的时候,会在镜像前面加上 DockerHub 的地址。 + +Docker 通过前缀地址的不同,来保证不同仓库中,重名镜像的唯一性。 + +### 1.4 PULL 子命令 + +命令行中输入: + +``` +docker pull --help +``` + +会得到如下信息: + +``` +[root@iZbp1j8y1bab0djl9gdp33Z ~]# docker pull --help +Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST] +Pull an image or a repository from a registry +Options: -a, --all-tags Download all tagged images in the repository --disable-content-trust Skip image verification (default true) --help Print usage +``` + +我们可以看到主要支持的子命令有: + +1. `-a,--all-tags=true|false`: 是否获取仓库中所有镜像,默认为否; +2. `--disable-content-trust`: 跳过镜像内容的校验,默认为 true; + +## 二 Docker 查看镜像信息 + +### 2.1 images 命令列出镜像 + +通过使用如下两个命令,列出本机已有的镜像: + +``` +docker images +``` + +或: + +``` +docker image ls +``` + +如下图所示: + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPo7bVaeYBiajiaZdfIN4J4D10WJ04W2zicZouiceKlDUbbte216HpCgErmQ/?wx_fmt=jpeg) + +对上述红色标注的字段做一下解释: + +- **REPOSITORY**: 来自于哪个仓库; +- **TAG**: 镜像的标签信息,比如 5.7、latest 表示不同的版本信息; +- **IMAGE ID**: 镜像的 ID, 如果您看到两个 ID 完全相同,那么实际上,它们指向的是同一个镜像,只是标签名称不同罢了; +- **CREATED**: 镜像最后的更新时间; +- **SIZE**: 镜像的大小,优秀的镜像一般体积都比较小,这也是我更倾向于使用轻量级的 alpine 版本的原因; + +> 注意:图中的镜像大小信息只是逻辑上的大小信息,因为一个镜像是由多个镜像层( `layer`)组成的,而相同的镜像层本地只会存储一份,所以,真实情况下,占用的物理存储空间大小,可能会小于逻辑大小。 + +### 2.2 使用 tag 命令为镜像添加标签 + +通常情况下,为了方便在后续工作中,快速地找到某个镜像,我们可以使用 `docker tag` 命令,为本地镜像添加一个新的标签。如下图所示: + +![Docker tag 添加标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPD2huWAicJUMBNZhf56OHXa0KjiaF2XcUvxyMm3mssibKvKa5UayFcD1WQ/?wx_fmt=jpeg) + +为 `docker.io/mysql` 镜像,添加新的镜像标签 `allen_mysql:5.7`。然后使用 `docker images` 命令,查看本地镜像: + +![Docker tag 添加标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPCDq6MvlhsiawBwhNn7OEwJassCnwjibP4gWwqAvG3xow9LwFf2ticUsdg/?wx_fmt=jpeg) + +可以看到,本地多了一个 `allen_mysql:5.7` 的镜像。细心的你一定还会发现, `allen_mysql:5.7` 和 `docker.io/mysql:5.7` 的镜像 ID 是一模一样的,说明它们是同一个镜像,只是别名不同而已。 + +`docker tag` 命令功能更像是, 为指定镜像添加快捷方式一样。 + +### 2.3 使用 inspect 命令查看镜像详细信息 + +通过 `docker inspect` 命令,我们可以获取镜像的详细信息,其中,包括创建者,各层的数字摘要等。 + +``` +docker inspect docker.io/mysql:5.7 +``` + +![Docker inspect 查看镜像详细信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPzL9a3cEkXSh4uuSSwia4pibNjT4dDV7t1AgFuJkrBAq3CkU2WWorEFdg/?wx_fmt=jpeg) + +`docker inspect` 返回的是 `JSON` 格式的信息,如果您想获取其中指定的一项内容,可以通过 `-f` 来指定,如获取镜像大小: + +``` +docker inspect -f {{".Size"}} docker.io/mysql:5.7 +``` + +![Docker inspect 查看镜像详细信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPsa7t1OSDhWgFxEzCdfyNaOduGmsz7xJEobUZFibzw5UFxmKvrcmwGNA/?wx_fmt=jpeg) + +### 2.4 使用 history 命令查看镜像历史 + +前面的小节中,我们知道了,一个镜像是由多个层(layer)组成的,那么,我们要如何知道各个层的具体内容呢? + +通过 `docker history` 命令,可以列出各个层(layer)的创建信息,如我们查看 `docker.io/mysql:5.7` 的各层信息: + +``` +docker history docker.io/mysql:5.7 +``` + +![Docker history 各层信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPQ8F21nmOQ7RLI5ibAgVpcOkBlIRR2kH4B0VlDExiauskHbE0w0HYeE0w/?wx_fmt=jpeg) + +可以看到,上面过长的信息,为了方便展示,后面都省略了,如果您想要看具体信息,可以通过添加 `--no-trunc` 选项,如下面命令: + +``` +docker history --no-trunc docker.io/mysql:5.7 +``` + +## 三 Docker 搜索镜像 + +### 3.1 search 命令 + +您可以通过下面命令进行搜索: + +``` +docker search [option] keyword +``` + +比如,您想搜索仓库中 `mysql` 相关的镜像,可以输入如下命令: + +``` +docker search mysql +``` + +![Docker 搜索镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTP3PBGU7q2NoK6WGSRmcxWs0OMicjeDwTBFvOAbXB5MuaDFPYXAod3NZA/?wx_fmt=jpeg) + +### 3.2 search 子命令 + +命令行输入 `docker search--help`, 输出如下: + +``` +Usage: docker search [OPTIONS] TERM +Search the Docker Hub for images +Options: -f, --filter filter Filter output based on conditions provided --help Print usage --limit int Max number of search results (default 25) --no-index Don't truncate output --no-trunc Don't truncate output +``` + +可以看到 `search` 支持的子命令有: + +- `-f,--filter filter`: 过滤输出的内容; +- `--limitint`:指定搜索内容展示个数; +- `--no-index`: 不截断输出内容; +- `--no-trunc`:不截断输出内容; + +举个列子,比如我们想搜索官方提供的 mysql 镜像,命令如下: + +``` +docker search --filter=is-offical=true mysql +``` + +![Docker 搜索官方镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPup8t50skwCOEX0pwnR9uicvWZWNxc7Vv4slXzIoGLhSPcwDq51xpUGA/?wx_fmt=jpeg) + +再比如,我们想搜索 Stars 数超过 100 的 mysql 镜像: + +``` +docker search --filter=stars=100 mysql +``` + +![Docker 搜索镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLHC3QNcSiaib3u3EM014CpBTPsbeMVBSiaLLINzVmkbG3VtIbr3XJnVbIqWKvS016Yib3WQQmraqlENGA/?wx_fmt=jpeg) + +## 四 Docker 删除镜像 + +### 4.1 通过标签删除镜像 + +通过如下两个都可以删除镜像: + +``` +docker rmi [image] +``` + +或者: + +``` +docker image rm [image] +``` + +支持的子命令如下: + +- `-f,-force`: 强制删除镜像,即便有容器引用该镜像; +- `-no-prune`: 不要删除未带标签的父镜像; + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHjMP3NiaSibZZ0XKTiasurB1giae3nfZvWZibRal7TKfiaAhJicXQfibicqCo5Kw/?wx_fmt=jpeg)Docker 查看镜像信息 + +例如,我们想删除上章节创建的 `allen_mysql:5.7` 镜像,命令如下: + +```shell +docker rmi allen_mysql:5.7 +``` + +![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHfh5oDc3GDzlp7B5oaVRic7hHIzvRicDz1wCbgIBrQvMXK8jYo3yPOl5Q/?wx_fmt=jpeg) + +从上面章节中,我们知道 `allen_mysql:5.7` 和 `docker.io/mysql:5.7` 实际上指向的是同一个镜像,那么,您可以能会有疑问,我删除了 `allen_mysql:5.7`, 会不会将 `docker.io/mysql:5.7` 镜像也给删除了? + +**实际上,当同一个镜像拥有多个标签时,执行 `docker rmi` 命令,只是会删除了该镜像众多标签中,您指定的标签而已,并不会影响原始的那个镜像文件。** + +不信的话,我们可以执行 `docker images` 命令,来看下 `docker.io/mysql:5.7` 镜像还在不在: + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHiciaqjoLKZiaoVZFeLJkfA2TfUKaib2muSNrTJP2Rvicib4ac3gMXPiaBkB9Q/?wx_fmt=jpeg) + +可以看到, `docker.io/mysql:5.7` 镜像依然存在! + +那么,如果某个镜像不存在多个标签,当且仅当只有一个标签时,执行删除命令时,您就要小心了,这会彻底删除镜像。 + +例如,这个时候,我们再执行 `docker rmi docker.io/mysql:5.7` 命令: + +![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHAjqtv8JHovzTCdfIM5fIT5Nia3iaI7wKLo13vQgsWibRR9Y2Fd73V9czg/?wx_fmt=jpeg) + +从上图可以看到,我们已经删除了 `docker.io/mysql:5.7` 镜像的所有文件层。该镜像在本地已不复存在了! + +### 4.2 通过 ID 删除镜像 + +除了通过标签名称来删除镜像,我们还可以通过制定镜像 ID, 来删除镜像,如: + +``` +docker rmi ee7cbd482336 +``` + +一旦制定了通过 ID 来删除镜像,它会先尝试删除所有指向该镜像的标签,然后在删除镜像本身。 + +### 4.3 删除镜像的限制 + +删除镜像很简单,但也不是我们何时何地都能删除的,它存在一些限制条件。 + +当通过该镜像创建的容器未被销毁时,镜像是无法被删除的。为了验证这一点,我们来做个试验。首先,我们通过 `docker pull alpine` 命令,拉取一个最新的 `alpine` 镜像, 然后启动镜像,让其输出 `hello,docker!`: + +![Docker run alpine](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHia157yicRQe5g5ad36peutDlAxuGcWbdxopEwmHXCM7rga80cYj0CguA/?wx_fmt=jpeg) + +接下来,我们来删除这个镜像试试: + +![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHia5wTHrVKT1NPHFZvLicwMicKibG5VHVjEWJOXrPOG4pK5VDwAYMcAYzJg/?wx_fmt=jpeg) + +可以看到提示信息,无法删除该镜像,因为有容器正在引用他!同时,这段信息还告诉我们,除非通过添加 `-f` 子命令,也就是强制删除,才能移除掉该镜像! + +``` +docker rmi -f docker.io/alpine +``` + +但是,我们一般不推荐这样暴力的做法,正确的做法应该是: + +1. 先删除引用这个镜像的容器; +2. 再删除这个镜像; + +也就是,根据上图中提示的,引用该镜像的容器 ID ( `9d59e2278553`), 执行删除命令: + +``` +docker rm 9d59e2278553 +``` + +然后,再执行删除镜像的命令: + +``` +docker rmi 5cb3aa00f899 +``` + +![Docker 删除镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHWibytB1NGVzS1KBMia7sYMNm2eStNd4PicxoYA5CfQficMh4eoJMjtHiacA/?wx_fmt=jpeg)Docker 删除镜像 + +这个时候,就能正常删除了! + +### 4.4 清理镜像 + +我们在使用 Docker 一段时间后,系统一般都会残存一些临时的、没有被使用的镜像文件,可以通过以下命令进行清理: + +``` +docker image prune +``` + +它支持的子命令有: + +- `-a,--all`: 删除所有没有用的镜像,而不仅仅是临时文件; +- `-f,--force`:强制删除镜像文件,无需弹出提示确认; + +## 五 Docker 创建镜像 + +此小节中,您将学习 Docker 如何创建镜像?Docker 创建镜像主要有三种: + +1. 基于已有的镜像创建; +2. 基于 Dockerfile 来创建; +3. 基于本地模板来导入; + +我们将主要介绍常用的 1,2 两种。 + +### 5.1 基于已有的镜像创建 + +通过如下命令来创建: + +``` +docker container commit +``` + +支持的子命令如下: + +- `-a,--author`="": 作者信息; +- `-c,--change`=[]: 可以在提交的时候执行 Dockerfile 指令,如 CMD、ENTRYPOINT、ENV、EXPOSE、LABEL、ONBUILD、USER、VOLUME、WORIR 等; +- `-m,--message`="": 提交信息; +- `-p,--pause`=true: 提交时,暂停容器运行。 + +接下来,基于本地已有的 Ubuntu 镜像,创建一个新的镜像: + +![Docker 创建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHMibkCiaNb1AbTNoQicVKkiaAOIhZO2FsRNbSY0kzqZezVGcfgOibJRD58QQ/?wx_fmt=jpeg) + +首先,让我将它运行起来,并在其中创建一个 test.txt 文件: + +![Docker 创建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHQd7AuibW8ml35Tk90OO15s43CAHQtXx5kYzibP5vtNAwic95qibDza61BQ/?wx_fmt=jpeg) + +命令如下: + +``` +docker run -it docker.io/ubuntu:latest /bin/bashroot@a0a0c8cfec3a:/# touch test.txtroot@a0a0c8cfec3a:/# exit +``` + +创建完 test.txt 文件后,需要记住标注的容器 ID: `a0a0c8cfec3a`, 用它来提交一个新的镜像(**PS: 你也可以通过名称来提交镜像,这里只演示通过 ID 的方式**)。 + +执行命令: + +``` +docker container commit -m "Added test.txt file" -a "Allen" a0a0c8cfec3a test:0.1 +``` + +提交成功后,会返回新创建的镜像 ID 信息,如下图所示: + +![Docker 提交新创建的镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHgX5ks187yqupLWLQnvNuwLGibc6So1xk8OZc6SpXEVB5zDEo6WlxQhw/?wx_fmt=jpeg) + +再次查看本地镜像信息,可以看到新创建的 `test:0.1` 镜像了: + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHibWZE9BBMrgVAzDAbpWibEANicPohJErNVCQpAFMfvKExoLj2EQlIYQ2g/?wx_fmt=jpeg) + +### 5.2 基于 Dockerfile 创建 + +通过 Dockerfile 的方式来创建镜像,是最常见的一种方式了,也是比较推荐的方式。Dockerfile 是一个文本指令文件,它描述了是如何基于一个父镜像,来创建一个新镜像的过程。 + +下面让我们来编写一个简单的 Dockerfile 文件,它描述了基于 Ubuntu 父镜像,安装 Python3 环境的镜像: + +``` +FROM docker.io/ubuntu:latest +LABEL version="1.0" maintainer="Allen " +RUN apt-get update && \ apt-get install -y python3 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* +``` + +创建完成后,通过这个 Dockerfile 文件,来构建新的镜像,执行命令: + +``` +docker image build -t python:3 . +``` + +**注意:** 命令的最后有个点,如果不加的话,会构建不成功 ! + +![Docker 通过 Dockerfile 构建镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHk6rCexCL5PcQqMia6QzvOicMg754BKO3mOibQCfQ6MI7tR1JA2A5ZqI7A/?wx_fmt=jpeg) + +编译成功后,再次查看本地镜像信息,就可以看到新构建的 python:3 镜像了。 + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH0amjBEf1pRGNUS4SbzibupypIebmiarHLotk3s1n2PdaqUPibrEaSoTvQ/?wx_fmt=jpeg) + +## 六 Docker 导出&加载镜像 + +此小节中,您将学习 Docker 如何导出&加载镜像。 + +通常我们会有下面这种需求,需要将镜像分享给别人,这个时候,我们可以将镜像导出成 tar 包,别人直接通过加载这个 tar 包,快速地将镜像引入到本地镜像库。 + +要想使用这两个功能,主要是通过如下两个命令: + +1. `docker save` +2. `docker load` + +### 6.1 导出镜像 + +查看本地镜像如下: + +![Docker 查看镜像信息](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH0amjBEf1pRGNUS4SbzibupypIebmiarHLotk3s1n2PdaqUPibrEaSoTvQ/?wx_fmt=jpeg) + +例如,我们想要将 python:3 镜像导出来,执行命令: + +``` +docker save -o python_3.tar python:3 +``` + +执行成功后,查看当前目录: + +![Docker 导出文件](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHOzOhDgY43hbGSGena4g7YpYREdwD1pzWPanhic1pb0LmFrsNGKAYK8g/?wx_fmt=jpeg)Docker 导出文件 + +可以看到 `python_3.tar` 镜像文件已经生成。接下来,你可以将它通过复制的方式,分享给别人了! + +### 6.2 加载镜像 + +别人拿到了这个 `tar` 包后,要如何导入到本地的镜像库呢? + +通过执行如下命令: + +``` +docker load -i python_3.tar +``` + +或者: + +``` +docker load < python_3.tar +``` + +导入成功后,查看本地镜像信息,你就可以获得别人分享的镜像了!怎么样,是不是很方便呢! + +## 七 Docker 上传镜像 + +我们将以上传到 Docker Hub 上为示例,演示 Docker 如何上传镜像。 + +### 7.1 获取 Docker ID + +想要上传镜像到 Docker Hub 上,首先,我们需要注册 Docker Hub 账号。打开 Docker Hub 网址 https://hub.docker.com,开始注册: + +![Docker Hub 注册账号](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH6JticENibsia3hkfDDuBq7PtOotic7rPK46wFdotM0LUYuyFZbOVUaJoeQ/?wx_fmt=jpeg) + +填写您的 Docker ID (也就是账号),以及密码,Email, 点击继续。 + +接下来,Docker Hub 会发送验证邮件,到您填写的邮箱当中: + +![Docker Hub 验证邮件](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHfedKcp34V4t351TqTfBiaRmwAbHmOnVadHydicp3MtLQnUykQYUV49FA/?wx_fmt=jpeg) + +点击验证即可,接下来,再次返回 Docker Hub 官网,用您刚刚注册的 Docker ID 和密码来登录账号! + +![Docker Hub 登录页面](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHa5I17YSce16BNFOgNayA0iaWYWGJnHWwZUjslrBRyV1jLssDKa7mysA/?wx_fmt=jpeg) + +### 7.2 创建镜像仓库 + +登录成功后,会出现如下页面: + +![欢迎来到 Docker Hub](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHRI3SFiaSl2yuXXO1CLhRDR03mVpTO4jwmljIaZC0KptcW7kmM03Xxicg/?wx_fmt=jpeg) + +选择创建一个镜像仓库: + +![创建 Python 仓库](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHMsc31Uskib3SRM4uZZCqkYwyNJFN8ia4LkAKNZuurAbHJyQ1fib9DKGEw/?wx_fmt=jpeg) + +填写**仓库名称**、**描述信息**、**是否公开后**,点击创建。 + +![仓库镜像展示页](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH688icujdAHnPOcHEaATxupbOn4u7LSKEBKoDWb1dPISiaP757VBibdwGQ/?wx_fmt=jpeg)仓库镜像展示页 + +我们看到,仓库已经创建成功了,但是里面还没有任何镜像,接下来开始上传镜像,到此新创建的仓库中。 + +### 7.3 上传镜像 + +进入命令行,**用我们刚刚获取的 Docker ID 以及密码登录**,执行命令: + +``` +docker login +``` + +![命令行登录 Docker ID](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHsJNOaXDpy3C4vu3xPOQUA9XfYFiasZOs69PLOxpUSiaGvEicYib3WKm88Q/?wx_fmt=jpeg)命令行登录 Docker ID + +登录成功后,我们开始准备上传本地的 `python:3` 镜像: + +![python:3 镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHNNakMbCH4TFQTT0iad9Eb2vde8JzfgwIXFLpiaKzeMAYIa7ft22wBMEA/?wx_fmt=jpeg) + +首先,我们对其打一个新的标签,**前缀与我们新创建的 Docker ID 、仓库名保持一致**: + +``` +docker tag python:3 weiwosuoai1991/python:3 +``` + +![python:3 镜像打标签](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHSl8ria0e1kFFWlI1gAwwszV28IkztLv0s9XSZG6ficYIAoO1mfo4LrmQ/?wx_fmt=jpeg) + +查看本地信息,可以看到,标签打成功了。接下开,开始上传!执行命令: + +``` +docker push weiwosuoai1991/python:3 +``` + +![上传 python:3 镜像](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHHeiaV2FtSpv7ewkpyOLEo7e6No42GSbCLqfaxjUicnFhBEq7m4OyIR6GA/?wx_fmt=jpeg) + +上传成功!去 Docker Hub 官网,新创建的仓库的信息页面验证一下,是否真的成功了: + +![仓库镜像展示页](https://mmbiz.qpic.cn/mmbiz_jpg/knmrNHnmCLFSKI1RxMqyrVlVX4GRveHH5ibFBuhibrBn6Xe9tgxgO7LxtXI9FJ0HtLjvuibJhBqZPyexWY78MmBiag/?wx_fmt=jpeg)仓库镜像展示页 + +大工告成!!! + +## 八 总结 + +本文中,我们着重学习了 Docker 中下载镜像,、查看镜像信息、搜索镜像、删除镜像,、创建镜像、导出&加载镜像以及向 Docker Hub 上传镜像的相关操作。 \ No newline at end of file diff --git a/docs/tools/Docker.md b/docs/tools/Docker.md new file mode 100644 index 00000000000..b7dd4f50ffd --- /dev/null +++ b/docs/tools/Docker.md @@ -0,0 +1,220 @@ +**本文只是对Docker的概念做了较为详细的介绍,并不涉及一些像Docker环境的安装以及Docker的一些常见操作和命令。** + + + +- [一 先从认识容器开始](#一-先从认识容器开始) + - [1.1 什么是容器?](#11-什么是容器) + - [先来看看容器较为官方的解释](#先来看看容器较为官方的解释) + - [再来看看容器较为通俗的解释](#再来看看容器较为通俗的解释) + - [1.2 图解物理机,虚拟机与容器](#12-图解物理机虚拟机与容器) +- [二 再来谈谈 Docker 的一些概念](#二-再来谈谈-docker-的一些概念) + - [2.1 什么是 Docker?](#21-什么是-docker) + - [2.2 Docker 思想](#22-docker-思想) + - [2.3 Docker 容器的特点](#23-docker-容器的特点) + - [2.4 为什么要用 Docker ?](#24-为什么要用-docker-) +- [三 容器 VS 虚拟机](#三-容器-vs-虚拟机) + - [3.1 两者对比图](#31-两者对比图) + - [3.2 容器与虚拟机总结](#32-容器与虚拟机总结) + - [3.3 容器与虚拟机两者是可以共存的](#33-容器与虚拟机两者是可以共存的) +- [四 Docker基本概念](#四-docker基本概念) + - [4.1 镜像(Image):一个特殊的文件系统](#41-镜像image一个特殊的文件系统) + - [4.2 容器(Container):镜像运行时的实体](#42-容器container镜像运行时的实体) + - [4.3仓库(Repository):集中存放镜像文件的地方](#43仓库repository集中存放镜像文件的地方) +- [五 最后谈谈:Build Ship and Run](#五-最后谈谈build-ship-and-run) +- [六 总结](#六-总结) + + + +> **Docker 是世界领先的软件容器平台**,所以想要搞懂Docker的概念我们必须先从容器开始说起。 + +## 一 先从认识容器开始 + +### 1.1 什么是容器? + +#### 先来看看容器较为官方的解释 + +**一句话概括容器:容器就是将软件打包成标准化单元,以用于开发、交付和部署。** + +- **容器镜像是轻量的、可执行的独立软件包** ,包含软件运行所需的所有内容:代码、运行时环境、系统工具、系统库和设置。 +- **容器化软件适用于基于Linux和Windows的应用,在任何环境中都能够始终如一地运行。** +- **容器赋予了软件独立性** ,使其免受外在环境差异(例如,开发和预演环境的差异)的影响,从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。 + +#### 再来看看容器较为通俗的解释 + +**如果需要通俗的描述容器的话,我觉得容器就是一个存放东西的地方,就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。** + +![认识容器](https://user-gold-cdn.xitu.io/2018/6/17/1640cae21c18e404?w=445&h=363&f=png&s=81473) + +### 1.2 图解物理机,虚拟机与容器 +关于虚拟机与容器的对比在后面会详细介绍到,这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解。 + +**物理机** +![物理机](https://user-gold-cdn.xitu.io/2018/6/18/1641129f0ecdf8ff?w=720&h=353&f=jpeg&s=55729) + +**虚拟机:** + +![虚拟机](https://user-gold-cdn.xitu.io/2018/6/18/164112a72a917f4a?w=720&h=321&f=jpeg&s=43096) + +**容器:** + +![容器](https://user-gold-cdn.xitu.io/2018/6/18/164112ac76e6f693?w=720&h=302&f=jpeg&s=41669) + +通过上面这三张抽象图,我们可以大概可以通过类比概括出: **容器虚拟化的是操作系统而不是硬件,容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。** + +--- + +> 相信通过上面的解释大家对于容器这个既陌生又熟悉的概念有了一个初步的认识,下面我们就来谈谈Docker的一些概念。 + +## 二 再来谈谈 Docker 的一些概念 + +![Docker的一些概念](https://user-gold-cdn.xitu.io/2018/6/18/16410734eb1ed373?w=1566&h=696&f=png&s=294564) + +### 2.1 什么是 Docker? + +说实话关于Docker是什么并太好说,下面我通过四点向你说明Docker到底是个什么东西。 + +- **Docker 是世界领先的软件容器平台。** +- **Docker** 使用 Google 公司推出的 **Go 语言** 进行开发实现,基于 **Linux 内核** 的cgroup,namespace,以及AUFS类的**UnionFS**等技术,**对进程进行封装隔离,属于操作系统层面的虚拟化技术。** 由于隔离的进程独立于宿主和其它的隔离的进 +程,因此也称其为容器。**Docke最初实现是基于 LXC.** +- **Docker 能够自动执行重复性任务,例如搭建和配置开发环境,从而解放了开发人员以便他们专注在真正重要的事情上:构建杰出的软件。** +- **用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。** + +![什么是Docker](https://user-gold-cdn.xitu.io/2018/6/18/16411c3946dda762?w=971&h=629&f=jpeg&s=56655) + +### 2.2 Docker 思想 + +- **集装箱** +- **标准化:** ①运输方式 ② 存储方式 ③ API接口 +- **隔离** + +### 2.3 Docker 容器的特点 + +- #### 轻量 + + 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核;它们能够迅速启动,只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的,并共享一些公共文件。这样就能尽量降低磁盘用量,并能更快地下载镜像。 +- #### 标准 + + Docker 容器基于开放式标准,能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。 +- #### 安全 + + Docker 赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker 默认提供最强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台机器。 + +### 2.4 为什么要用 Docker ? + +- **Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题;——一致的运行环境** +- **可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间** +- **避免公用的服务器,资源会容易受到其他用户的影响。——隔离性** +- **善于处理集中爆发的服务器使用压力;——弹性伸缩,快速扩展** +- **可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便** +- **使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署** + +--- + +> 每当说起容器,我们不得不将其与虚拟机做一个比较。就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。 + +## 三 容器 VS 虚拟机 + +  简单来说: **容器和虚拟机具有相似的资源隔离和分配优势,但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。** + +### 3.1 两者对比图 + +  传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便. + +![容器 VS 虚拟机](https://user-gold-cdn.xitu.io/2018/6/17/1640cb4abec9e902?w=1086&h=406&f=png&s=70264) + +### 3.2 容器与虚拟机总结 + +![容器与虚拟机 (VM) 总结](https://user-gold-cdn.xitu.io/2018/6/18/16410aa3b89ae481?w=801&h=206&f=png&s=37241) + +- **容器是一个应用层抽象,用于将代码和依赖资源打包在一起。** **多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行** 。与虚拟机相比, **容器占用的空间较少**(容器镜像大小通常只有几十兆),**瞬间就能完成启动** 。 + +- **虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。** 管理程序允许多个 VM 在一台机器上运行。每个VM都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此 **占用大量空间** 。而且 VM **启动也十分缓慢** 。 + +  通过Docker官网,我们知道了这么多Docker的优势,但是大家也没有必要完全否定虚拟机技术,因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker通常用于隔离不同的应用** ,例如前端,后端以及数据库。 + +### 3.3 容器与虚拟机两者是可以共存的 + +就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。 + +![两者是可以共存的](https://user-gold-cdn.xitu.io/2018/6/17/1640cca26fc38f9e) + +--- + +> Docker中非常重要的三个基本概念,理解了这三个概念,就理解了 Docker 的整个生命周期。 + +## 四 Docker基本概念 + +Docker 包括三个基本概念 + +- **镜像(Image)** +- **容器(Container)** +- **仓库(Repository)** + +理解了这三个概念,就理解了 Docker 的整个生命周期 + +![Docker 包括三个基本概念](https://user-gold-cdn.xitu.io/2018/6/18/164109e4900357a9?w=1024&h=784&f=jpeg&s=127361) + +### 4.1 镜像(Image):一个特殊的文件系统 + +  **操作系统分为内核和用户空间**。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像(Image),就相当于是一个 root 文件系统。 + +  **Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。** 镜像不包含任何动态数据,其内容在构建之后也不会被改变。 + +  Docker 设计时,就充分利用 **Union FS**的技术,将其设计为 **分层存储的架构** 。 镜像实际是由多层文件系统联合组成。 + +  **镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。** 比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。 + +  分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。 + +### 4.2 容器(Container):镜像运行时的实体 + +  镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。 + +  **容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。** + +  **容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。** + +  按照 Docker 最佳实践的要求,**容器不应该向其存储层内写入任何数据** ,容器存储层要保持无状态化。**所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录**,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, **使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。** + + +### 4.3仓库(Repository):集中存放镜像文件的地方 + +  镜像构建完成后,可以很容易的在当前宿主上运行,但是, **如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。** + +  一个 Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:**镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。** + +  通常,**一个仓库会包含同一个软件不同版本的镜像**,而**标签就常用于对应该软件的各个版本** 。我们可以通过```<仓库名>:<标签>```的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。 + +**这里补充一下Docker Registry 公开服务和私有 Docker Registry的概念:** + +  **Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。 + +  最常使用的 Registry 公开服务是官方的 **Docker Hub** ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:[https://hub.docker.com/](https://hub.docker.com/) 。在国内访问**Docker Hub** 可能会比较慢国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [时速云镜像库](https://hub.tenxcloud.com/)、[网易云镜像服务](https://www.163yun.com/product/repo)、[DaoCloud 镜像市场](https://www.daocloud.io/)、[阿里云镜像库](https://www.aliyun.com/product/containerservice?utm_content=se_1292836)等。 + +  除了使用公开服务外,用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。 + +--- + +> Docker的概念基本上已经讲完,最后我们谈谈:Build, Ship, and Run。 + +## 五 最后谈谈:Build Ship and Run +如果你搜索Docker官网,会发现如下的字样:**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么Build, Ship, and Run到底是在干什么呢? + +![build ship run](https://user-gold-cdn.xitu.io/2018/6/18/16411c521e79bd82?w=486&h=255&f=png&s=185903) + +- **Build(构建镜像)** : 镜像就像是集装箱包括文件以及运行环境等等资源。 +- **Ship(运输镜像)** :主机和仓库间运输,这里的仓库就像是超级码头一样。 +- **Run (运行镜像)** :运行的镜像就是一个容器,容器就是运行程序的地方。 + +**Docker 运行过程也就是去仓库把镜像拉到本地,然后用一条命令把镜像运行起来变成容器。所以,我们也常常将Docker称为码头工人或码头装卸工,这和Docker的中文翻译搬运工人如出一辙。** + +## 六 总结 + +本文主要把Docker中的一些常见概念做了详细的阐述,但是并不涉及Docker的安装、镜像的使用、容器的操作等内容。这部分东西,希望读者自己可以通过阅读书籍与官方文档的形式掌握。如果觉得官方文档阅读起来很费力的话,这里推荐一本书籍《Docker技术入门与实战第二版》。 + + + + + + + diff --git a/docs/tools/Git.md b/docs/tools/Git.md new file mode 100644 index 00000000000..37e1f4465c7 --- /dev/null +++ b/docs/tools/Git.md @@ -0,0 +1,258 @@ + + +- [版本控制](#版本控制) + - [什么是版本控制](#什么是版本控制) + - [为什么要版本控制](#为什么要版本控制) + - [本地版本控制系统](#本地版本控制系统) + - [集中化的版本控制系统](#集中化的版本控制系统) + - [分布式版本控制系统](#分布式版本控制系统) +- [认识 Git](#认识-git) + - [Git 简史](#git-简史) + - [Git 与其他版本管理系统的主要区别](#git-与其他版本管理系统的主要区别) + - [Git 的三种状态](#git-的三种状态) +- [Git 使用快速入门](#git-使用快速入门) + - [获取 Git 仓库](#获取-git-仓库) + - [记录每次更新到仓库](#记录每次更新到仓库) + - [推送改动到远程仓库](#推送改动到远程仓库) + - [远程仓库的移除与重命名](#远程仓库的移除与重命名) + - [查看提交历史](#查看提交历史) + - [撤销操作](#撤销操作) + - [分支](#分支) +- [推荐阅读](#推荐阅读) + + + +## 版本控制 + +### 什么是版本控制 + +版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。 除了项目源代码,你可以对任何类型的文件进行版本控制。 + +### 为什么要版本控制 + +有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。 + +### 本地版本控制系统 + +许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。 + +为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。 + +![本地版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/本地版本控制系统.png) + +### 集中化的版本控制系统 + +接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 + +集中化的版本控制系统都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 + +![集中化的版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/集中化的版本控制系统.png) + +这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题: + +- **单点故障:** 中央服务器宕机,则其他人无法使用;如果中心数据库磁盘损坏有没有进行备份,你将丢失所有数据。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。 +- **必须联网才能工作:** 受网络状况、带宽影响。 + +### 分布式版本控制系统 + +于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 Git 就是一个典型的分布式版本控制系统。 + +这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。 + +![分布式版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/分布式版本控制系统.png) + +分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。 + +分布式版本控制系统的优势不单是不必联网这么简单,后面我们还会看到 Git 极其强大的分支管理等功能。 + +## 认识 Git + +### Git 简史 + +Linux 内核项目组当时使用分布式版本控制系统 BitKeeper 来管理和维护代码。但是,后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统,而且对新的版本控制系统做了很多改进。 + +### Git 与其他版本管理系统的主要区别 + + Git 在保存和对待各种信息的时候与其它版本控制系统有很大差异,尽管操作起来的命令形式非常相近,理解这些差异将有助于防止你使用中的困惑。 + +下面我们主要说一个关于 Git 其他版本管理系统的主要差别:**对待数据的方式**。 + +**Git采用的是直接记录快照的方式,而非差异比较。我后面会详细介绍这两种方式的差别。** + +大部分版本控制系统(CVS、Subversion、Perforce、Bazaar 等等)都是以文件变更列表的方式存储信息,这类系统**将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。** + +具体原理如下图所示,理解起来其实很简单,每个我们对提交更新一个文件之后,系统记录都会记录这个文件做了哪些更新,以增量符号Δ(Delta)表示。 + +
+ +
+
+ +**我们怎样才能得到一个文件的最终版本呢?** + +很简单,高中数学的基本知识,我们只需要将这些原文件和这些增加进行相加就行了。 + +**这种方式有什么问题呢?** + +比如我们的增量特别特别多的话,如果我们要得到最终的文件是不是会耗费时间和性能。 + +Git 不按照以上方式对待或保存数据。 反之,Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 **快照流**。 + +
+ +
+
+ + +### Git 的三种状态 + +Git 有三种状态,你的文件可能处于其中之一: + +1. **已提交(committed)**:数据已经安全的保存在本地数据库中。 +2. **已修改(modified)**:已修改表示修改了文件,但还没保存到数据库中。 +3. **已暂存(staged)**:表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。 + +由此引入 Git 项目的三个工作区域的概念:**Git 仓库(.git directoty)**、**工作目录(Working Directory)** 以及 **暂存区域(Staging Area)** 。 + +
+ +
+ +**基本的 Git 工作流程如下:** + +1. 在工作目录中修改文件。 +2. 暂存文件,将文件的快照放入暂存区域。 +3. 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。 + +## Git 使用快速入门 + +### 获取 Git 仓库 + +有两种取得 Git 项目仓库的方法。 + +1. 在现有目录中初始化仓库: 进入项目目录运行 `git init` 命令,该命令将创建一个名为 `.git` 的子目录。 +2. 从一个服务器克隆一个现有的 Git 仓库: `git clone [url]` 自定义本地仓库的名字: `git clone [url]` directoryname + +### 记录每次更新到仓库 + +1. **检测当前文件状态** : `git status` +2. **提出更改(把它们添加到暂存区**):`git add filename ` (针对特定文件)、`git add *`(所有文件)、`git add *.txt`(支持通配符,所有 .txt 文件) +3. **忽略文件**:`.gitignore` 文件 +4. **提交更新:** `git commit -m "代码提交信息"` (每次准备提交前,先用 `git status` 看下,是不是都已暂存起来了, 然后再运行提交命令 `git commit`) +5. **跳过使用暂存区域更新的方式** : `git commit -a -m "代码提交信息"`。 `git commit` 加上 `-a` 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 `git add` 步骤。 +6. **移除文件** :`git rm filename` (从暂存区域移除,然后提交。) +7. **对文件重命名** :`git mv README.md README`(这个命令相当于`mv README.md README`、`git rm README.md`、`git add README` 这三条命令的集合) + +### 推送改动到远程仓库 + +- 如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:·`git remote add origin ` ,比如我们要让本地的一个仓库和 Github 上创建的一个仓库关联可以这样`git remote add origin https://github.com/Snailclimb/test.git` +- 将这些改动提交到远端仓库:`git push origin master` (可以把 *master* 换成你想要推送的任何分支) + + 如此你就能够将你的改动推送到所添加的服务器上去了。 + +### 远程仓库的移除与重命名 + +- 将 test 重命名位 test1:`git remote rename test test1` +- 移除远程仓库 test1:`git remote rm test1` + +### 查看提交历史 + +在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 `git log` 命令。`git log` 会按提交时间列出所有的更新,最近的更新排在最上面。 + +**可以添加一些参数来查看自己希望看到的内容:** + +只看某个人的提交记录: + +```shell +git log --author=bob +``` + +### 撤销操作 + +有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 `--amend` 选项的提交命令尝试重新提交: + +```console +git commit --amend +``` + +取消暂存的文件 + +```console +git reset filename +``` + +撤消对文件的修改: + +``` +git checkout -- filename +``` + +假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它: + +``` +git fetch origin +git reset --hard origin/master +``` + + + +### 分支 + +分支是用来将特性开发绝缘开来的。在你创建仓库的时候,*master* 是“默认的”分支。在其他分支上进行开发,完成后再将它们合并到主分支上。 + +我们通常在开发新功能、修复一个紧急 bug 等等时候会选择创建分支。单分支开发好还是多分支开发好,还是要看具体场景来说。 + +创建一个名字叫做 test 的分支 + +```console +git branch test +``` + +切换当前分支到 test(当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样) + +```console +git checkout test +``` + +
+ +
+ +你也可以直接这样创建分支并切换过去(上面两条命令的合写) + +```console +git checkout -b feature_x +``` + +切换到主分支 + +``` +git checkout master +``` + +合并分支(可能会有冲突) + +```java + git merge test +``` + +把新建的分支删掉 + +``` +git branch -d feature_x +``` + +将分支推送到远端仓库(推送成功后其他人可见): + +``` +git push origin +``` + + + +## 推荐阅读 + +- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) +- [图解Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html) +- [猴子都能懂得Git入门](https://backlog.com/git-tutorial/cn/intro/intro1_1.html) +- https://git-scm.com/book/en/v2 diff --git a/submission.html b/submission.html new file mode 100644 index 00000000000..b291af73d07 --- /dev/null +++ b/submission.html @@ -0,0 +1,231 @@ + + + + +Untitled + + +

前言

如果你有不错的大厂面试经历

如果你对某一个Java知识点有过深刻的见解和记录

如果你有什么学习编程的经验分享给其他人

......

如果你想,你都可以把这些整理成文章投稿给我,你的文章将被更多人看到,你所写的东西也可能会默默影响到很多人。

欢迎的文章类型

本公众号主要接受下面几类文章投稿:

  1. 大厂Java面试经历、面试总结、面试注意事项等等
  2. 对某一个Java知识点的深刻讲解
  3. 技术热点
  4. 一些行业内不错的人物或者公司传记
  5. ......

因为本公众号主要是Java方向,所以还有其他Java方向的文章都可以投稿。

投稿的要求很简单,你能把一个知识点讲清楚就好,JavaGuide 里面还有很多知识点没有加,我觉得都是不错的方向(内容我会帮你完善)。 这个投稿的目的是完善开源项目 JavaGuide 的同时能够让大家自主去学习并总结知识点。

奖励

投稿自己的原创文章成功之后不仅可以顺利加入32k+star 的开源项目 JavaGuide:https://github.com/Snailclimb/JavaGuide 扩大自己的竞争力,还会有50元左右的任意书籍或者50元现金奖励(对于不错的文章会更高)。

投稿方式

下面 2 种方式都可以

  1. 将你的文章以 Markdown 格式发送到我的邮箱:koushuangbwcx@163.com ;
  2. 直接在 Github : https://github.com/Snailclimb/JavaGuide 提PR也可以 。

如果你也是公众号号主,你也有自己不错的文章想投稿给我的话,虽然你不会有奖励,但是发了你的文章之后,肯定会为你带来一些新人关注。

 

+ + \ No newline at end of file diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" deleted file mode 100644 index dc56d44af82..00000000000 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" +++ /dev/null @@ -1,128 +0,0 @@ - - -# Spring相关教程/资料: - -> ## 官网相关 - - [Spring官网](https://spring.io/) - -[Spring系列主要项目](https://spring.io/projects) - -从配置到安全性,Web应用到大数据 - 无论您的应用程序的基础架构需求如何,都有一个Spring Project来帮助您构建它。 从小处着手,根据需要使用 - Spring是通过设计模块化的。 - - [Spring官网指南](https://spring.io/guides) - -无论您在构建什么,这些指南都旨在尽可能快地提高您的工作效率 - 使用Spring团队推荐的最新Spring项目发布和技术。 - - [Spring官方文档翻译(1~6章)](https://blog.csdn.net/tangtong1/article/details/51326887) - -> ## 系统学习教程: - -### 文档: - - [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html) - - [Spring W3Cschool教程 ](https://www.w3cschool.cn/wkspring/f6pk1ic8.html) - -### 视频: - -[网易云课堂——58集精通java教程Spring框架开发](http://study.163.com/course/courseMain.htm?courseId=1004475015#/courseDetail?tab=1&35) - - [慕课网相关视频](https://www.imooc.com/) - -**黑马视频(非常推荐):** -微信公众号:“**Java面试通关手册**”后台回复“**资源分享第一波**”免费领取。 - -> ## 一些常用的东西 - -[Spring Framework 4.3.17.RELEASE API](https://docs.spring.io/spring/docs/4.3.17.RELEASE/javadoc-api/) - -默认浏览器打开,当需要查某个类的作用的时候,可以在浏览器通过ctrl+f搜索。 - - -# 面试必备知识点 - - -> ## SpringAOP,IOC实现原理 - -AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器?这些东西都是经常会被问到的。 - -[自己动手实现的 Spring IOC 和 AOP - 上篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/) - -[自己动手实现的 Spring IOC 和 AOP - 下篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-下篇/) - -### AOP: - -AOP思想的实现一般都是基于 **代理模式** ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,**JDK动态代理模式只能代理接口而不能代理类**。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。 - -- 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类; -- 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。 - - - -[※静态代理、JDK动态代理、CGLIB动态代理讲解](http://www.cnblogs.com/puyangsky/p/6218925.html) - -我们知道AOP思想的实现一般都是基于 **代理模式** ,所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。 - -[Spring AOP 入门](https://juejin.im/post/5aa7818af265da23844040c6) - -带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念:5种类型的通知(Before,After,After-returning,After-throwing,Around);Spring中对AOP的支持:AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理模式,Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理, - -[※Spring AOP 基于AspectJ注解如何实现AOP](https://juejin.im/post/5a55af9e518825734d14813f) - - -**AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)**,可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易 - -Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。 - - -[※探秘Spring AOP(慕课网视频,很不错)](https://www.imooc.com/learn/869) - -慕课网视频,讲解的很不错,详细且深入 - - -[spring源码剖析(六)AOP实现原理剖析](https://blog.csdn.net/fighterandknight/article/details/51209822) - -通过源码分析Spring AOP的原理 - -### IOC: - -Spring IOC的初始化过程: -![Spring IOC的初始化过程](https://user-gold-cdn.xitu.io/2018/5/22/16387903ee72c831?w=709&h=56&f=png&s=4673) - -[[Spring框架]Spring IOC的原理及详解。](https://www.cnblogs.com/wang-meng/p/5597490.html) - -[Spring IOC核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/) - -比较简短,推荐阅读。 - -[Spring IOC 容器源码分析](https://javadoop.com/post/spring-ioc) - -强烈推荐,内容详尽,而且便于阅读。 - -> ## Spring事务管理 - -[可能是最漂亮的Spring事务管理详解](https://juejin.im/post/5b00c52ef265da0b95276091) - -[Spring编程式和声明式事务实例讲解](https://juejin.im/post/5b010f27518825426539ba38) - -> ## 其他 - -**Spring单例与线程安全:** - -[Spring框架中的单例模式(源码解读)](http://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html) - -单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时,使用了 多重判断加锁 的单例模式。 - -> ## Spring源码阅读 - -阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 - -### [Spring源码阅读](https://github.com/seaswalker/Spring) - - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md) -- [spring-aop](https://github.com/seaswalker/Spring/blob/master/note/spring-aop.md) -- [spring-context](https://github.com/seaswalker/Spring/blob/master/note/spring-context.md) -- [spring-task](https://github.com/seaswalker/Spring/blob/master/note/spring-task.md) -- [spring-transaction](https://github.com/seaswalker/Spring/blob/master/note/spring-transaction.md) -- [spring-mvc](https://github.com/seaswalker/Spring/blob/master/note/spring-mvc.md) -- [guava-cache](https://github.com/seaswalker/Spring/blob/master/note/guava-cache.md) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" deleted file mode 100644 index 44eb02aaa9d..00000000000 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" +++ /dev/null @@ -1,171 +0,0 @@ - - -Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide) - -> ## 书籍推荐 - -**《高性能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常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇](https://juejin.im/post/5b1685bef265da6e5c3c1c34) - -- ### ②字符集及校对规则 - - 字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。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索引的数据结构](https://www.jianshu.com/p/1775b4ff123a) - - [MySQL优化系列(三)--索引的使用、原理和设计优化](https://blog.csdn.net/Jack__Frost/article/details/72571540) - - [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79#comment) - -- ### ④查询缓存的使用 - - my.cnf加入以下配置,重启Mysql开启查询缓存 - ``` - query_cache_type=1 - query_cache_size=600000 - ``` - - Mysql执行以下命令也可以开启查询缓存 - - ``` - set global query_cache_type=1; - set global query_cache_size=600000; - ``` - 如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、Mysql库中的系统表,其查询结果也不会被缓存。 - - 缓存建立之后,Mysql的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。 - - **缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十MB比较合适。此外,**还可以通过sql_cache和sql_no_cache来控制某个查询语句是否需要缓存:** - ``` - select sql_no_cache count(*) from usr; - ``` - -- ### ⑤事务机制 - - **关系性数据库需要遵循ACID规则,具体内容如下:** - -![事务的特性](https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430) - - 1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 2. **一致性:** 执行事务前后,数据库从一个一致性状态转换到另一个一致性状态。 - 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事务所干扰,各并发事务之间数据库是独立的; - 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 - - **为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:** - -- **READ_UNCOMMITTED(未提交读):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** -- **READ_COMMITTED(提交读):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** -- **SERIALIZABLE(串行):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 - - 这里需要注意的是:**Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.** - - 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVCC(多版本并发控制),通过行的创建时间和行的过期时间来支持并发一致性读和回滚等特性。 - - 详细内容可以参考: [可能是最漂亮的Spring事务管理详解](https://blog.csdn.net/qq_34337272/article/details/80394121) - -- ### ⑥锁机制与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) - - **InnoDB存储引擎的锁的算法有三种:** - - Record lock:单个行记录上的锁 - - Gap lock:间隙锁,锁定一个范围,不包括记录本身 - - Next-key lock:record+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://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015) - - **垂直拆分的优点:** 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。 - - **垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; - - 4. **水平分区:** - - - **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** - - 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。 - - ![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119) - - 水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。 - - 水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 - - **下面补充一下数据库分片的两种常见方案:** - - **客户端代理:** **分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。 - - **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。 - - - 详细内容可以参考: - [MySQL大表优化方案](https://segmentfault.com/a/1190000006158186) - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.classpath" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.classpath" deleted file mode 100644 index 0a1daddd3e8..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.classpath" +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.gitignore" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.gitignore" deleted file mode 100644 index b83d22266ac..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.gitignore" +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.project" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.project" deleted file mode 100644 index 7b9c539d09c..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.project" +++ /dev/null @@ -1,23 +0,0 @@ - - - securityAlgorithm - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.core.resources.prefs" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.core.resources.prefs" deleted file mode 100644 index f9fe34593fc..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.core.resources.prefs" +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/test/java=UTF-8 -encoding/=UTF-8 diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.jdt.core.prefs" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.jdt.core.prefs" deleted file mode 100644 index abec6ca389a..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.jdt.core.prefs" +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -org.eclipse.jdt.core.compiler.compliance=1.5 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.5 diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.m2e.core.prefs" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.m2e.core.prefs" deleted file mode 100644 index f897a7f1cb2..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/.settings/org.eclipse.m2e.core.prefs" +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/pom.xml" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/pom.xml" deleted file mode 100644 index 39d693b84b0..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/pom.xml" +++ /dev/null @@ -1,37 +0,0 @@ - - 4.0.0 - - com.snailclimb.ks - securityAlgorithm - 0.0.1-SNAPSHOT - jar - - securityAlgorithm - http://maven.apache.org - - - UTF-8 - - - - - junit - junit - 4.12 - test - - - - commons-codec - commons-codec - 1.8 - - - org.bouncycastle - bcprov-jdk15on - 1.56 - - - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/Base64Demo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/Base64Demo.java" deleted file mode 100644 index 1c6fd6df0ea..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/Base64Demo.java" +++ /dev/null @@ -1,49 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import java.io.UnsupportedEncodingException; -import java.util.Base64; - -public class Base64Demo { - - public static void main(String[] args) throws UnsupportedEncodingException { - // TODO Auto-generated method stub - CommonsCodecDemo(); - bouncyCastleDemo(); - jdkDemo(); - } - - static String str = "你若安好,便是晴天"; - - /** - * commons codec实现Base64加密解密 - */ - public static void CommonsCodecDemo() { - // 加密: - byte[] encodeBytes = org.apache.commons.codec.binary.Base64.encodeBase64(str.getBytes()); - System.out.println("commons codec实现base64加密: " + new String(encodeBytes)); - // 解密: - byte[] decodeBytes = org.apache.commons.codec.binary.Base64.decodeBase64(encodeBytes); - System.out.println("commons codec实现base64解密: " + new String(decodeBytes)); - } - - /** - * bouncy castle实现Base64加密解密 - */ - public static void bouncyCastleDemo() { - // 加密 - byte[] encodeBytes = org.bouncycastle.util.encoders.Base64.encode(str.getBytes()); - System.out.println("bouncy castle实现base64加密: " + new String(encodeBytes)); - // 解密 - byte[] decodeBytes = org.bouncycastle.util.encoders.Base64.decode(encodeBytes); - System.out.println("bouncy castle实现base64解密:" + new String(decodeBytes)); - } - - public static void jdkDemo() throws UnsupportedEncodingException { - // 加密 - String encodeBytes = Base64.getEncoder().encodeToString(str.getBytes("UTF-8")); - System.out.println("JDK实现的base64加密: " + encodeBytes); - //解密 - byte[] decodeBytes = Base64.getDecoder().decode(encodeBytes.getBytes("UTF-8")); - System.out.println("JDK实现的base64解密: "+new String(decodeBytes)); - } -} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/DesDemo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/DesDemo.java" deleted file mode 100644 index ce8b09ee1da..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/DesDemo.java" +++ /dev/null @@ -1,100 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.SecretKeyFactory; -import javax.crypto.SecretKey; -import javax.crypto.Cipher; - -/** - * DES加密介绍 DES是一种对称加密算法,所谓对称加密算法即:加密和解密使用相同密钥的算法。DES加密算法出自IBM的研究, - * 后来被美国政府正式采用,之后开始广泛流传,但是近些年使用越来越少,因为DES使用56位密钥,以现代计算能力, - * 24小时内即可被破解。虽然如此,在某些简单应用中,我们还是可以使用DES加密算法,本文简单讲解DES的JAVA实现 。 - * 注意:DES加密和解密过程中,密钥长度都必须是8的倍数 - */ -public class DesDemo { - public DesDemo() { - } - - // 测试 - public static void main(String args[]) { - // 待加密内容 - String str = "cryptology"; - // 密码,长度要是8的倍数 - String password = "95880288"; - - byte[] result; - try { - result = DesDemo.encrypt(str.getBytes(), password); - System.out.println("加密后:" + result); - byte[] decryResult = DesDemo.decrypt(result, password); - System.out.println("解密后:" + decryResult); - } catch (UnsupportedEncodingException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); - } catch (Exception e1) { - e1.printStackTrace(); - } - } - - // 直接将如上内容解密 - - /** - * 加密 - * - * @param datasource - * byte[] - * @param password - * String - * @return byte[] - */ - public static byte[] encrypt(byte[] datasource, String password) { - try { - SecureRandom random = new SecureRandom(); - DESKeySpec desKey = new DESKeySpec(password.getBytes()); - // 创建一个密匙工厂,然后用它把DESKeySpec转换成 - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey securekey = keyFactory.generateSecret(desKey); - // Cipher对象实际完成加密操作 - Cipher cipher = Cipher.getInstance("DES"); - // 用密匙初始化Cipher对象,ENCRYPT_MODE用于将 Cipher 初始化为加密模式的常量 - cipher.init(Cipher.ENCRYPT_MODE, securekey, random); - // 现在,获取数据并加密 - // 正式执行加密操作 - return cipher.doFinal(datasource); // 按单部分操作加密或解密数据,或者结束一个多部分操作 - } catch (Throwable e) { - e.printStackTrace(); - } - return null; - } - - /** - * 解密 - * - * @param src - * byte[] - * @param password - * String - * @return byte[] - * @throws Exception - */ - public static byte[] decrypt(byte[] src, String password) throws Exception { - // DES算法要求有一个可信任的随机数源 - SecureRandom random = new SecureRandom(); - // 创建一个DESKeySpec对象 - DESKeySpec desKey = new DESKeySpec(password.getBytes()); - // 创建一个密匙工厂 - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 返回实现指定转换的 - // Cipher - // 对象 - // 将DESKeySpec对象转换成SecretKey对象 - SecretKey securekey = keyFactory.generateSecret(desKey); - // Cipher对象实际完成解密操作 - Cipher cipher = Cipher.getInstance("DES"); - // 用密匙初始化Cipher对象 - cipher.init(Cipher.DECRYPT_MODE, securekey, random); - // 真正开始解密操作 - return cipher.doFinal(src); - } -} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/IDEADemo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/IDEADemo.java" deleted file mode 100644 index 5ce251df0fa..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/IDEADemo.java" +++ /dev/null @@ -1,46 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import java.security.Key; -import java.security.Security; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -public class IDEADemo { - public static void main(String args[]) { - bcIDEA(); - } - public static void bcIDEA() { - String src = "www.xttblog.com security idea"; - try { - Security.addProvider(new BouncyCastleProvider()); - - //生成key - KeyGenerator keyGenerator = KeyGenerator.getInstance("IDEA"); - keyGenerator.init(128); - SecretKey secretKey = keyGenerator.generateKey(); - byte[] keyBytes = secretKey.getEncoded(); - - //转换密钥 - Key key = new SecretKeySpec(keyBytes, "IDEA"); - - //加密 - Cipher cipher = Cipher.getInstance("IDEA/ECB/ISO10126Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] result = cipher.doFinal(src.getBytes()); - System.out.println("bc idea encrypt : " + Base64.encodeBase64String(result)); - - //解密 - cipher.init(Cipher.DECRYPT_MODE, key); - result = cipher.doFinal(result); - System.out.println("bc idea decrypt : " + new String(result)); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5.java" deleted file mode 100644 index 2b8e31f83f5..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5.java" +++ /dev/null @@ -1,160 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -public class MD5{ - /* - *四个链接变量 - */ - private final int A=0x67452301; - private final int B=0xefcdab89; - private final int C=0x98badcfe; - private final int D=0x10325476; - /* - *ABCD的临时变量 - */ - private int Atemp,Btemp,Ctemp,Dtemp; - - /* - *常量ti - *公式:floor(abs(sin(i+1))×(2pow32) - */ - private final int K[]={ - 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee, - 0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8, - 0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193, - 0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51, - 0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, - 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905, - 0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681, - 0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60, - 0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05, - 0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244, - 0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92, - 0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314, - 0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391}; - /* - *向左位移数,计算方法未知 - */ - private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7, - 12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, - 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10, - 15,21,6,10,15,21,6,10,15,21,6,10,15,21}; - - - /* - *初始化函数 - */ - private void init(){ - Atemp=A; - Btemp=B; - Ctemp=C; - Dtemp=D; - } - /* - *移动一定位数 - */ - private int shift(int a,int s){ - return(a<>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位 - } - /* - *主循环 - */ - private void MainLoop(int M[]){ - int F,g; - int a=Atemp; - int b=Btemp; - int c=Ctemp; - int d=Dtemp; - for(int i = 0; i < 64; i ++){ - if(i<16){ - F=(b&c)|((~b)&d); - g=i; - }else if(i<32){ - F=(d&b)|((~d)&c); - g=(5*i+1)%16; - }else if(i<48){ - F=b^c^d; - g=(3*i+5)%16; - }else{ - F=c^(b|(~d)); - g=(7*i)%16; - } - int tmp=d; - d=c; - c=b; - b=b+shift(a+F+K[i]+M[g],s[i]); - a=tmp; - } - Atemp=a+Atemp; - Btemp=b+Btemp; - Ctemp=c+Ctemp; - Dtemp=d+Dtemp; - - } - /* - *填充函数 - *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) - *填充方式为先加一个0,其它位补零 - *最后加上64位的原来长度 - */ - private int[] add(String str){ - int num=((str.length()+8)/64)+1;//以512位,64个字节为一组 - int strByte[]=new int[num*16];//64/4=16,所以有16个整数 - for(int i=0;i>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序 - } - strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1 - /* - *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 - */ - strByte[num*16-2]=str.length()*8; - return strByte; - } - /* - *调用函数 - */ - public String getMD5(String source){ - init(); - int strByte[]=add(source); - for(int i=0;i>i*8)%(1<<8))&0xff)).replace(' ', '0'); - - } - return str; - } - /* - *单例 - */ - private static MD5 instance; - public static MD5 getInstance(){ - if(instance==null){ - instance=new MD5(); - } - return instance; - } - - private MD5(){}; - - public static void main(String[] args){ - String str=MD5.getInstance().getMD5("你若安好,便是晴天"); - System.out.println(str); - } -} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5Demo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5Demo.java" deleted file mode 100644 index 3a8635d1e69..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/MD5Demo.java" +++ /dev/null @@ -1,50 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import java.security.MessageDigest; - -public class MD5Demo { - - // test - public static void main(String[] args) { - System.out.println(getMD5Code("你若安好,便是晴天")); - } - - private MD5Demo() { - } - - // md5加密 - public static String getMD5Code(String message) { - String md5Str = ""; - try { - //创建MD5算法消息摘要 - MessageDigest md = MessageDigest.getInstance("MD5"); - //生成的哈希值的字节数组 - byte[] md5Bytes = md.digest(message.getBytes()); - md5Str = bytes2Hex(md5Bytes); - }catch(Exception e) { - e.printStackTrace(); - } - return md5Str; - } - - // 2进制转16进制 - public static String bytes2Hex(byte[] bytes) { - StringBuffer result = new StringBuffer(); - int temp; - try { - for (int i = 0; i < bytes.length; i++) { - temp = bytes[i]; - if(temp < 0) { - temp += 256; - } - if (temp < 16) { - result.append("0"); - } - result.append(Integer.toHexString(temp)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return result.toString(); - } -} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/RSADemo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/RSADemo.java" deleted file mode 100644 index 5234028ece4..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/RSADemo.java" +++ /dev/null @@ -1,249 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import org.apache.commons.codec.binary.Base64; - -import java.security.*; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.Cipher; - -/** - * Created by humf.需要依赖 commons-codec 包 - */ -public class RSADemo { - - public static void main(String[] args) throws Exception { - Map keyMap = initKey(); - String publicKey = getPublicKey(keyMap); - String privateKey = getPrivateKey(keyMap); - - System.out.println(keyMap); - System.out.println("-----------------------------------"); - System.out.println(publicKey); - System.out.println("-----------------------------------"); - System.out.println(privateKey); - System.out.println("-----------------------------------"); - byte[] encryptByPrivateKey = encryptByPrivateKey("123456".getBytes(), privateKey); - byte[] encryptByPublicKey = encryptByPublicKey("123456", publicKey); - System.out.println(encryptByPrivateKey); - System.out.println("-----------------------------------"); - System.out.println(encryptByPublicKey); - System.out.println("-----------------------------------"); - String sign = sign(encryptByPrivateKey, privateKey); - System.out.println(sign); - System.out.println("-----------------------------------"); - boolean verify = verify(encryptByPrivateKey, publicKey, sign); - System.out.println(verify); - System.out.println("-----------------------------------"); - byte[] decryptByPublicKey = decryptByPublicKey(encryptByPrivateKey, publicKey); - byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, privateKey); - System.out.println(decryptByPublicKey); - System.out.println("-----------------------------------"); - System.out.println(decryptByPrivateKey); - - } - - public static final String KEY_ALGORITHM = "RSA"; - public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; - - private static final String PUBLIC_KEY = "RSAPublicKey"; - private static final String PRIVATE_KEY = "RSAPrivateKey"; - - public static byte[] decryptBASE64(String key) { - return Base64.decodeBase64(key); - } - - public static String encryptBASE64(byte[] bytes) { - return Base64.encodeBase64String(bytes); - } - - /** - * 用私钥对信息生成数字签名 - * - * @param data - * 加密数据 - * @param privateKey - * 私钥 - * @return - * @throws Exception - */ - public static String sign(byte[] data, String privateKey) throws Exception { - // 解密由base64编码的私钥 - byte[] keyBytes = decryptBASE64(privateKey); - // 构造PKCS8EncodedKeySpec对象 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - // KEY_ALGORITHM 指定的加密算法 - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - // 取私钥匙对象 - PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 用私钥对信息生成数字签名 - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initSign(priKey); - signature.update(data); - return encryptBASE64(signature.sign()); - } - - /** - * 校验数字签名 - * - * @param data - * 加密数据 - * @param publicKey - * 公钥 - * @param sign - * 数字签名 - * @return 校验成功返回true 失败返回false - * @throws Exception - */ - public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { - // 解密由base64编码的公钥 - byte[] keyBytes = decryptBASE64(publicKey); - // 构造X509EncodedKeySpec对象 - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - // KEY_ALGORITHM 指定的加密算法 - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - // 取公钥匙对象 - PublicKey pubKey = keyFactory.generatePublic(keySpec); - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initVerify(pubKey); - signature.update(data); - // 验证签名是否正常 - return signature.verify(decryptBASE64(sign)); - } - - public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得私钥 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 对数据解密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, privateKey); - return cipher.doFinal(data); - } - - /** - * 解密
- * 用私钥解密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] decryptByPrivateKey(String data, String key) throws Exception { - return decryptByPrivateKey(decryptBASE64(data), key); - } - - /** - * 解密
- * 用公钥解密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得公钥 - X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key publicKey = keyFactory.generatePublic(x509KeySpec); - // 对数据解密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, publicKey); - return cipher.doFinal(data); - } - - /** - * 加密
- * 用公钥加密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] encryptByPublicKey(String data, String key) throws Exception { - // 对公钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得公钥 - X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key publicKey = keyFactory.generatePublic(x509KeySpec); - // 对数据加密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - return cipher.doFinal(data.getBytes()); - } - - /** - * 加密
- * 用私钥加密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得私钥 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 对数据加密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, privateKey); - return cipher.doFinal(data); - } - - /** - * 取得私钥 - * - * @param keyMap - * @return - * @throws Exception - */ - public static String getPrivateKey(Map keyMap) throws Exception { - Key key = (Key) keyMap.get(PRIVATE_KEY); - return encryptBASE64(key.getEncoded()); - } - - /** - * 取得公钥 - * - * @param keyMap - * @return - * @throws Exception - */ - public static String getPublicKey(Map keyMap) throws Exception { - Key key = keyMap.get(PUBLIC_KEY); - return encryptBASE64(key.getEncoded()); - } - - /** - * 初始化密钥 - * - * @return - * @throws Exception - */ - public static Map initKey() throws Exception { - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - keyPairGen.initialize(1024); - KeyPair keyPair = keyPairGen.generateKeyPair(); - Map keyMap = new HashMap(2); - keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公钥 - keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私钥 - return keyMap; - } - -} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/SHA1Demo.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/SHA1Demo.java" deleted file mode 100644 index ab19e3d0cf9..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/SHA1Demo.java" +++ /dev/null @@ -1,45 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class SHA1Demo { - - public static void main(String[] args) { - // TODO Auto-generated method stub - System.out.println(getSha1("你若安好,便是晴天")); - - } - - public static String getSha1(String str) { - if (null == str || 0 == str.length()) { - return null; - } - char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - try { - //创建SHA1算法消息摘要对象 - MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); - //使用指定的字节数组更新摘要。 - mdTemp.update(str.getBytes("UTF-8")); - //生成的哈希值的字节数组 - byte[] md = mdTemp.digest(); - //SHA1算法生成信息摘要关键过程 - int j = md.length; - char[] buf = new char[j * 2]; - int k = 0; - for (int i = 0; i < j; i++) { - byte byte0 = md[i]; - buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; - buf[k++] = hexDigits[byte0 & 0xf]; - } - return new String(buf); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return "0"; - - } -} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/readme" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/readme" deleted file mode 100644 index 5c2d452dd23..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/main/java/com/snailclimb/ks/securityAlgorithm/readme" +++ /dev/null @@ -1,3 +0,0 @@ -Des算法参考:http://blog.csdn.net/super_cui/article/details/70820983 -IDEA算法参考:https://www.xttblog.com/?p=1121 -RSA算法实现参考:https://www.cnblogs.com/xlhan/p/7120488.html \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/test/java/com/snailclimb/ks/securityAlgorithm/AppTest.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/test/java/com/snailclimb/ks/securityAlgorithm/AppTest.java" deleted file mode 100644 index 932254515a0..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/source code/securityAlgorithm/src/test/java/com/snailclimb/ks/securityAlgorithm/AppTest.java" +++ /dev/null @@ -1,38 +0,0 @@ -package com.snailclimb.ks.securityAlgorithm; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" deleted file mode 100644 index 8dddb1b9f4b..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" +++ /dev/null @@ -1,849 +0,0 @@ -本文主要对消息摘要算法和加密算法做了整理,包括MD5、SHA、DES、AES、RSA等,并且提供了相应算法的Java实现和测试。 - -# 一 消息摘要算法 - -## **1. 简介:** - -- **消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密** -- **只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。** -- **消息摘要算法主要应用在“数字签名”领域,作为对明文的摘要算法。** -- **著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体**。 - -## **2. 特点:** - -1. **无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。** -2. **消息摘要看起来是“伪随机的”。也就是说对相同的信息求摘要结果相同。** -3. **消息轻微改变生成的摘要变化会很大** -4. **只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息** - -## **3. 应用:** - -消息摘要算法最常用的场景就是数字签名以及数据(密码)加密了。(一般平时做项目用的比较多的就是使用MD5对用户密码进行加密) - -## **4. 何谓数字签名:** - -数字签名主要用到了非对称密钥加密技术与数字摘要技术。数字签名技术是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。 -如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过. - -因此数字签名能够验证信息的完整性。 -数字签名是个加密的过程,数字签名验证是个解密的过程。 - -## **5. 常见消息/数字摘要算法:** - -### [**MD5:**](https://baike.baidu.com/item/MD5/212708?fr=aladdin) - -#### 简介: - -MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式 - (也就是把一个任意长度的字节串变换成一定长的十六进制数字串)。 - -#### 特点: - -1. **压缩性:** 任意长度的数据,算出的MD5值长度都是固定的。 -2. **容易计算:** 从原数据计算出MD5值很容易。 -3. **抗修改性:** 对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。 -4. **强抗碰撞:** 已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。 - -#### 代码实现: - -**利用JDK提供java.security.MessageDigest类实现MD5算法:** - -```java -package com.snailclimb.ks.securityAlgorithm; - -import java.security.MessageDigest; - -public class MD5Demo { - - // test - public static void main(String[] args) { - System.out.println(getMD5Code("你若安好,便是晴天")); - } - - private MD5Demo() { - } - - // md5加密 - public static String getMD5Code(String message) { - String md5Str = ""; - try { - //创建MD5算法消息摘要 - MessageDigest md = MessageDigest.getInstance("MD5"); - //生成的哈希值的字节数组 - byte[] md5Bytes = md.digest(message.getBytes()); - md5Str = bytes2Hex(md5Bytes); - }catch(Exception e) { - e.printStackTrace(); - } - return md5Str; - } - - // 2进制转16进制 - public static String bytes2Hex(byte[] bytes) { - StringBuffer result = new StringBuffer(); - int temp; - try { - for (int i = 0; i < bytes.length; i++) { - temp = bytes[i]; - if(temp < 0) { - temp += 256; - } - if (temp < 16) { - result.append("0"); - } - result.append(Integer.toHexString(temp)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return result.toString(); - } -} - -``` - -**结果:** -``` -6bab82679914f7cb480a120b532ffa80 - -``` - -**注意MessageDigest类的几个方法:** - -```java -static MessageDigest getInstance(String algorithm)//返回实现指定摘要算法的MessageDigest对象 -``` -```java -byte[] digest(byte[] input)//使用指定的字节数组对摘要执行最终更新,然后完成摘要计算。 -``` - -#### 不利用Java提供的java.security.MessageDigest类实现MD5算法: - -```java -package com.snailclimb.ks.securityAlgorithm; - -public class MD5{ - /* - *四个链接变量 - */ - private final int A=0x67452301; - private final int B=0xefcdab89; - private final int C=0x98badcfe; - private final int D=0x10325476; - /* - *ABCD的临时变量 - */ - private int Atemp,Btemp,Ctemp,Dtemp; - - /* - *常量ti - *公式:floor(abs(sin(i+1))×(2pow32) - */ - private final int K[]={ - 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee, - 0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8, - 0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193, - 0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51, - 0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, - 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905, - 0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681, - 0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60, - 0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05, - 0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244, - 0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92, - 0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314, - 0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391}; - /* - *向左位移数,计算方法未知 - */ - private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7, - 12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, - 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10, - 15,21,6,10,15,21,6,10,15,21,6,10,15,21}; - - - /* - *初始化函数 - */ - private void init(){ - Atemp=A; - Btemp=B; - Ctemp=C; - Dtemp=D; - } - /* - *移动一定位数 - */ - private int shift(int a,int s){ - return(a<>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位 - } - /* - *主循环 - */ - private void MainLoop(int M[]){ - int F,g; - int a=Atemp; - int b=Btemp; - int c=Ctemp; - int d=Dtemp; - for(int i = 0; i < 64; i ++){ - if(i<16){ - F=(b&c)|((~b)&d); - g=i; - }else if(i<32){ - F=(d&b)|((~d)&c); - g=(5*i+1)%16; - }else if(i<48){ - F=b^c^d; - g=(3*i+5)%16; - }else{ - F=c^(b|(~d)); - g=(7*i)%16; - } - int tmp=d; - d=c; - c=b; - b=b+shift(a+F+K[i]+M[g],s[i]); - a=tmp; - } - Atemp=a+Atemp; - Btemp=b+Btemp; - Ctemp=c+Ctemp; - Dtemp=d+Dtemp; - - } - /* - *填充函数 - *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) - *填充方式为先加一个0,其它位补零 - *最后加上64位的原来长度 - */ - private int[] add(String str){ - int num=((str.length()+8)/64)+1;//以512位,64个字节为一组 - int strByte[]=new int[num*16];//64/4=16,所以有16个整数 - for(int i=0;i>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序 - } - strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1 - /* - *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 - */ - strByte[num*16-2]=str.length()*8; - return strByte; - } - /* - *调用函数 - */ - public String getMD5(String source){ - init(); - int strByte[]=add(source); - for(int i=0;i>i*8)%(1<<8))&0xff)).replace(' ', '0'); - - } - return str; - } - /* - *单例 - */ - private static MD5 instance; - public static MD5 getInstance(){ - if(instance==null){ - instance=new MD5(); - } - return instance; - } - - private MD5(){}; - - public static void main(String[] args){ - String str=MD5.getInstance().getMD5("你若安好,便是晴天"); - System.out.println(str); - } -} -``` - -### [**SHA1:**](https://baike.baidu.com/item/MD5/212708?fr=aladdin) -对于长度小于2^64位的消息,SHA1会产生一个160位(40个字符)的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。 - -SHA1有如下特性: - -- 不可以从消息摘要中复原信息; -- 两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。 - -#### 代码实现: - -**利用JDK提供java.security.MessageDigest类实现SHA1算法:* - -```java -package com.snailclimb.ks.securityAlgorithm; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class SHA1Demo { - - public static void main(String[] args) { - // TODO Auto-generated method stub - System.out.println(getSha1("你若安好,便是晴天")); - - } - - public static String getSha1(String str) { - if (null == str || 0 == str.length()) { - return null; - } - char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - try { - //创建SHA1算法消息摘要对象 - MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); - //使用指定的字节数组更新摘要。 - mdTemp.update(str.getBytes("UTF-8")); - //生成的哈希值的字节数组 - byte[] md = mdTemp.digest(); - //SHA1算法生成信息摘要关键过程 - int j = md.length; - char[] buf = new char[j * 2]; - int k = 0; - for (int i = 0; i < j; i++) { - byte byte0 = md[i]; - buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; - buf[k++] = hexDigits[byte0 & 0xf]; - } - return new String(buf); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return "0"; - - } -} - -``` - -**结果:** - -``` -8ce764110a42da9b08504b20e26b19c9e3382414 -``` - - - -# 二 加密算法 - -## **1. 简介:** - -- **加密技术包括两个元素:加密算法和密钥。** -- **加密算法是将普通的文本(或者可以理解的信息)与一串数字(密钥)的结合,产生不可理解的密文的步骤。** -- **密钥是用来对数据进行编码和解码的一种算法。** -- **在安全保密中,可通过适当的密钥加密技术和管理机制来保证网络的信息通讯安全。** - -## **2. 分类:** - -**密钥加密技术的密码体制分为对称密钥体制和非对称密钥体制两种。相应地,对数据加密的技术分为两类,即对称加密(私人密钥加密)和非对称加密(公开密钥加密)。** - -**对称加密以数据加密标准(DES,Data Encryption Standard)算法为典型代表,非对称加密通常以RSA(Rivest Shamir Adleman)算法为代表。** - -**对称加密的加密密钥和解密密钥相同。非对称加密的加密密钥和解密密钥不同,加密密钥可以公开而解密密钥需要保密** -## **3. 应用:** - -常被用在电子商务或者其他需要保证网络传输安全的范围。 - -## **4. 对称加密:** - -加密密钥和解密密钥相同的加密算法。 - -对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES), -另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,而且对计算机功能要求也没有那么高。IDEA加密标准由PGP(Pretty Good Privacy)系统使用。 -### [**DES:**](https://baike.baidu.com/item/DES) - -DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,现在已经过时。 - -#### 代码实现: - -DES算法实现 : - -```java -package com.snailclimb.ks.securityAlgorithm; - -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.SecretKeyFactory; -import javax.crypto.SecretKey; -import javax.crypto.Cipher; - -/** - * DES加密介绍 DES是一种对称加密算法,所谓对称加密算法即:加密和解密使用相同密钥的算法。DES加密算法出自IBM的研究, - * 后来被美国政府正式采用,之后开始广泛流传,但是近些年使用越来越少,因为DES使用56位密钥,以现代计算能力, - * 24小时内即可被破解。虽然如此,在某些简单应用中,我们还是可以使用DES加密算法,本文简单讲解DES的JAVA实现 。 - * 注意:DES加密和解密过程中,密钥长度都必须是8的倍数 - */ -public class DesDemo { - public DesDemo() { - } - - // 测试 - public static void main(String args[]) { - // 待加密内容 - String str = "cryptology"; - // 密码,长度要是8的倍数 - String password = "95880288"; - - byte[] result; - try { - result = DesDemo.encrypt(str.getBytes(), password); - System.out.println("加密后:" + result); - byte[] decryResult = DesDemo.decrypt(result, password); - System.out.println("解密后:" + new String(decryResult)); - } catch (UnsupportedEncodingException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); - } catch (Exception e1) { - e1.printStackTrace(); - } - } - - // 直接将如上内容解密 - - /** - * 加密 - * - * @param datasource - * byte[] - * @param password - * String - * @return byte[] - */ - public static byte[] encrypt(byte[] datasource, String password) { - try { - SecureRandom random = new SecureRandom(); - DESKeySpec desKey = new DESKeySpec(password.getBytes()); - // 创建一个密匙工厂,然后用它把DESKeySpec转换成 - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey securekey = keyFactory.generateSecret(desKey); - // Cipher对象实际完成加密操作 - Cipher cipher = Cipher.getInstance("DES"); - // 用密匙初始化Cipher对象,ENCRYPT_MODE用于将 Cipher 初始化为加密模式的常量 - cipher.init(Cipher.ENCRYPT_MODE, securekey, random); - // 现在,获取数据并加密 - // 正式执行加密操作 - return cipher.doFinal(datasource); // 按单部分操作加密或解密数据,或者结束一个多部分操作 - } catch (Throwable e) { - e.printStackTrace(); - } - return null; - } - - /** - * 解密 - * - * @param src - * byte[] - * @param password - * String - * @return byte[] - * @throws Exception - */ - public static byte[] decrypt(byte[] src, String password) throws Exception { - // DES算法要求有一个可信任的随机数源 - SecureRandom random = new SecureRandom(); - // 创建一个DESKeySpec对象 - DESKeySpec desKey = new DESKeySpec(password.getBytes()); - // 创建一个密匙工厂 - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 返回实现指定转换的 - // Cipher - // 对象 - // 将DESKeySpec对象转换成SecretKey对象 - SecretKey securekey = keyFactory.generateSecret(desKey); - // Cipher对象实际完成解密操作 - Cipher cipher = Cipher.getInstance("DES"); - // 用密匙初始化Cipher对象 - cipher.init(Cipher.DECRYPT_MODE, securekey, random); - // 真正开始解密操作 - return cipher.doFinal(src); - } -} -``` - -结果: - -``` -加密后:[B@50cbc42f -解密后:cryptology -``` - -### [**IDEA:**](https://baike.baidu.com/item/%E5%9B%BD%E9%99%85%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/11048972?fr=aladdin) - -- **这种算法是在DES算法的基础上发展出来的,类似于三重DES。** -- **发展IDEA也是因为感到DES具有密钥太短等缺点。** -- **IDEA的密钥为128位,这么长的密钥在今后若干年内应该是安全的。** -- **在实际项目中用到的很少了解即可。** - -#### 代码实现: - -IDEA算法实现 - -```java -package com.snailclimb.ks.securityAlgorithm; - -import java.security.Key; -import java.security.Security; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -public class IDEADemo { - public static void main(String args[]) { - bcIDEA(); - } - public static void bcIDEA() { - String src = "www.xttblog.com security idea"; - try { - Security.addProvider(new BouncyCastleProvider()); - - //生成key - KeyGenerator keyGenerator = KeyGenerator.getInstance("IDEA"); - keyGenerator.init(128); - SecretKey secretKey = keyGenerator.generateKey(); - byte[] keyBytes = secretKey.getEncoded(); - - //转换密钥 - Key key = new SecretKeySpec(keyBytes, "IDEA"); - - //加密 - Cipher cipher = Cipher.getInstance("IDEA/ECB/ISO10126Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] result = cipher.doFinal(src.getBytes()); - System.out.println("bc idea encrypt : " + Base64.encodeBase64String(result)); - - //解密 - cipher.init(Cipher.DECRYPT_MODE, key); - result = cipher.doFinal(result); - System.out.println("bc idea decrypt : " + new String(result)); - } catch (Exception e) { - e.printStackTrace(); - } - } -} - -``` - -## **5. 非对称加密:** - -- 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥 (privatekey)。 -- 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密; -- 如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。 -- 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。 -### [**RAS:**](https://baike.baidu.com/item/DES) - -RSA是目前最有影响力和最常用的公钥加密算法。它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。 - -#### 代码实现: - -RAS算法实现: - -```java -package com.snailclimb.ks.securityAlgorithm; - -import org.apache.commons.codec.binary.Base64; - -import java.security.*; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.Cipher; - -/** - * Created by humf.需要依赖 commons-codec 包 - */ -public class RSADemo { - - public static void main(String[] args) throws Exception { - Map keyMap = initKey(); - String publicKey = getPublicKey(keyMap); - String privateKey = getPrivateKey(keyMap); - - System.out.println(keyMap); - System.out.println("-----------------------------------"); - System.out.println(publicKey); - System.out.println("-----------------------------------"); - System.out.println(privateKey); - System.out.println("-----------------------------------"); - byte[] encryptByPrivateKey = encryptByPrivateKey("123456".getBytes(), privateKey); - byte[] encryptByPublicKey = encryptByPublicKey("123456", publicKey); - System.out.println(encryptByPrivateKey); - System.out.println("-----------------------------------"); - System.out.println(encryptByPublicKey); - System.out.println("-----------------------------------"); - String sign = sign(encryptByPrivateKey, privateKey); - System.out.println(sign); - System.out.println("-----------------------------------"); - boolean verify = verify(encryptByPrivateKey, publicKey, sign); - System.out.println(verify); - System.out.println("-----------------------------------"); - byte[] decryptByPublicKey = decryptByPublicKey(encryptByPrivateKey, publicKey); - byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, privateKey); - System.out.println(decryptByPublicKey); - System.out.println("-----------------------------------"); - System.out.println(decryptByPrivateKey); - - } - - public static final String KEY_ALGORITHM = "RSA"; - public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; - - private static final String PUBLIC_KEY = "RSAPublicKey"; - private static final String PRIVATE_KEY = "RSAPrivateKey"; - - public static byte[] decryptBASE64(String key) { - return Base64.decodeBase64(key); - } - - public static String encryptBASE64(byte[] bytes) { - return Base64.encodeBase64String(bytes); - } - - /** - * 用私钥对信息生成数字签名 - * - * @param data - * 加密数据 - * @param privateKey - * 私钥 - * @return - * @throws Exception - */ - public static String sign(byte[] data, String privateKey) throws Exception { - // 解密由base64编码的私钥 - byte[] keyBytes = decryptBASE64(privateKey); - // 构造PKCS8EncodedKeySpec对象 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - // KEY_ALGORITHM 指定的加密算法 - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - // 取私钥匙对象 - PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 用私钥对信息生成数字签名 - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initSign(priKey); - signature.update(data); - return encryptBASE64(signature.sign()); - } - - /** - * 校验数字签名 - * - * @param data - * 加密数据 - * @param publicKey - * 公钥 - * @param sign - * 数字签名 - * @return 校验成功返回true 失败返回false - * @throws Exception - */ - public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { - // 解密由base64编码的公钥 - byte[] keyBytes = decryptBASE64(publicKey); - // 构造X509EncodedKeySpec对象 - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); - // KEY_ALGORITHM 指定的加密算法 - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - // 取公钥匙对象 - PublicKey pubKey = keyFactory.generatePublic(keySpec); - Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); - signature.initVerify(pubKey); - signature.update(data); - // 验证签名是否正常 - return signature.verify(decryptBASE64(sign)); - } - - public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得私钥 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 对数据解密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, privateKey); - return cipher.doFinal(data); - } - - /** - * 解密
- * 用私钥解密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] decryptByPrivateKey(String data, String key) throws Exception { - return decryptByPrivateKey(decryptBASE64(data), key); - } - - /** - * 解密
- * 用公钥解密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得公钥 - X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key publicKey = keyFactory.generatePublic(x509KeySpec); - // 对数据解密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, publicKey); - return cipher.doFinal(data); - } - - /** - * 加密
- * 用公钥加密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] encryptByPublicKey(String data, String key) throws Exception { - // 对公钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得公钥 - X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key publicKey = keyFactory.generatePublic(x509KeySpec); - // 对数据加密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - return cipher.doFinal(data.getBytes()); - } - - /** - * 加密
- * 用私钥加密 - * - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { - // 对密钥解密 - byte[] keyBytes = decryptBASE64(key); - // 取得私钥 - PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); - // 对数据加密 - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, privateKey); - return cipher.doFinal(data); - } - - /** - * 取得私钥 - * - * @param keyMap - * @return - * @throws Exception - */ - public static String getPrivateKey(Map keyMap) throws Exception { - Key key = (Key) keyMap.get(PRIVATE_KEY); - return encryptBASE64(key.getEncoded()); - } - - /** - * 取得公钥 - * - * @param keyMap - * @return - * @throws Exception - */ - public static String getPublicKey(Map keyMap) throws Exception { - Key key = keyMap.get(PUBLIC_KEY); - return encryptBASE64(key.getEncoded()); - } - - /** - * 初始化密钥 - * - * @return - * @throws Exception - */ - public static Map initKey() throws Exception { - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - keyPairGen.initialize(1024); - KeyPair keyPair = keyPairGen.generateKeyPair(); - Map keyMap = new HashMap(2); - keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公钥 - keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私钥 - return keyMap; - } - -} -``` - -结果: - -``` -{RSAPublicKey=Sun RSA public key, 1024 bits - modulus: 115328826086047873902606456571034976538836553998745367981848911677968062571831626674499650854318207280419960767020601253071739555161388135589487284843845439403614883967713749605268831336418001722701924537624573180276356615050309809260289965219855862692230362893996010057188170525719351126759886050891484226169 - public exponent: 65537, RSAPrivateKey=sun.security.rsa.RSAPrivateCrtKeyImpl@93479} ------------------------------------ -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkO9PBTOFJQTkzznALN62PU7ixd9YFjXrt2dPOGj3wwhymbOU8HLoCztjwpLXHgbpBUJlGmbURV955M1BkZ1kr5dkZYR5x1gO4xOnu8rEipy4AAMcpFttfiarIZrtzL9pKEvEOxABltVN4yzFDr3IjBqY46aHna7YjwhXI0xHieQIDAQAB ------------------------------------ -MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKQ708FM4UlBOTPOcAs3rY9TuLF31gWNeu3Z084aPfDCHKZs5TwcugLO2PCktceBukFQmUaZtRFX3nkzUGRnWSvl2RlhHnHWA7jE6e7ysSKnLgAAxykW21+Jqshmu3Mv2koS8Q7EAGW1U3jLMUOvciMGpjjpoedrtiPCFcjTEeJ5AgMBAAECgYAK4sxOa8IjEOexv2U92Rrv/SSo3sCY7Z/QVDft2V9xrewoO9+V9HF/7iYDDWffKYInAiimvVl7JM/iSLxza0ZFv29VMpyDcr4TigYmWwBlk7ZbxSTkqLdNwxxldMmEoTn1py53MUm+1V1K3rzNvJjuZaZFAevU7vUnwQwD+JGQYQJBAM9HBaC+dF3PJ2mkXekHpDS1ZPaSFdrdzd/GvHFi/cJAMM+Uz6PmpkosNXRtOpSYWwlOMRamLZtrHhfQoqSk3S8CQQDK1qL1jGvVdqw5OjqxktR7MmOsWUVZdWiBN+6ojxBgA0yVn0n7vkdAAgEZBj89WG0VHPEu3hd4AgXFZHDfXeDXAkBvSn7nE9t/Et7ihfI2UHgGJO8UxNMfNMB5Skebyb7eMYEDs67ZHdpjMOFypcMyTatzj5wjwQ3zyMvblZX+ONbZAkAX4ysRy9WvL+icXLUo0Gfhkk+WrnSyUldaUGH0y9Rb2kecn0OxN/lgGlxSvB+ac910zRHCOTl+Uo6nbmq0g3PFAkAyqA4eT7G9GXfncakgW1Kdkn72w/ODpozgfhTLNX0SGw1ITML3c4THTtH5h3zLi3AF9zJO2O+K6ajRbV0szHHI ------------------------------------ -[B@387c703b ------------------------------------ -[B@224aed64 ------------------------------------ -la4Hc4n/UbeBu0z9iLRuwKVv014SiOJMXkO5qdJvKBsw0MlnsrM+89a3p73yMrb1dAnCU/2kgO0PtFpvmG8pzxTe1u/5nX/25iIyUXALlwVRptJyjzFE83g2IX0XEv/Dxqr1RCRcrMHOLQM0oBoxZCaChmyw1Ub4wsSs6Ndxb9M= ------------------------------------ -true ------------------------------------ -[B@c39f790 ------------------------------------ -[B@71e7a66b - -``` - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" deleted file mode 100644 index ec53e7002f9..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225.md" +++ /dev/null @@ -1,90 +0,0 @@ - -## 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)** - -**分类解析:** -- [(1)斐波那契数列问题和跳台阶问题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(1)斐波那契数列问题和跳台阶问题.md) -- [(2)二维数组查找和替换空格问题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(2)二维数组查找和替换空格问题.md) -- [(3)数值的整数次方和调整数组元素顺序](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(3)数值的整数次方和调整数组元素顺序.md) -- [(4)链表相关编程题](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(4)链表相关编程题.md) -- [(5)栈变队列和栈的压入、弹出序列](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/剑指offer/(5)栈变队列和栈的压入、弹出序列.md) - -> ### [2017校招真题](https://www.nowcoder.com/ta/2017test) - -> ### [华为机试题](https://www.nowcoder.com/ta/huawei) - - -## 公司真题 - -> [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary) - -**解析:** -- [ 网易2018校招编程题1-3](https://github.com/Snailclimb/Java-Guide/tree/master/数据结构与算法/算法题解析/公司真题/网易2018校招编程题1-3.md) - -> [ 网易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) - - - -## 排序算法: -[图解排序算法(一)之3种简单排序(选择,冒泡,直接插入)](http://www.cnblogs.com/chengxiao/p/6103002.html) - -[图解排序算法(二)之希尔排序](https://www.cnblogs.com/chengxiao/p/6104371.html) - -[图解排序算法(三)之堆排序](http://www.cnblogs.com/chengxiao/p/6129630.html) - -[图解排序算法(四)之归并排序](http://www.cnblogs.com/chengxiao/p/6194356.html) - -[图解排序算法(五)之快速排序——三数取中法](http://www.cnblogs.com/chengxiao/p/6262208.html) - - - - - - - - - - - - - - - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2101\357\274\211\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227\351\227\256\351\242\230\345\222\214\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2101\357\274\211\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227\351\227\256\351\242\230\345\222\214\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" deleted file mode 100644 index 87a51d9083e..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2101\357\274\211\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227\351\227\256\351\242\230\345\222\214\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.md" +++ /dev/null @@ -1,127 +0,0 @@ -### 一 斐波那契数列 -#### **题目描述:** -大家都知道斐波那契数列,现在要求输入一个整数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.由a,b假设可以得出总跳法为: 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 - - diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2102\357\274\211\344\272\214\347\273\264\346\225\260\347\273\204\346\237\245\346\211\276\345\222\214\346\233\277\346\215\242\347\251\272\346\240\274\351\227\256\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2102\357\274\211\344\272\214\347\273\264\346\225\260\347\273\204\346\237\245\346\211\276\345\222\214\346\233\277\346\215\242\347\251\272\346\240\274\351\227\256\351\242\230.md" deleted file mode 100644 index 964ed6c9892..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2102\357\274\211\344\272\214\347\273\264\346\225\260\347\273\204\346\237\245\346\211\276\345\222\214\346\233\277\346\215\242\347\251\272\346\240\274\351\227\256\351\242\230.md" +++ /dev/null @@ -1,64 +0,0 @@ -### 一 二维数组查找 -#### **题目描述:** -在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 -#### **问题解析:** -这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路: - -> 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增, - 因此从左下角开始查找,当要查找数字比左下角数字大时。右移 - 要查找数字比左下角数字小时,上移。这样找的速度最快。 - -#### **示例代码:** -```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"); - } - -``` diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2103\357\274\211\346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271\345\222\214\350\260\203\346\225\264\346\225\260\347\273\204\345\205\203\347\264\240\351\241\272\345\272\217.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2103\357\274\211\346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271\345\222\214\350\260\203\346\225\264\346\225\260\347\273\204\345\205\203\347\264\240\351\241\272\345\272\217.md" deleted file mode 100644 index 291569c0375..00000000000 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\347\256\227\346\263\225\351\242\230\350\247\243\346\236\220/\345\211\221\346\214\207offer/\357\274\2103\357\274\211\346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271\345\222\214\350\260\203\346\225\264\346\225\260\347\273\204\345\205\203\347\264\240\351\241\272\345\272\217.md" +++ /dev/null @@ -1,113 +0,0 @@ -### 一 数值的整数次方 -#### **题目描述:** -给定一个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; - } -} -``` - -当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。 -```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的元素开始把该偶数添加到新数组中。 - -#### **示例代码:** -时间复杂度为O(n),空间复杂度为O(n)的算法 -```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 stack1 = new Stack(); - Stack stack2 = new Stack(); - - //当执行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是某栈的压入顺序,序列4,5,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 - -此时栈顶4=4,出栈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 s = new Stack(); - //用于标识弹出序列的位置 - 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(); - } -} -``` \ No newline at end of file diff --git "a/\351\227\262\350\260\210/2018 summary.md" "b/\351\227\262\350\260\210/2018 summary.md" deleted file mode 100644 index 8d82e40b876..00000000000 --- "a/\351\227\262\350\260\210/2018 summary.md" +++ /dev/null @@ -1,174 +0,0 @@ -# 【2018总结】即使平凡,也要热爱自己的生活 - -2018 年于我而讲,虽然平凡,但是自己就是在这平凡的一年也收货了很多东西。不光是自己学到的知识,我觉得 2018 年最大的幸运有三:其一是自己拥有了一份爱情,一份甜蜜的初恋,我真的很幸运遇到我现在的女朋友,愿以后的日子都能有她;其一是在 2018 年,我拥有了一份自己还算满意的 offer,马上就要毕业了,自己也要正式进去社会了;其一是自己在 2018 年的实现了自己的经济独立,这是一件让我很高兴的事情,我觉得大在学生时代实现经济独立还算是一件很不错的事情,花了这么多年父母的辛苦钱,自己也终于能替他们分担一点了。2018 年,感恩父母,感恩老师,感恩朋友,感恩遇到的每个善良的人,同时感恩2018年那个还算努力的自己。2019 继续加油! - -## 一份甜蜜的初恋(分手) - -先说说爱情。我和我的女朋友在一起已经半年多了,准确的来说截止到今天也就是 2018-12-30 号已经 190 天了。 - -![我和傻欢](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/54482393.jpg) - -我俩是异地,我在荆州,她在三亚。相见一面不管是时间上还是经济上对于还是学生的我们来说都甚是不易。见过很多人议论异地恋的种种不好,但是,于我而言,一份好的感情是值得被等待的。“待每一天如初恋,互相尊重彼此生活,共同努力,等老了就退隐山林养老......”,这应该是我和她之间最好的承诺了。 - -## 还算不错的学习收获 - -再来说说学习。这一年还算是让人满意,虽然我也不知道这一年自己到底学到了什么。如果你要问我这一年在学习上做的最满意的事情是什么,我还真不好回答,下面就从下面几个维度来简单谈一谈。 - -### 开源 - -这一年自己在Github上还是挺活跃的,提交了很多的代码和文档,同时也收获了很多的star、follower、pr、issue以及fork。 - -![我的Github概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/41250517.jpg) - -![我的Github贡献概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/33580466.jpg) - -开源的Java学习/面试指南—JavaGuide 某种程度上让我挺满意的,3月份开源 ,到现在的18k+ star 也算是可以用厚积薄发来形容了。但是,JavaGuide 也有很多让我不满意的,在2019年以及以后我也会继续完善。JavaGuide 地址:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) - -![JavaGuide 概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) - -### 技术博客 - -我更新的博客主要都是关于Java方面的,也更新了几篇Python的,有一篇Python的文章竟然在我的CSDN上面阅读霸榜。 -![霸榜的 Python 文章](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/19527688.jpg) - -在这一年,我更新了挺多技术文章,这里就不一一列举了,我贴一下自己觉得不错的文章吧! - -#### 最常见面试题系列 - -- [最最最常见的Java面试题总结——第一周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484252&idx=1&sn=cb160d67fc1c0a95babc464b703df5e7&chksm=fd98553dcaefdc2b18f934957dd950aeaf04e90136099fa2817fffbd1e1df452b581e1caee17&token=1398134989&lang=zh_CN#rd) -- [最最最常见的Java面试题总结——第二周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484282&idx=1&sn=7f986dc3263b6ca0f9e182145fdd40a1&chksm=fd98551bcaefdc0d5aff9577692881dc79765a339ce97e55958e23e1956aa7092dfac44b68f1&token=1398134989&lang=zh_CN#rd) -- [这几道Java集合框架面试题在面试中几乎必问](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484308&idx=1&sn=e3607919aed604be629617f867f46844&chksm=fd9855f5caefdce3f1ee72cb33b9b3bf9899fa2b64bbb92f1e820c0ef3985245b1f7dfc05358&token=1398134989&lang=zh_CN#rd) -- [如果不会这几道多线程基础题,请自觉面壁!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484337&idx=1&sn=d5e953d4b2da7ed37a7f843bfb437ed8&chksm=fd9855d0caefdcc65cb2e5cc0c69d27f785fc41477bcf55fff2cdff3268b0b078eb1a5107726&token=1398134989&lang=zh_CN#rd) -- [值得立马保存的 synchronized 关键字总结](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484355&idx=1&sn=6da29974b6dd1a4aa0d032f44d5fa8de&chksm=fd9855a2caefdcb4c370814baafd4baca27dfccaf609c9edf82370637ba4856176ab143a375e&token=1398134989&lang=zh_CN#rd) -- [【面试必备】源码角度一步一步分析 ArrayList 扩容机制](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=1&sn=1b6155015fedfc9f78fabecc18da7b18&chksm=fd985591caefdc870cb018d27f92e1908b6c6e22816a77ead03c4e44b2f53caec00871172b1f&token=1398134989&lang=zh_CN#rd) -- [Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1398134989&lang=zh_CN#rd) - -#### Github - -- [近几个月Github上最热门的Java项目一览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484188&idx=1&sn=40037de4844f62316465bbe4e910c69c&chksm=fd98557dcaefdc6bedcaeb275aae7c340d46cf6ab0dc96e49c51982f9c53d6a44de283efc9a8&token=1398134989&lang=zh_CN#rd) -- [推荐10个Java方向最热门的开源项目(8月)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484333&idx=1&sn=8c97b029692877a537d55175a8c82977&chksm=fd9855cccaefdcdaffe0558ba5e8dca415495935b0ad1181e6b148b08e1c86ce5d841e9df901&token=1398134989&lang=zh_CN#rd) -- [Github上 Star 数相加超过 7w+ 的三个面试相关的仓库推荐](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484644&idx=1&sn=5016caaf97e498b76de2189e3f55e9dc&chksm=fd985285caefdb93f4e3c7545d30edac6ad31b99f1fcc4503350101f0b20bba9a9705ed7d124&token=1398134989&lang=zh_CN#rd) -- [11月 Github Trending 榜最热门的 10 个 Java 项目](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=1&sn=86e35dfea1478221b6d14a263e88ac89&chksm=fd98535bcaefda4d4f03bf0cd2e0a8fd9f44b1a2b118457a0c8b3de2ff8a1f4c4b7cd083f40e&token=1398134989&lang=zh_CN#rd) -- [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=1398134989&lang=zh_CN#rd) - - -#### 备战面试系列 - -- [可能是一份最适合你的后端面试指南(部分内容前端同样适用)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484529&idx=1&sn=9c7a3d6ad124affcadc19b0ff49bf68a&chksm=fd985210caefdb0615a9643fa698cb6267e89562730423841d942cde17ec9c1280dfc3a2b933&token=1398134989&lang=zh_CN#rd) -- [【备战春招/秋招系列1】程序员的简历就该这样写](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484573&idx=1&sn=8c5965d4a3710d405d8e8cc10c7b0ce5&chksm=fd9852fccaefdbea8dfe0bc40188b7579f1cddb1e8905dc981669a3f21d2a04cadceafa9023f&token=1990180468&lang=zh_CN#rd) -- [【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484578&idx=1&sn=eea72d80a2325257f00aaed21d5b226f&chksm=fd9852c3caefdbd52dd8a537cc723ed1509314401b3a669a253ef5bc0360b6fddef48b9c2e94&token=1990180468&lang=zh_CN#rd) -- [【备战春招/秋招系列3】Java程序员必备书单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484592&idx=1&sn=6d9731ce7401be49e97c1af6ed384ecc&chksm=fd9852d1caefdbc720a361ae65a8ad9d53cfb4800b15a7c68cbdc630b313215c6c52e0934ec2&token=1990180468&lang=zh_CN#rd) -- [【备战春招/秋招系列】美团面经总结基础篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484601&idx=1&sn=4907b7fef0856791c565d49d788ba8cc&chksm=fd9852d8caefdbce88e51c0a10a4ec77c97f382fd2af4a840ea47cffc828bfd0f993f50d5f0d&token=1895808268&lang=zh_CN#rd) -- [【备战春招/秋招系列】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=1895808268&lang=zh_CN#rd) -- [【备战春招/秋招系列】美团Java面经总结终结篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484668&idx=1&sn=9d4631588393075d9c453f307410f0cd&chksm=fd98529dcaefdb8b5497d1f161834af6917c33ea3d305eb41872e522707fa94218769ca60101&token=1398134989&lang=zh_CN#rd) -- [GitHub 上四万 Star 大佬的求职回忆](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484739&idx=1&sn=25cf5b36090f69299150663bdccfeec2&chksm=fd985322caefda34df0734efa607114704d1937f083aee2230b797d1f5aa04f7d13bf2f81dc5&token=1398134989&lang=zh_CN#rd)(非原创) - - -#### 并发编程面试必备 - -- [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&scene=21#wechat_redirect) -- [并发编程面试必备:JUC 中的 Atomic 原子类总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484553&idx=1&sn=aca9fa19f723206eff7e33a10973a887&chksm=fd9852e8caefdbfe7180c34f83bbb422a1a0bef1ed44b1e84f56924244ea3fd2da720f25c6dd#rd) -- [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484559&idx=1&sn=28dae85c38c4c500201c39234d25d731&chksm=fd9852eecaefdbf80cc54a25204e7c7d81170ce659acf92b7fa4151799ca3d0d7df2225d4ff1#rd) -- [并发编程面试必备:BATJ都爱问的多线程面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484564&idx=1&sn=d8467fdc5c1b3883e9b99485f7b0fb9a&chksm=fd9852f5caefdbe364d1c438865cff84acd8f40c1c9e2f9f5c8fef673b30f905b4c5f5255368&token=1398134989&lang=zh_CN#rd) - -#### 虚拟机 - -- [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=1398134989&lang=zh_CN#rd) -- [搞定 JVM 垃圾回收就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484328&idx=1&sn=214f5e18a6afa096eb552fd8627e0cea&chksm=fd9855c9caefdcdf70c746c74d31f65bbb109eedaea0cfe311a1e10af666047df59ff04c873b&token=1398134989&lang=zh_CN#rd) - -#### Spring Boot - -- [超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1398134989&lang=zh_CN#rd) -- [基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=2&sn=9be4636dd9a416b46f9029df68fad232&chksm=fd98535bcaefda4dccf14a286a24fcd2b3d4ab0d0e4d89dfbc955df99d2b06a1e17392b3c10b&token=1398134989&lang=zh_CN#rd) -- [新手也能实现,基于SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484737&idx=1&sn=e39693d845f022d689437ee58948ef6a&chksm=fd985320caefda36d5ab8abd52f5516c11cc5d1104608695bcea5909602b28dc40c132d6d46c&token=1398134989&lang=zh_CN#rd) -- [SpringBoot 整合 阿里云OSS 存储服务,快来免费搭建一个自己的图床](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484745&idx=1&sn=dbeec694916d204605929244d48a6b1c&chksm=fd985328caefda3e793170d81433c7c0b7dc1c4a4ae99395cce23b1d1a239482fd5bf1d89bc6&token=1398134989&lang=zh_CN#rd) - -#### 成长 - -- [结束了我短暂的秋招,说点自己的感受](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484516&idx=1&sn=4e2320613e76dd73a130c63beebbc3ca&chksm=fd985205caefdb13b4b611ed3c604d95314d28d567ec0c3b44585b89a7dc3142bcd52bc2d4cb&token=1398134989&lang=zh_CN#rd) -- [保研之路:从双非到南大](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484477&idx=1&sn=3b597e2431611aacca2b5d671a309d85&chksm=fd98525ccaefdb4a7e3742b5958244d453efe26f61f42f9f108190a0c18313f083189f10944e&token=1398134989&lang=zh_CN#rd)(非原创) -- [【周日闲谈】最近想说的几件小事](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484650&idx=1&sn=e97ea1eeebdb5def58bae1949bec9448&chksm=fd98528bcaefdb9d76ac62fd10544f058b1fee4a40ffd06ab9312b7eb3a62f86d67ea653b88a&token=1398134989&lang=zh_CN#rd) -- [这7个问题,可能大部分Java程序员都比较关心吧!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484836&idx=1&sn=a6ada99c9506af01dc3bb472f66c57be&chksm=fd9853c5caefdad3034dbed00cf04412ea990fc05b6168720e6828ae6c90c9a885793acd7a14&token=1398134989&lang=zh_CN#rd) - -#### Docker - -- [可能是把Docker的概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484127&idx=1&sn=70ee95619ec761da884c4f9af3e83194&chksm=fd9854becaefdda81a02bf6cf9bd07a2fc879efa7cefc79691a0d319b501d8572e8bad981d87&token=1398134989&lang=zh_CN#rd) - -#### Linux - -- [后端程序员必备的Linux基础知识](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484157&idx=1&sn=8b47e623e83fb3666bce7c680e4649b8&chksm=fd98549ccaefdd8ad815f3d8eaca86cc7e7245b4f8de1d23897af3017f5fdb3f152734c40f5e&token=1398134989&lang=zh_CN#rd) -- [快速入门大厂后端面试必备的 Shell 编程](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484696&idx=1&sn=d3c1ba5abc10c10ff844cae2109a2628&chksm=fd985379caefda6faff8e050b7dfa1e92fbfe2912e44150cb4ae349aea807836166355062970&token=1398134989&lang=zh_CN#rd) - -#### ZooKeeper - -- [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484379&idx=1&sn=036f5d3defa8a6979afb77acc82a9517&chksm=fd9855bacaefdcacc1462f781b634e5599f2ee9e806bd24297dae4af0e4196a70ca6bbd8c354&token=1398134989&lang=zh_CN#rd) - -#### Redis - -- [redis 总结——重构版](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484450&idx=1&sn=7ee03fa67aecd05c5becd2a8259d3631&chksm=fd985243caefdb554ebab9149e750ac0c819074c57bd208f2d7f097fbc461ed58223e71c05f1&token=1398134989&lang=zh_CN#rd) -- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484478&idx=1&sn=a1250d9b8025cd7cb6fc6a58238ab51e&chksm=fd98525fcaefdb499a027df0138c98d4b02d828f27bd6144a4d40a1c088d340c29dd53d4a026&token=1398134989&lang=zh_CN#rd)(非原创) - -#### 计算机网络 - -- [搞定计算机网络面试,看这篇就够了(补充版)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484289&idx=1&sn=6b556843c60aac9a17b0e7c2e3cd6bca&chksm=fd9855e0caefdcf6af4123b719448c81d90c5442d4052ae01a4698047e226c0c18c14b2cc54a&token=1398134989&lang=zh_CN#rd) - -#### 数据库 - -- [【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1398134989&lang=zh_CN#rd) - -#### 消息队列 - -- [新手也能看懂,消息队列其实很简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484789&idx=1&sn=ba972f0aac39e9a28b29ddf92fc15c18&chksm=fd985314caefda0278235427d43846b6374ff32f4149352dec063287cbf9733b888acbb79923&token=1398134989&lang=zh_CN#rd) -- [一文搞懂 RabbitMQ 的重要概念以及安装](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484796&idx=1&sn=bc56fecb634732669cfe7db8d1820ded&chksm=fd98531dcaefda0b07b8a9c13429ef225d36a6e287c96c53d7aa3dfd65c62ccd60d13b22ebbf&token=1398134989&lang=zh_CN#rd) - -### 读书 - -推荐一下 2018 年看过的书籍,小部分可能2017年也看过一些。 - -#### 已看完 - -- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 -- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 -- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! -- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 -- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 -- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 -- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! -- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 -- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 -- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) -- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 - - -#### 未看完 - - -- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 -- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 -- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 -- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 -- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 -- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 -- [《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入门书籍! -- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 -- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 - -## 一些个人愚见 - -### 关于读书 - -不知道大家收藏栏是不是和我一样收藏了很多文章,但是有多少篇是你真真认真看的呢?或者那几篇你认真看之后,经过一个月之后还记得这篇文章的大概内容。现在这个社会真是一个信息爆炸的社会,我个人真的深有感触,就在刚刚我还取消关注了好多微信公众号,很多公众号给我推的文章都有好几十篇了,但是我一篇都没有看,所以索性取消关注,省个位置。我个人觉得遇到好的文章,我们不光要读,而且要记录下来。就拿我本人来说,我平时喜欢用 OneNote 来记录学习笔记以及其他我感觉重要的事情比如重要人的生日啦这些。每当遇到自己喜欢的文章的时候,我都先会把文章的地址保存到我分好类的笔记本上,我会先进行第一遍阅读,第一遍我会读的很仔细,如果晦涩难懂的话我会先快速把总体看一遍,然后在细细品读。一般第二遍的时候我就会在笔记本上记录这篇文章的一些要点,以便我日后看到这些要点可以快速回忆起整篇文章的内容。如果某篇文章的知识点太过庞大的话,我会去选择采用思维导图的方式展示要点。看视频一样,看教学视频的话,如果觉得老师讲的不错,我们不妨记录下来,Onenote 或者有道云笔记都行,记录大概,够我们日后回忆就好。 - -### 关于学习 - -做事不要有功利性,我最早在掘金写文章,其实也只是为了记录自己的学习,没想到会有人喜欢自己的文章,另外我课外学的很多东西,我自己也不清楚以后工作会不会用到,反正我自己感觉即然自己感兴趣,那么就去学吧。我相信,很多东西可能暂时带给你不了带多实质性的帮助,但是总有一天它会对你有帮助。如果感到迷茫的话,就做好眼前的事(拿我们班主任的话说,如果你感到迷茫,你就学好现在的专业知识就好了),我觉得没毛病。 - -### 关于个人 - -在生活中一定要保持谦虚,保持谦虚,保持谦虚,时刻都要有反省的准备,你要记住学无止境,永远不要满足现在的现状。另外,就是一定要掌控好自己的时间,多留点时间给父母亲人,以及那些自己在乎的人。如果对别人很在乎的话,不要去装作不在乎,因为这样真的不是太好,虽然我之前也会这样,很多时候撰写的消息,最后没发出去。 - -## 最后分享一句话 - -分享给大家,我笔记本里一直保存的杨绛老先生的一句话:“我们曾如此渴望命运的波澜,到最后才发现:人生最曼妙的风景,竟是内心的淡定与从容……我们曾如此期盼外界的认可,到最后才知道:世界是自己的,与他人毫无关系!”。 diff --git "a/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" "b/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" deleted file mode 100644 index a2b47de8eee..00000000000 --- "a/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" +++ /dev/null @@ -1,93 +0,0 @@ - - -# 秋招历程流水账总结 - -笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks. - -![今天去签约在门外拍的照片](https://images.gitbook.cn/1433af10-d5f7-11e8-841a-4f0b0cc7be7b) - -从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。 - -零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。 - -网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。 - - -我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。 - -说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。 - - - -中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。 - -这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。 - -![思特沃克可爱的招聘官网](https://images.gitbook.cn/83f765e0-d5f6-11e8-9c1a-919e09988420) - - -# 关于面试一些重要的问题总结 -另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。 - -### 面试前 - -**如何准备** - - -运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: - -1. 自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!) -2. 自己面试中可能涉及哪些知识点、那些知识点是重点。 -3. 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) -4. 自己的简历该如何写。 - - - -另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。 - -**关于着装** - -穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 - -**关于自我介绍** - -如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。 - - - -**提前准备** - -面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 - - -### 面试中 - -面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题: - -- SpringMVC 工作原理 -- 说一下自己对 IOC 、AOP 的理解 -- Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解 -- Spring Bean 的作用域和生命周期了解吗 -- Spring 事务中的隔离级别 -- Spring 事务中的事务传播行为 -- 手写一个 LRU 算法 -- 知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排 -- String 为什么是不可变的?String为啥要设计为不可变的? -- Arraylist 与 LinkedList 异同 -- HashMap的底层实现 -- HashMap 的长度为什么是2的幂次方 -- ConcurrentHashMap 和 Hashtable 的区别 -- ConcurrentHashMap线程安全的具体实现方式/底层具体实现 -- 如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。 -- 一些简单的 Linux 命令。 -- 为什么要用 消息队列 -- 关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。 -- 关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。 - - -### 面试后 - -如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! - - - diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" "b/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" deleted file mode 100644 index 229f6d2bdef..00000000000 --- "a/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" +++ /dev/null @@ -1,4 +0,0 @@ -- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2018-12.md) -- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2019-1.md) -- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2019-2.md) - diff --git "a/\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" "b/\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" deleted file mode 100644 index f14ee33a211..00000000000 --- "a/\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" +++ /dev/null @@ -1,89 +0,0 @@ -下面是个人阅读书籍的部分清单,我比较建议阅读的书籍前都加上了:thumbsup: 表情。 -> ### 核心基础知识 - -- :thumbsup: [《图解HTTP》](https://book.douban.com/subject/25863515/) - - 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 - -> ### Java相关 - -- :thumbsup: [《Head First Java.第二版》](https://book.douban.com/subject/2000732/) - - 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 - -- [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/) - - Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 - -- [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/) - - 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 - -- :thumbsup: [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/) - - 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 - -- :thumbsup: [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/) - - 这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 - -- :thumbsup: [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/) - - 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 -- :thumbsup: [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/) - - 豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 - -- [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/) - - 很杂,我只看了前面几章,不太推荐阅读。 - -- :thumbsup: [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/) - - 神书!神书!神书!建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 - -> ### JavaWeb相关 - -- :thumbsup: [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/) - - 感觉还行,涉及的东西也蛮多,推荐阅读。 - -- :thumbsup: [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/) - - 不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 - -- [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/) - - 当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 - -- :thumbsup: [《Redis实战》](https://book.douban.com/subject/26612779/) - - 如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 - -> ### 架构相关 - -- :thumbsup: [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/) - - 这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 - -- [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/) - - 很一般的书籍,我就是当做课后图书来阅读的。 - -> ### 代码优化 - -- :thumbsup: [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/) - - 豆瓣 9.1 分,重构书籍的开山鼻祖。 -> ### linux操作系统相关 -- :thumbsup:[<>](https://book.douban.com/subject/25900403/) :thumbsup: [<>](https://book.douban.com/subject/1500149/) - - 对于理解linux操作系统原理非常有用,同时可以打好个人的基本功力,面试中很多公司也会问到linux知识。 -> ### 课外书籍 - -《技术奇点》 :thumbsup:《追风筝的人》 :thumbsup:《穆斯林的葬礼》 :thumbsup:《三体》 《人工智能——李开复》 -:thumbsup:《活着——余华》 - - - - diff --git "a/\351\227\262\350\260\210/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" "b/\351\227\262\350\260\210/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" deleted file mode 100644 index fa39c7c1150..00000000000 --- "a/\351\227\262\350\260\210/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" +++ /dev/null @@ -1,63 +0,0 @@ -本文主要是作者读安晓辉老师的《程序员程序员职场进阶 32 讲 》中关于“选择技术方向都要考虑哪些因素”这部分做的一些笔记和自己的思考。在这里分享给各位! - -### 选择一种技术可能会考虑到的决定因素 - -1. 就业机会 - - 选择一门就业面广的技术还是比较重要的。我的很多学PHP的同学现在都在培训班学Java,真的!!! -2. 难易程度 - - 我当时是在C/C++语言与Java中选择了Java,因为我感觉Java学起来确实要比C++简单一些。 -3. 个人兴趣 - - 兴趣是你能坚持下来的一个很重要的条件。 -4. 薪资水平 - - 薪资虽然不是人的唯一追求,但是一定是必备的追求。 -5. 发展前景 - - 你肯定不愿意看到这种情况发生:选择了一门技术,结果一年后它就没人用、没市场了。所以我们在选择时就要考虑这一点,做一些预判。 - - 选择技术时存在两种考虑:一种是选择稳定的、经典的技术;一种是卡位将来的市场缺口,选择将来可能需要用到的技术。 -6. 他人推荐 - - 我们在懵懵懂懂的时候,往往最容易听从别人的推荐,然后选择某种技术。 -7. 相近原则 - - 当我们已经掌握了一些技术,要学习新技术时,就可以根据一种新技术是否和自己已经掌握的技术比较接近来判断选择。相近的技术,学起来会更容易上手。 -8. 互补原则 - - 和相近性类似,互补性也常用在拓展我们技术能力的情景下。它指的是,有一些技术可以和你已经掌握的技术互相补充,组合在一起,形成更完整、更系统的技术图谱,给你带来更大的竞争力。关于相近原则与互补原则,我们也会在后面的文章里具体解读。 -9. 团队技术图谱 - - 我觉得这个可能就是团队开发过程中的需要。比如在做一个项目的时候,这个项目需要你去学习一下某个你没有接触过的新技术。 - -### 入行时如何选择技术方向 - - 为了明确自己的求职目标,可以问问自己下面的问题: -- 我想在哪个城市工作? -- 我想在哪些行业、领域发展? -- 我想去什么样的公司? -- 我想做什么样的产品? - -另外你要知道的是热门技术会有更多机会,相应竞争压力也会更大,并不能保证你找到合适的工作。 -冷门技术,机会相对较少,而且机会相对确定 。 - -### 构建技能树时如何选择技术方向 - -当我们过了专项能力提升的初级阶段之后,就应该开始构建自己的技能体系了。在为搭建技能树而选择技术时,通常考虑下面两个原则: -- 相近原则 -- 互补原则 - -“学习技术时一定要学对自己以后发展有用的技术”是我经常对自己强调的,另外我觉得很误导人同时也很错误的一个思想是:“只要是技术学了就会有用的”,这句话在我刚学编程时经常听到有人对我说。希望大家不要被误导,很多技术过时了就是过时了,没有必要再去花时间学。 - -我觉得相近原则和互补原则互补原则就是你主精和自己技术方向相同的的东西或者对自己技术领域有提升的东西。比如我目前暂时选择了Java为我的主要发展语言,所以我就要求自己大部分时间还是搞和Java相关的东西比如:Spring、SpingBoot、Dubbo、Mybatis等等。但是千万不要被语言所束缚,在业余时间我学的比较多的就是Python以及JS、C/C++/C#也会偶尔接触。因为我经常会接触前端另外我自己偶尔有爬虫需求或者需要用Python的一些第三库解决一些问题,所以我业余学Pyton以及JS就比较多一点,我觉得这两门技术也是对我现有技术的一个补充了。 - - -### 技术转型时的方向选择 - -我觉得对于技术转型主要有一下几点建议 - -- 与自己当前技术栈跨度不太大的领域,比如你做安卓的话转型可以选择做Java后端。 -- 真正适合自己去做的,并不是一味看着这个领域火了(比如人工智能),然后自己就不考虑实际的去转型到这个领域里去。 -- 技术转型方向尽量对自己以后的发展需要有帮助。 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" deleted file mode 100644 index 4901f88932d..00000000000 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/JavaInterviewGithub.md" +++ /dev/null @@ -1,78 +0,0 @@ -最近浏览 Github ,收藏了一些还算不错的 Java面试/学习相关的仓库,分享给大家,希望对你有帮助。我暂且按照目前的 Star 数量来排序。 - -本文由 SnailClimb 整理,如需转载请联系作者。 - -### 1. 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: 31k -- 介绍: 软件工程技术面试个人指南。 -- 概览: - - ![interviews](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/47663247.jpg) - -### 2. JCSprout - -- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout) -- star: 17.7k -- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。 -- 概览: - - ![ JCSprout](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/85903384.jpg) - -### 3. JavaGuide - -- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) -- star: 17.4k -- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 -- 概览: - - ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) - -### 4. technology-talk - -- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk) -- star: 4.2k -- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。 - -### 5. fullstack-tutorial - -- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial) -- star: 2.8k -- 介绍: Full Stack Developer Tutorial,后台技术栈/全栈开发/架构师之路,秋招/春招/校招/面试。 from zero to hero。 -- 概览: - - ![fullstack-tutorial](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/67104534.jpg) - -### 6. java-bible - -- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible) -- star: 1.9k -- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。 -- 概览: - - ![ java-bible](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/90223588.jpg) - -### 7. EasyJob - -- Github地址:[https://github.com/it-interview/EasyJob](https://github.com/it-interview/EasyJob) -- star: 1.9k -- 介绍: 互联网求职面试题、知识点和面经整理。 - -### 8. advanced-java - -- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) -- star: 1k -- 介绍: 互联网 Java 工程师进阶知识完全扫盲 - -### 9. 3y - -- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y) -- star: 0.4 k -- 介绍: Java 知识整合。 - -除了这九个仓库,再推荐几个不错的学习方向的仓库给大家。 - -1. Star 数高达 4w+的 CS 笔记-CS-Notes:[https://github.com/CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes) -2. 后端(尤其是Java)程序员的 Linux 学习仓库-Linux-Tutorial:[https://github.com/judasn/Linux-Tutorial](https://github.com/judasn/Linux-Tutorial)( Star:4.6k) -3. 两个算法相关的仓库,刷 Leetcode 的小伙伴必备:①awesome-java-leetcode:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode);②LintCode:[https://github.com/awangdev/LintCode](https://github.com/awangdev/LintCode) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" deleted file mode 100644 index 34f495fd798..00000000000 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/books.md" +++ /dev/null @@ -1,66 +0,0 @@ - -### 核心基础知识 - -- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 -- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 -- [《数据结构与算法分析:C语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。 -- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! -- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 - - - - -### Java相关 - -- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 -- [《Head First Java.第二版》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 -- [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/): Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 -- [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 -- [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 -- [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 -- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 -- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 -- [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/): 很杂,我只看了前面几章,不太推荐阅读。 -- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 -- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! -- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 - -### JavaWeb相关 - -- [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。 -- [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3 -,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 -- [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/)(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 -- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 -- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) -- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 -- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 -- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 -- [《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入门书籍! - -### 操作系统 - -- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 - -### 架构相关 - -- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 -- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 -- [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/):很一般的书籍,我就是当做课后图书来阅读的。 - -### 代码优化 - -- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 - -### 课外书籍 - -- 《追风筝的人》(推荐) -- 《穆斯林的葬礼》 (推荐) -- 《三体》 (推荐) -- 《活着——余华》 (推荐) - - - -