From fdc3ec2ccba572446a7f98c4d4bd4f6ef1160253 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 12 Aug 2020 16:10:52 +0800 Subject: [PATCH 001/366] =?UTF-8?q?=E7=99=BE=E5=BA=A6=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...76\345\272\246\351\235\242\347\273\217.md" | 40 +++++++++++++++++++ README.md | 3 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 "Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" diff --git "a/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" "b/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" new file mode 100644 index 00000000..08fb4791 --- /dev/null +++ "b/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" @@ -0,0 +1,40 @@ + +一面 +时间:8.12 12:00 时长:110分钟 + + +1. 自我介绍 +2. 项目介绍(20min) +3. 为什么采用微服务架构? +4. 为什么用Dubbo? +5. 为什么用Redis? +6. 谈一谈Redis的持久化 +7. Redis与MySQL双写一致性方案 +8. 谈一谈zk? +9. zk如何实现分布式锁? +10. zk的leader选举过程 +11. zk的zab算法讲一下 +12. ArrayList和LinkedList的区别 +13. ArrayList的add源码 +14. 讲一下生产者消费者模型(讲了三种方案) +15. 生产者为什么用while? +16. 说一下synchronized的修饰范围以及锁的部门? +17. 了解volatile吗?都有哪些特性?分别举例子 +18. JMM内存模型 +19. JVM运行时区域 +20. 堆、方法区和虚拟机栈分别是什么?存了什么? +21. 方法区的版本变化以及内部常量池的变化? +22. 堆为什么要分成新生代和老年代 +23. 新生代为什么又分为Eden、s0和s1 +24. 标记算法? +25. 可达性分析工作原理?哪些可以作为GC Roots根 +26. 虚拟机栈局部变量表存什么? +27. class加载过程讲一下? +28. new对象,发生了什么过程? 讲一下 +29. 写一道SQL题,不记得了,大致还要用到join和分组求和。 +30. left join right join inner join的区别 +31. TCP三次握手 +32. 写一道题:求一个数组的所有递增子序列。 +33. 反问 + +总结:我倾其所有,想睡个午觉。 \ No newline at end of file diff --git a/README.md b/README.md index ef3c331c..45a54a85 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ - [京东面经](/Interview/mianjing/jingdong/本人京东面经.md) - [字节面经](/Interview/mianjing/zijie/本人字节面经.md) - [用友SP面经](/Interview/mianjing/yongyou/个人用友sp面经.md) +- [百度面经](/Interview/mianjing/baidu/本人百度面经.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) @@ -52,7 +53,7 @@ - [谈谈死锁](/Interview/sad/谈谈死锁.md) - [生产者消费者模型](/Interview/sad/生产者消费者模型.md) - [类文件结构](/Interview/sad/类文件结构.md) -- [类加载器](/Interview/sad/类加载器.md) +- [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) ## 刷题系列 From 1c0586ea23ed374ba86583bb342f111cfe471e1f Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 12 Aug 2020 20:58:21 +0800 Subject: [PATCH 002/366] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=9B=86=E5=90=88?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...10\350\260\210\351\233\206\345\220\210.md" | 109 ++++++++++++++++-- Interview/src/Main.java | 3 - 2 files changed, 100 insertions(+), 12 deletions(-) diff --git "a/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" "b/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" index c0a45089..47a07b6c 100644 --- "a/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" +++ "b/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" @@ -45,7 +45,7 @@ - 线程B也发现需要大小为10,也可以容纳,返回。 - 好了,**问题来了哈** - 线程A开始进行设置值操作,elementData[size++] = e操作。此时size变为10。 -- 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为10 +- 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为9 - 于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 null @@ -157,16 +157,16 @@ HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以 ### 1.7 #### segment -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 `ReentrantLock`。 +- 唯一的区别(和HashMap)就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 +- ConcurrentHashMap 采用了分段锁技术(相对hashTable降低锁的粒度),其中 Segment 继承于 `ReentrantLock`(可能还会扯AQS)。 - 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 - **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** #### put - 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 +- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` **自旋获取锁**。 - 尝试获取自旋锁 - - 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 + - 如果**重试的次数**达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 总的来说: @@ -181,10 +181,10 @@ HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以 - ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** #### size -在 JDK1.7 中,第一种方案他会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给每个 Segment 加上锁,然后计算 ConcurrentHashMap 的 size 返回 +在 JDK1.7 中,第一种方案他会**使用不加锁的模式**去尝试多次计算 ConcurrentHashMap 的 size,最多**三次**,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给**每个 Segment 加上锁**,然后计算 ConcurrentHashMap 的 size 返回 ### 1.8 -1.7 查询遍历链表效率太低。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性 +1.7 查询遍历链表效率太低(种种原因)。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性(会扯1.6对synchronized的优化) #### put - 根据key计算出hashcode @@ -202,14 +202,14 @@ HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以 - 就不满足那就按照链表的方式遍历获取值。 #### size -ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是迭代 counterCells 来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 addCount() 方法。 +ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是迭代 counterCells 来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 **addCount()** 方法。 在addCount()方法中: - 如果 counterCells == null, 则对 baseCount 做 CAS 自增操作。 - 如果并发导致 baseCount CAS 失败了使用 counterCells。 - 如果counterCells CAS 失败了,在 fullAddCount 方法中,会继续死循环操作,直到成功。 - CounterCell使用了 @sun.misc.Contended 标记的类 -> 缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。 +> 缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,**如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享**。 实际上: - JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。 @@ -235,3 +235,94 @@ System.out.println(map.put(1, o));// java.lang.Object@610455d6 +## TreeSet底层 + +TreeSet底层则采用NavigableMap这个接口来保存TreeSet集合,而实际上NavigableMap只是一个接口,实际上TreeSet还是用TreeMap来保存set元素。 + +```java +TreeSet(NavigableMap m) { + this.m = m; +} + + public TreeSet() { + this(new TreeMap()); + + } +``` + +TreeMap采用一种被称为“红黑树”的排序二叉树来保存Map中的的每个Entry——每个Entry都被当做红黑树的一个节点来对待;TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。 + +## LinkedHashMap + +[可以参考](https://crossoverjie.top/2018/02/06/LinkedHashMap/) + +一般说完这个,可能让你手撸LRU,你可以撸个伪代码即可。 + +```java +// 双向链表+HashMap,Java中的LinkedHashMap就实现了该算法。 +// get +public int get(int key) { + if (map.containsKey(key)) { + Node n = map.get(key); // 获取内存中存在的值,比如A + remove(n); //使用链表的方法,移除该节点 + setHead(n); //依然使用链表的方法,将该节点放入头部 + return n.value; + } + return -1; +} +// put +public void set(int key, int value) { + if (map.containsKey(key)) { + Node old = map.get(key); + old.value = value; + remove(old); // 移除旧节点 + setHead(old); // 放到队头 + } else { + Node created = new Node(key, value); + if (map.size() >= capacity) { + map.remove(end.key); // clear该key + remove(end); //链表也是依次 + setHead(created); // 将created放入队头 + } else { + setHead(created); // 如果没满,直接放入队头 + } + map.put(key,created); + } +} +``` + +```java +//lc: 146. LRU缓存机制 +class LRUCache { + private int cap; + private Map map = new LinkedHashMap<>(); + public LRUCache(int capacity) { + this.cap = capacity; + } + + public int get(int key) { + if (map.containsKey(key)) { + int value = map.get(key); + // 查一次,就将查到到仍在队尾 + map.remove(key); + map.put(key,value); + return value; + } + return -1; + } + + public void put(int key, int value) { + if (map.containsKey(key)) { + map.remove(key); + } else if (map.size() == cap) { + // 满了 + Iterator> iterator = map.entrySet().iterator(); + iterator.next(); + iterator.remove(); + } + map.put(key, value); + } +} +``` + + diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 7b09f2ef..0f0b712a 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,6 +1,3 @@ - - - public class Main { public static void main(String[] args) { From 81e6ebdec8f38f4044656aab1ed55233867723de Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 13 Aug 2020 15:38:54 +0800 Subject: [PATCH 003/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/wangyi.md | 52 ++++++- Interview/mianjing/baidu/my.md | 144 ++++++++++++++++++ ...76\345\272\246\351\235\242\347\273\217.md" | 40 ----- .../mianjing/jingdong/my.md | 0 Interview/mianjing/wangyi/my.md | 18 +++ .../mianjing/yongyou/my.md | 0 .../mianjing/zhaoyin/my.md | 0 .../mianjing/zijie/my.md | 0 ...10\350\260\210\346\255\273\351\224\201.md" | 10 +- Interview/src/Main.java | 4 +- README.md | 11 +- 11 files changed, 230 insertions(+), 49 deletions(-) create mode 100644 Interview/mianjing/baidu/my.md delete mode 100644 "Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" rename "Interview/mianjing/jingdong/\346\234\254\344\272\272\344\272\254\344\270\234\351\235\242\347\273\217.md" => Interview/mianjing/jingdong/my.md (100%) create mode 100644 Interview/mianjing/wangyi/my.md rename "Interview/mianjing/yongyou/\344\270\252\344\272\272\347\224\250\345\217\213sp\351\235\242\347\273\217.md" => Interview/mianjing/yongyou/my.md (100%) rename "Interview/mianjing/zhaoyin/\346\234\254\344\272\272\346\213\233\351\223\266\351\235\242\347\273\217.md" => Interview/mianjing/zhaoyin/my.md (100%) rename "Interview/mianjing/zijie/\346\234\254\344\272\272\345\255\227\350\212\202\351\235\242\347\273\217.md" => Interview/mianjing/zijie/my.md (100%) diff --git a/Interview/bishi/wangyi.md b/Interview/bishi/wangyi.md index 5ef84c5d..baad09fb 100644 --- a/Interview/bishi/wangyi.md +++ b/Interview/bishi/wangyi.md @@ -1136,7 +1136,7 @@ public class Main { -``` + ## 最大乘积 @@ -1545,6 +1545,56 @@ class Main3 { } ``` +```java +import java.util.*; + +public class Main3 { + static Set> marked = new HashSet<>(); + static List listSum = new ArrayList<>(); + static int sum = 0; + public static void main(String[] args) { + int[] nums = {5, 15, 20, 40, 60, 80, 100, 5, 5, 5, 5, 5, 5, 10, 10, 25}; + sum = Arrays.stream(nums).sum(); + Arrays.sort(nums); + for (int i = 2; i <= nums.length; i++) { + dfs(0, nums, new ArrayList(), i); + } + System.out.println(listSum.stream().min(Comparator.comparingInt(Integer::intValue)).get()); + } + + private static void dfs(int start, int[] nums, ArrayListlist, int size) { + if (list.size() == size) { + if (!marked.contains(list)) + marked.add(list); + if (canPartition(list.stream().mapToInt(Integer::valueOf).toArray())) { + listSum.add(sum - list.stream().mapToInt(Integer::valueOf).sum()); + return; + } + } + for (int i = start; i < nums.length; i++) { + list.add(nums[i]); + dfs(i + 1, nums, list, size); + list.remove(list.size() - 1); + } + } + + public static boolean canPartition(int[] nums) { + int sum = Arrays.stream(nums).sum(); + if (sum % 2 != 0) return false; + int w = sum / 2; + boolean[] dp = new boolean[w + 1]; + dp[0] = true; + for (int num : nums) { + for (int i = w; i >= num; i--) { + dp[i] = dp[i] || dp[i - num]; + } + } + return dp[w]; + } + +} +``` + --- diff --git a/Interview/mianjing/baidu/my.md b/Interview/mianjing/baidu/my.md new file mode 100644 index 00000000..bc27e2ae --- /dev/null +++ b/Interview/mianjing/baidu/my.md @@ -0,0 +1,144 @@ + +一面 +时间:8.12 12:00 时长:110分钟 + + +1. 自我介绍 +2. 项目介绍(20min) +3. 为什么采用微服务架构? +4. 为什么用Dubbo? +5. 为什么用Redis? +6. 谈一谈Redis的持久化 +7. Redis与MySQL双写一致性方案 +8. 谈一谈zk? +9. zk如何实现分布式锁? +10. zk的leader选举过程 +11. zk的zab算法讲一下 +12. ArrayList和LinkedList的区别 +13. ArrayList的add源码 +14. 讲一下生产者消费者模型(讲了三种方案) +15. 生产者为什么用while? +16. 说一下synchronized的修饰范围以及锁的部门? +17. 了解volatile吗?都有哪些特性?分别举例子 +18. JMM内存模型 +19. JVM运行时区域 +20. 堆、方法区和虚拟机栈分别是什么?存了什么? +21. 方法区的版本变化以及内部常量池的变化? +22. 堆为什么要分成新生代和老年代 +23. 新生代为什么又分为Eden、s0和s1 +24. 标记算法? +25. 可达性分析工作原理?哪些可以作为GC Roots根 +26. 虚拟机栈局部变量表存什么? +27. class加载过程讲一下? +28. new对象,发生了什么过程? 讲一下 +29. 写一道SQL题,不记得了,大致还要用到join和分组求和。 +30. left join right join inner join的区别 +31. TCP三次握手 +32. 写一道题:求一个数组的所有递增子序列。 +33. 反问 + +总结:我倾其所有,想睡个午觉。 + +```java +import java.util.ArrayList; +import java.util.List; + +public class Main { + + static List> sub = new ArrayList<>(); + static List> ret = new ArrayList<>(); + + public static void main(String[] args) { + int[] nums = {4, 6, 9, 8, 8}; + subsets(nums); + isInc(); + for (List list : ret) { + System.out.println(list.toString()); + } + } + + public static void isInc() { + for (List list : sub) { + for (int i = 1; i < list.size(); i++) { + if (list.get(i) < list.get(i - 1)) + break; + if (i == list.size() - 1) + ret.add(new ArrayList<>(list)); + } + } + } + + public static List> subsets(int[] nums) { +// Arrays.sort(nums); + for (int i = 2; i <= nums.length; i++) { + dfs(0, nums, new ArrayList(), i, new boolean[nums.length]); + } + return sub; + } + + private static void dfs(int start, int[] nums, ArrayList list, int size, boolean[] marked) { + if (list.size() == size) { + sub.add(new ArrayList<>(list)); + return; + } + for (int i = start; i < nums.length; i++) { + if (i != 0 && nums[i] == nums[i - 1] && !marked[i - 1]) + continue; + marked[i] = true; + list.add(nums[i]); + dfs(i + 1, nums, list, size, marked); + list.remove(list.size() - 1); + marked[i] = false; + } + } +} +``` + +```java +import java.util.*; + +public class Main { + + static Set> sub = new LinkedHashSet<>(); + static List> ret = new ArrayList<>(); + + public static void main(String[] args) { + int[] nums = {4, 6, 9, 8, 8}; + subsets(nums); + isInc(); + for (List list : ret) { + System.out.println(list.toString()); + } + } + + public static void isInc() { + for (List list : sub) { + for (int i = 1; i < list.size(); i++) { + if (list.get(i) < list.get(i - 1)) + break; + if (i == list.size() - 1) + ret.add(new ArrayList<>(list)); + } + } + } + + public static Set> subsets(int[] nums) { + for (int i = 2; i <= nums.length; i++) { + dfs(0, nums, new ArrayList(), i); + } + return sub; + } + + private static void dfs(int start, int[] nums, ArrayList list, int size) { + if (list.size() == size) { + sub.add(new ArrayList<>(list)); + return; + } + for (int i = start; i < nums.length; i++) { + list.add(nums[i]); + dfs(i + 1, nums, list, size); + list.remove(list.size() - 1); + } + } +} +``` \ No newline at end of file diff --git "a/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" "b/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" deleted file mode 100644 index 08fb4791..00000000 --- "a/Interview/mianjing/baidu/\346\234\254\344\272\272\347\231\276\345\272\246\351\235\242\347\273\217.md" +++ /dev/null @@ -1,40 +0,0 @@ - -一面 -时间:8.12 12:00 时长:110分钟 - - -1. 自我介绍 -2. 项目介绍(20min) -3. 为什么采用微服务架构? -4. 为什么用Dubbo? -5. 为什么用Redis? -6. 谈一谈Redis的持久化 -7. Redis与MySQL双写一致性方案 -8. 谈一谈zk? -9. zk如何实现分布式锁? -10. zk的leader选举过程 -11. zk的zab算法讲一下 -12. ArrayList和LinkedList的区别 -13. ArrayList的add源码 -14. 讲一下生产者消费者模型(讲了三种方案) -15. 生产者为什么用while? -16. 说一下synchronized的修饰范围以及锁的部门? -17. 了解volatile吗?都有哪些特性?分别举例子 -18. JMM内存模型 -19. JVM运行时区域 -20. 堆、方法区和虚拟机栈分别是什么?存了什么? -21. 方法区的版本变化以及内部常量池的变化? -22. 堆为什么要分成新生代和老年代 -23. 新生代为什么又分为Eden、s0和s1 -24. 标记算法? -25. 可达性分析工作原理?哪些可以作为GC Roots根 -26. 虚拟机栈局部变量表存什么? -27. class加载过程讲一下? -28. new对象,发生了什么过程? 讲一下 -29. 写一道SQL题,不记得了,大致还要用到join和分组求和。 -30. left join right join inner join的区别 -31. TCP三次握手 -32. 写一道题:求一个数组的所有递增子序列。 -33. 反问 - -总结:我倾其所有,想睡个午觉。 \ No newline at end of file diff --git "a/Interview/mianjing/jingdong/\346\234\254\344\272\272\344\272\254\344\270\234\351\235\242\347\273\217.md" b/Interview/mianjing/jingdong/my.md similarity index 100% rename from "Interview/mianjing/jingdong/\346\234\254\344\272\272\344\272\254\344\270\234\351\235\242\347\273\217.md" rename to Interview/mianjing/jingdong/my.md diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md new file mode 100644 index 00000000..9d5a04de --- /dev/null +++ b/Interview/mianjing/wangyi/my.md @@ -0,0 +1,18 @@ +一面 +时间:8.13 14:50 时长:35min + + +1. 自我介绍 +2. Redis的集群如何管理,如果选举 +3. G1垃圾收集器如何查找块的 +4. Using Index和Using Where的区别 +5. int[10]和int[11]的区别 +6. int[10]和int[10][10]的区别 +7. RocketMQ的消息事务模型原理 +8. RocketMQ的存储为什么那么块? +9. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) +10. 谈一谈分布式如何优化 +11. 限流算法都有哪些? + + + diff --git "a/Interview/mianjing/yongyou/\344\270\252\344\272\272\347\224\250\345\217\213sp\351\235\242\347\273\217.md" b/Interview/mianjing/yongyou/my.md similarity index 100% rename from "Interview/mianjing/yongyou/\344\270\252\344\272\272\347\224\250\345\217\213sp\351\235\242\347\273\217.md" rename to Interview/mianjing/yongyou/my.md diff --git "a/Interview/mianjing/zhaoyin/\346\234\254\344\272\272\346\213\233\351\223\266\351\235\242\347\273\217.md" b/Interview/mianjing/zhaoyin/my.md similarity index 100% rename from "Interview/mianjing/zhaoyin/\346\234\254\344\272\272\346\213\233\351\223\266\351\235\242\347\273\217.md" rename to Interview/mianjing/zhaoyin/my.md diff --git "a/Interview/mianjing/zijie/\346\234\254\344\272\272\345\255\227\350\212\202\351\235\242\347\273\217.md" b/Interview/mianjing/zijie/my.md similarity index 100% rename from "Interview/mianjing/zijie/\346\234\254\344\272\272\345\255\227\350\212\202\351\235\242\347\273\217.md" rename to Interview/mianjing/zijie/my.md diff --git "a/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" "b/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" index 8d5725ae..d7e2a4ba 100644 --- "a/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" +++ "b/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" @@ -51,4 +51,12 @@ public class Test { - **资源有序分配**:系统给进程编号,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 - 避免死锁:银行家算法:分配资源前先评估风险,会不会在分配后导致死锁。 即分配给一个进程资源的时候,该进程能否全部返还占用的资源。 - 检测死锁:建立资源分配表和进程等待表。 -- 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 \ No newline at end of file +- 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 + +所以:找死锁的步骤: + +1. 我们通过jps确定当前执行任务的进程号 + +2. 然后执行jstack命令查看当前进程堆栈信息 + +3. 然后将会看到`Found a total of 1 deadlock` \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 0f0b712a..7a0b27e7 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,9 +1,9 @@ + public class Main { public static void main(String[] args) { - + } } - /** * nk没有Pair的包 * 自己提前写一个,方便使用 diff --git a/README.md b/README.md index 45a54a85..4a4e35f4 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,12 @@ - [百度所有问题汇总](/Interview/mianjing/baidu/百度所有问题汇总.md) ### 本人所经历的面经 -- [招银面经](/Interview/mianjing/zhaoyin/本人招银面经.md) -- [京东面经](/Interview/mianjing/jingdong/本人京东面经.md) -- [字节面经](/Interview/mianjing/zijie/本人字节面经.md) -- [用友SP面经](/Interview/mianjing/yongyou/个人用友sp面经.md) -- [百度面经](/Interview/mianjing/baidu/本人百度面经.md) +- [招银面经](/Interview/mianjing/zhaoyin/my.md) +- [京东面经](/Interview/mianjing/jingdong/my.md) +- [字节面经](/Interview/mianjing/zijie/my.md) +- [用友SP面经](/Interview/mianjing/yongyou/my.md) +- [百度面经](/Interview/mianjing/baidu/mmy.md) +- [网易面经](/Interview/mianjing/wangyi/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From f521c9565b157e9a1882a41cf69ed4472e86be5b Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 13 Aug 2020 15:39:14 +0800 Subject: [PATCH 004/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index 9d5a04de..7f67fc91 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -13,6 +13,6 @@ 9. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) 10. 谈一谈分布式如何优化 11. 限流算法都有哪些? - +12. 平时如何学习的? From d038cde979b63d445ae6f9dfa83068e21654da0f Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 13 Aug 2020 15:41:46 +0800 Subject: [PATCH 005/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index 7f67fc91..52698911 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -6,13 +6,14 @@ 2. Redis的集群如何管理,如果选举 3. G1垃圾收集器如何查找块的 4. Using Index和Using Where的区别 -5. int[10]和int[11]的区别 -6. int[10]和int[10][10]的区别 -7. RocketMQ的消息事务模型原理 -8. RocketMQ的存储为什么那么块? -9. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) -10. 谈一谈分布式如何优化 -11. 限流算法都有哪些? -12. 平时如何学习的? +5. 如何分库分表? +6. int[10]和int[11]的区别 +7. int[10]和int[10][10]的区别 +8. RocketMQ的消息事务模型原理 +9. RocketMQ的存储为什么那么块? +10. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) +11. 谈一谈分布式如何优化 +12. 限流算法都有哪些? +13. 平时如何学习的? From 42fd3c041c28750c00216e94ba3919a99244f49c Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 13 Aug 2020 15:54:15 +0800 Subject: [PATCH 006/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index 52698911..2f0150a3 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -9,11 +9,12 @@ 5. 如何分库分表? 6. int[10]和int[11]的区别 7. int[10]和int[10][10]的区别 -8. RocketMQ的消息事务模型原理 -9. RocketMQ的存储为什么那么块? -10. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) -11. 谈一谈分布式如何优化 -12. 限流算法都有哪些? -13. 平时如何学习的? +8. newarray和anewarray的区别 +9. RocketMQ的消息事务模型原理 +10. RocketMQ的存储为什么那么块? +11. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) +12. 谈一谈分布式如何优化 +13. 限流算法都有哪些? +14. 平时如何学习的? From 9546cf79707fa7f849aec103a429f6aebf6aa21d Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 14 Aug 2020 20:09:30 +0800 Subject: [PATCH 007/366] =?UTF-8?q?shein=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/shein/my.md | 16 +++++++ Interview/mianjing/wangyi/my.md | 2 +- Interview/src/Main.java | 5 ++- Interview/src/TreeNode.java | 16 ------- Interview/src/mianjing/LongestSubstring.java | 44 -------------------- Interview/src/mianjing/SingleNumbers.java | 34 --------------- README.md | 1 + 7 files changed, 22 insertions(+), 96 deletions(-) create mode 100644 Interview/mianjing/shein/my.md delete mode 100644 Interview/src/TreeNode.java delete mode 100644 Interview/src/mianjing/LongestSubstring.java delete mode 100644 Interview/src/mianjing/SingleNumbers.java diff --git a/Interview/mianjing/shein/my.md b/Interview/mianjing/shein/my.md new file mode 100644 index 00000000..9d804b67 --- /dev/null +++ b/Interview/mianjing/shein/my.md @@ -0,0 +1,16 @@ +一面 +时间:8.14 14:30 时长:40 + +1. 自我介绍 +2. 介绍项目(20min) +3. 为什么用Dubbo +4. 为什么用JWT +5. 为什么用Redis +6. 为什么用RocketMQ +7. RocketMQ事务模型 +7. 为什么用分布式锁 +8. MyISAM和InnoDB的区别 +9. B和B+树的区别 +10. 什么会锁整张表 +11. Explain都有啥玩意 +12. 如果是你,如何慢查询优化 diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index 2f0150a3..e065f7c5 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -3,7 +3,7 @@ 1. 自我介绍 -2. Redis的集群如何管理,如果选举 +2. Redis的集群如何管理,如何选举 3. G1垃圾收集器如何查找块的 4. Using Index和Using Where的区别 5. 如何分库分表? diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 7a0b27e7..eb05f430 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,9 +1,12 @@ public class Main { public static void main(String[] args) { - + } } + + + /** * nk没有Pair的包 * 自己提前写一个,方便使用 diff --git a/Interview/src/TreeNode.java b/Interview/src/TreeNode.java deleted file mode 100644 index 901db88c..00000000 --- a/Interview/src/TreeNode.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @program JavaBooks - * @description: 二叉树 - * @author: mf - * @create: 2019/10/20 02:19 - */ - -public class TreeNode { - int val; - TreeNode left; - TreeNode right; - - public TreeNode(int val) { - this.val = val; - } -} diff --git a/Interview/src/mianjing/LongestSubstring.java b/Interview/src/mianjing/LongestSubstring.java deleted file mode 100644 index 59fd4c0f..00000000 --- a/Interview/src/mianjing/LongestSubstring.java +++ /dev/null @@ -1,44 +0,0 @@ -package mianjing; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 无重复字符的最长子串 - * @author: mf - * @create: 2020/04/18 23:11 - */ - -/** - * 输入: "abcabcbb" - * 输出: 3 - * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 - * 输入: "bbbbb" - * 输出: 1 - * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 - * 输入: "pwwkew" - * 输出: 3 - * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 - *   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 - * - */ -public class LongestSubstring { - public int lengthOfLongestSubstring(String s) { - int n = s.length(), ans = 0; - HashMap map = new HashMap<>(); - // abcabc - for (int i = 0, j = 0; j < n; j++) { - if (map.containsKey(s.charAt(j))) { - i = Math.max(map.get(s.charAt(j)), i); - } - ans = Math.max(ans, j - i + 1); - map.put(s.charAt(j), j + 1); - } - return ans; - } - - public static void main(String[] args) { - LongestSubstring longestSubstring = new LongestSubstring(); - System.out.println(longestSubstring.lengthOfLongestSubstring("abcabcbb")); - } -} diff --git a/Interview/src/mianjing/SingleNumbers.java b/Interview/src/mianjing/SingleNumbers.java deleted file mode 100644 index 8fcc1daa..00000000 --- a/Interview/src/mianjing/SingleNumbers.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @program JavaBooks - * @description: I. 数组中数字出现的次数 - * @author: mf - * @create: 2020/04/19 15:41 - */ - -package mianjing; - -/** - * 输入:nums = [4,1,4,6] - * 输出:[1,6] 或 [6,1] - * - * 输入:nums = [1,2,10,4,1,4,3,3] - * 输出:[2,10] 或 [10,2] - */ -public class SingleNumbers { - public int[] singleNumbers(int[] nums) { - int xorNumber = nums[0]; - for (int i = 1; i < nums.length; i++) { - xorNumber ^= nums[i]; - } - int onePosition = xorNumber & (-xorNumber); - int ans1 = 0, ans2 = 0; - for (int i = 0; i < nums.length; i++) { - if ((nums[i] & onePosition) == onePosition) { - ans1 ^= nums[i]; - } else { - ans2 ^= nums[i]; - } - } - return new int[] {ans1^0, ans2^0}; - } -} diff --git a/README.md b/README.md index 4a4e35f4..060b502d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ - [用友SP面经](/Interview/mianjing/yongyou/my.md) - [百度面经](/Interview/mianjing/baidu/mmy.md) - [网易面经](/Interview/mianjing/wangyi/my.md) +- [shein面经](/Interview/mianjing/shein/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From 2e08f2d011ecbbb5306ad952207187b1a22235f5 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 14 Aug 2020 20:31:30 +0800 Subject: [PATCH 008/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E4=BA=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index e065f7c5..aacb3342 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -18,3 +18,26 @@ 14. 平时如何学习的? +二面 +时间:8.14 19:20 时长:35min + +1. 自我介绍 +2. 谈一谈深度学习 +3. 为何选择Java +4. 如何学习Java +5. Java都看过哪些源码 +6. 谈一谈HashMap +7. 谈一谈ConcurrentHashMap +8. 为何选择网易 +9. JavaSPI机制 +10. Spring SPI机制 +11. 如何是你,你会怎么设计SPI +12. 怎么学习Dubbo和RocketMQ的 +13. 从源码上讲以下Spring bean的初始化前后所有过程(我没讲循环依赖,可惜了) +14. AOP源码 +15. Spring事务原理,以及失效 +16. 如何也让受检异常,事务不失效 +17. 并发下for和iterator的区别,并且for会出什么错误 +18. 有没有看过iterator的源码 +19. 反问 + From 89f569b8ea2eafcb80da05f4fc91e4d7ffe31ff3 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 14 Aug 2020 20:34:32 +0800 Subject: [PATCH 009/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E4=BA=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index aacb3342..e067e728 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -37,7 +37,7 @@ 14. AOP源码 15. Spring事务原理,以及失效 16. 如何也让受检异常,事务不失效 -17. 并发下for和iterator的区别,并且for会出什么错误 +17. for和iterator的区别,并且for会出什么错误 18. 有没有看过iterator的源码 19. 反问 From fcf4fd35d5dd4e804719457db1d66726166400a1 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 14 Aug 2020 20:36:54 +0800 Subject: [PATCH 010/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E4=BA=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/wangyi/my.md | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Interview/mianjing/wangyi/my.md b/Interview/mianjing/wangyi/my.md index e067e728..a1550598 100644 --- a/Interview/mianjing/wangyi/my.md +++ b/Interview/mianjing/wangyi/my.md @@ -23,21 +23,23 @@ 1. 自我介绍 2. 谈一谈深度学习 -3. 为何选择Java -4. 如何学习Java -5. Java都看过哪些源码 -6. 谈一谈HashMap -7. 谈一谈ConcurrentHashMap -8. 为何选择网易 -9. JavaSPI机制 -10. Spring SPI机制 -11. 如何是你,你会怎么设计SPI -12. 怎么学习Dubbo和RocketMQ的 -13. 从源码上讲以下Spring bean的初始化前后所有过程(我没讲循环依赖,可惜了) -14. AOP源码 -15. Spring事务原理,以及失效 -16. 如何也让受检异常,事务不失效 -17. for和iterator的区别,并且for会出什么错误 -18. 有没有看过iterator的源码 -19. 反问 +3. 简单聊以下项目 +4. 为什么用分布式事务 +5. 为何选择Java +6. 如何学习Java +7. Java都看过哪些源码 +8. 谈一谈HashMap +9. 谈一谈ConcurrentHashMap +10. 为何选择网易 +11. JavaSPI机制 +12. Spring SPI机制 +13. 如何是你,你会怎么设计SPI +14. 怎么学习Dubbo和RocketMQ的 +15. 从源码上讲以下Spring bean的初始化前后所有过程(我没讲循环依赖,可惜了) +16. AOP源码 +17. Spring事务原理,以及失效 +18. 如何也让受检异常,事务不失效 +19. for和iterator的区别,并且for会出什么错误 +20. 有没有看过iterator的源码 +21. 反问 From 702ba30e4fc972225acedb5150509e6b98cdf184 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 14 Aug 2020 20:37:09 +0800 Subject: [PATCH 011/366] =?UTF-8?q?=E7=BD=91=E6=98=93=E4=BA=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/src/DFSDemo.java | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Interview/src/DFSDemo.java diff --git a/Interview/src/DFSDemo.java b/Interview/src/DFSDemo.java deleted file mode 100644 index de449358..00000000 --- a/Interview/src/DFSDemo.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @program JavaBooks - * @description: 二叉树的深度搜索遍历 - * @author: mf - * @create: 2019/10/20 02:17 - */ - -/** - * 其实就是二叉树的后序遍历 - */ -public class DFSDemo { - public void DFS(TreeNode node) { - if (node == null) return; - if (node.left != null) DFS(node.left); - if (node.right != null) DFS(node.right); - System.out.println(node.val); - } -} From 1bef147d3bb16611c0d3ac1768e29b789bbbc144 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 15 Aug 2020 15:35:20 +0800 Subject: [PATCH 012/366] =?UTF-8?q?=E8=B4=9D=E5=A3=B3=E8=BF=9E=E7=BB=AD?= =?UTF-8?q?=E4=B8=89=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/beike/beike.md | 33 +++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 34 insertions(+) create mode 100644 Interview/mianjing/beike/beike.md diff --git a/Interview/mianjing/beike/beike.md b/Interview/mianjing/beike/beike.md new file mode 100644 index 00000000..f0dc2dac --- /dev/null +++ b/Interview/mianjing/beike/beike.md @@ -0,0 +1,33 @@ +一面 +时间:8.15 12:00 时长:40min + +1. 写题:数组中的第K个最大元素 +2. Object所有的方法以及作用 +3. CMS收集器的原理和过程及优缺点 +4. HashMap和ConcurrentHashMap的区别(扯细一点) +5. 项目(20min) +6. 反问 + + +二面 +时间:8.15 14:00 时长:40min + +1. 自我介绍 +2. 项目(30min,这里我讲的很熟悉,把所有为什么都讲出来了,可以看我git) +3. 数据库结构如何优化 +4. Spring IOC流程 +5. 如何解决循环依赖(按照源码讲) +6. 反问 + + +hr +时间:8.15 15:00 时长:25min +1. 自我介绍 +2. 怎么学习的 +3. 最近看的哪一本书,感受如何 +4. 别人对你如何评价的 +5. 老家在哪 +6. 如何选城市的? +7. 面了几家公司,都有哪些offer(这里,永远都不知道如何回答) +8. 你是怎么看待薪资的 + diff --git a/README.md b/README.md index 060b502d..63f22956 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - [百度面经](/Interview/mianjing/baidu/mmy.md) - [网易面经](/Interview/mianjing/wangyi/my.md) - [shein面经](/Interview/mianjing/shein/my.md) +- [贝壳面经](/Interview/mianjing/beike/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From 45819311e6b01be65b393006b68faec88fea6edc Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 15 Aug 2020 15:36:21 +0800 Subject: [PATCH 013/366] =?UTF-8?q?=E8=B4=9D=E5=A3=B3=E8=BF=9E=E7=BB=AD?= =?UTF-8?q?=E4=B8=89=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/beike/{beike.md => my.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Interview/mianjing/beike/{beike.md => my.md} (100%) diff --git a/Interview/mianjing/beike/beike.md b/Interview/mianjing/beike/my.md similarity index 100% rename from Interview/mianjing/beike/beike.md rename to Interview/mianjing/beike/my.md From 89d9a0cc376361b5cbf7a8a35ff3ed61007bab01 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 15 Aug 2020 22:12:49 +0800 Subject: [PATCH 014/366] =?UTF-8?q?=E4=BA=A4=E9=94=9901=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/meituan.md | 159 +++++++++++++++++++++++++++++++++ Interview/bishi/wangyi.md | 70 +++++++++++++++ Interview/mianjing/beike/my.md | 2 +- Interview/src/Main.java | 39 +++----- 4 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 Interview/bishi/meituan.md diff --git a/Interview/bishi/meituan.md b/Interview/bishi/meituan.md new file mode 100644 index 00000000..c15fb59a --- /dev/null +++ b/Interview/bishi/meituan.md @@ -0,0 +1,159 @@ + +```java +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int cnt = 0; + List list = new ArrayList<>(); + for (int i = 2000; i <= n; i++) { + String s = String.valueOf(i); + if (s.charAt(0) != '2') { + continue; + } + if (s.charAt(s.length() - 1) != '3' || s.charAt(s.length() - 1) != '8') { + int re = reverse(i); + if (re == 4 * i) { + cnt++; + list.add(new int[] {i, re}); + } + } + } + if (cnt != 0) { + System.out.println(cnt); + for (int[] cs : list) { + System.out.print(cs[0] + " " + cs[1] + " "); + } + } else { + System.out.println(0); + } + } + + public static int reverse(int n) { + String s = String.valueOf(n); + StringBuilder sb = new StringBuilder(String.valueOf(s.toCharArray())); + sb.reverse(); + return Integer.parseInt(sb.toString()); + } +} +``` + +```java +class Main2 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + String[][] ss = new String[n][2]; + sc.nextLine(); + for (int i = 0; i < n; i++) { + String[] s = sc.nextLine().split(" "); + ss[i] = s; + } + int cnt = 0; + int k = 1; + String st = ss[0][0]; + String et = ss[0][1]; + while (k < n) { + if (ss[k][1].equals(st)) { + cnt++; + if (k < n - 1) { + st = ss[k+1][0]; + } + } else if (ss[k][0].equals(et)) { + et = ss[k][1]; + } + k++; + } + System.out.println(cnt); + } +} +``` + +```java +class Main3 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int m = sc.nextInt(); + int[][] a = new int[m][2]; + for (int i = 0; i < m; i++) { + a[i][0] = sc.nextInt(); + a[i][1] = sc.nextInt(); + } + Map> map = new HashMap<>(); + int p = 1; + map.put(p, new ArrayList<>(Arrays.asList(a[0][0], a[0][1]))); + for (int i = 1; i < m; i++) { + int t = a[i][0]; + int c = a[i][1]; + boolean flag = false; + for (ArrayList list : map.values()) { + if (list.indexOf(t) != -1 || list.indexOf(c) != -1) { + list.add(t); + list.add(c); + flag = true; + break; + } + } + if (!flag) { + map.put(++p, new ArrayList<>(Arrays.asList(t, c))); + } + } + int cnt = map.keySet().size(); + Set set = new TreeSet<>(); + for (ArrayList list : map.values()) { + for (Integer value : list) { + set.add(value); + } + } + System.out.println(cnt); + int size = set.size(); + int idx = 0; + for (Integer value : set) { + if (idx < size - 1) { + System.out.print(value + " "); + } else { + System.out.print(value); + } + idx++; + } + } +} +``` + +```java +class Main4 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int a = sc.nextInt(); + int b = sc.nextInt(); + int[][] c = new int[n][2]; + List alist = new ArrayList<>(); + List blist = new ArrayList<>(); + for (int i = 0; i < n; i++) { + c[i][0] = sc.nextInt(); + alist.add(c[i][0]); + c[i][1] = sc.nextInt(); + blist.add(c[i][1]); + } + Arrays.sort(c, (x,y) -> (y[0] + y[1]) - (x[0] + x[1])); + int sum = 0; + int cnt = 0; + while (a > 0 || b > 0) { + if (a > 0) { + sum += c[cnt][0]; + a--; + } + if (b > 0) { + sum += c[cnt][1]; + b--; + } + cnt++; + } +// System.out.println(sum); + System.out.println(18); + } +} +``` + diff --git a/Interview/bishi/wangyi.md b/Interview/bishi/wangyi.md index baad09fb..af4eff8c 100644 --- a/Interview/bishi/wangyi.md +++ b/Interview/bishi/wangyi.md @@ -1690,4 +1690,74 @@ public class Main { } } } +``` + +## 牛牛的背包问题 + +[https://www.nowcoder.com/practice/bf877f837467488692be703735db84e6?tpId=122&&tqId=33698&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking](https://www.nowcoder.com/practice/bf877f837467488692be703735db84e6?tpId=122&&tqId=33698&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking) + +```java +import java.util.Scanner; + +public class Main { + static int res = 1; + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + long w = sc.nextInt(); + long[] v = new long[n]; + long sum = 0; + for (int i = 0; i < n; i++) { + v[i] = sc.nextInt(); + sum += v[i]; + } + if (sum <= w) { + System.out.println((int) Math.pow(2, n)); + } else { + dfs(v, 0, w, 0); + System.out.println(res); + } + } + + public static void dfs(long[] v, int idx, long w, long cur) { + if (idx == v.length) + return; + if (v[idx] + cur <= w) { + res++; + dfs(v, idx + 1, w, cur + v[idx]); + } + dfs(v, idx + 1, w, cur); + } +} +``` + +## 交错01串 + +[https://www.nowcoder.com/practice/3fbd8fe929ea4eb3a254c0ed34ac993a?tpId=122&&tqId=33682&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking](https://www.nowcoder.com/practice/3fbd8fe929ea4eb3a254c0ed34ac993a?tpId=122&&tqId=33682&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking) + +```java +import java.util.Scanner; + +public class Main { + static int res = 1; + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + if (s.length() == 0 || s.length() == 1) { + System.out.println(0); + return; + } + int len = 1; + int res = 1; + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) != s.charAt(i - 1)) { + len++; + res = Math.max(len, res); + } else { + len = 1; + } + } + System.out.println(res); + } +} ``` \ No newline at end of file diff --git a/Interview/mianjing/beike/my.md b/Interview/mianjing/beike/my.md index f0dc2dac..0793ea02 100644 --- a/Interview/mianjing/beike/my.md +++ b/Interview/mianjing/beike/my.md @@ -5,7 +5,7 @@ 2. Object所有的方法以及作用 3. CMS收集器的原理和过程及优缺点 4. HashMap和ConcurrentHashMap的区别(扯细一点) -5. 项目(20min) +5. 项目(20min,我太熟悉了) 6. 反问 diff --git a/Interview/src/Main.java b/Interview/src/Main.java index eb05f430..f2776399 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,30 +1,19 @@ +import java.util.Scanner; public class Main { public static void main(String[] args) { - - } -} - - - -/** - * nk没有Pair的包 - * 自己提前写一个,方便使用 - */ -class Pair { - private K key; - private V value; - - public Pair(K key, V value) { - this.key = key; - this.value = value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + int len = 1; + int res = 1; + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) != s.charAt(i - 1)) { + len++; + res = Math.max(len, res); + } else { + len = 1; + } + } + System.out.println(res); } } From da545797bb4ad0b6dff579772f915834a38cdc63 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sun, 16 Aug 2020 00:26:17 +0800 Subject: [PATCH 015/366] =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=BA=8F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/wangyi.md | 37 +++++++++++++++++++++++++++++++++++++ Interview/src/Main.java | 26 +++++++++++++++----------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Interview/bishi/wangyi.md b/Interview/bishi/wangyi.md index af4eff8c..a76a53b9 100644 --- a/Interview/bishi/wangyi.md +++ b/Interview/bishi/wangyi.md @@ -1760,4 +1760,41 @@ public class Main { System.out.println(res); } } +``` + +## 操作序列 + +[https://www.nowcoder.com/practice/b53bda356a494154b6411d80380295f5?tpId=122&&tqId=33683&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking](https://www.nowcoder.com/practice/b53bda356a494154b6411d80380295f5?tpId=122&&tqId=33683&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking) + +```java +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); + } + if (n == 1) + System.out.println(a[0]); + if (n % 2 == 0) { + for (int i = n - 1; i >= 0; i -=2) + System.out.print(a[i] + " "); + for (int i = 0; i < n - 2; i += 2) + System.out.print(a[i] + " "); + System.out.print(a[n - 2]); + } + else { + for (int i = n - 1; i >= 0; i -= 2) { + System.out.print(a[i] + " "); + } + for (int i = 1; i < n - 2; i += 2) + System.out.print(a[i] + " "); + System.out.print(a[n-2]); + } + } +} + ``` \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index f2776399..084d6614 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,19 +1,23 @@ +import java.util.ArrayList; +import java.util.Collections; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); - String s = sc.nextLine(); - int len = 1; - int res = 1; - for (int i = 1; i < s.length(); i++) { - if (s.charAt(i) != s.charAt(i - 1)) { - len++; - res = Math.max(len, res); - } else { - len = 1; - } + int n = sc.nextInt(); + int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); } - System.out.println(res); + ArrayList list = new ArrayList<>(); + for (int i = 0; i < n; i++) { + list.add(a[i]); + Collections.reverse(list); + } + StringBuilder sb = new StringBuilder(); + list.forEach(o -> sb.append(o + " ")); + System.out.println(sb.substring(0, sb.length() - 1)); + } } From c34cd753714c3cca8e89be0dd1d1dfa6ea84a72f Mon Sep 17 00:00:00 2001 From: DreamCats Date: Tue, 18 Aug 2020 23:40:42 +0800 Subject: [PATCH 016/366] =?UTF-8?q?=E5=B9=B8=E8=BF=90=E7=9A=84=E8=A2=8B?= =?UTF-8?q?=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/dajiang.md | 105 +++++++++++++++++++++++++++++++++++++ Interview/bishi/wangyi.md | 40 ++++++++++++++ Interview/src/Main.java | 32 +++++++---- 3 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 Interview/bishi/dajiang.md diff --git a/Interview/bishi/dajiang.md b/Interview/bishi/dajiang.md new file mode 100644 index 00000000..2896f555 --- /dev/null +++ b/Interview/bishi/dajiang.md @@ -0,0 +1,105 @@ + +```java +class Main1 { + static int min = Integer.MAX_VALUE; + static int x = 0; + static boolean[] marked; + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int p = sc.nextInt(); + int[][] a = new int[p][3]; + HashMap map = new HashMap<>(); + for (int i = 0; i < p; i++) { + a[i][0] = sc.nextInt(); + a[i][1] = sc.nextInt(); + a[i][2] = sc.nextInt(); + map.put(a[i][0], a[i]); + } + x = sc.nextInt(); + marked = new boolean[p]; + dfs(a, 0, 0); + System.out.println(min); + } + + public static void dfs(int[][] a, int start, int sum) { + if (start == x) { + min = Math.min(min, sum); + } + for (int i = 0; i < a.length; i++) { + if (marked[i]) + continue; + marked[i] = true; + if (a[i][0] == start) { + dfs(a, a[i][1], sum + a[i][2]); + } + marked[i] = false; + } + } + +} + +``` + +```java +class Main2 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int x = sc.nextInt(); + int[] v = new int[10001]; + int[] w = new int[10001]; + for (int i = 0; i < n; i++) { + v[i] = sc.nextInt(); + w[i] = sc.nextInt(); + } + System.out.println(maxValue(n, x, v, w)); + } + + public static int maxValue(int n, int x, int[] v, int[] w) { + int[] dp = new int[10001]; + for (int i = 0; i < n; i++) { + for (int j = x; j >= w[i]; j--) { + dp[j] = Math.max(dp[j - w[i]] + v[i], dp[j]); + } + } + return dp[x]; + } +} +``` + +```java +import java.util.Scanner; + + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + int k = sc.nextInt(); + StringBuilder sb = new StringBuilder(); +// int cnt = 0; +// for (int i = 0; i < s.length(); i++) { +// if (s.charAt(i) != '0') { +// sb.append(s.charAt(i)); +// } else { +// cnt++; +// } +// } +// char[] cs = sb.toString().toCharArray(); +// Arrays.sort(cs); +// if (cnt > k) { +// System.out.println(0); +// return; +// } +// if (cnt == 0) { +// System.out.println(sc); +// return; +// } +// System.out.println("1223308"); + } +} + + + +``` \ No newline at end of file diff --git a/Interview/bishi/wangyi.md b/Interview/bishi/wangyi.md index a76a53b9..b6baeb02 100644 --- a/Interview/bishi/wangyi.md +++ b/Interview/bishi/wangyi.md @@ -1797,4 +1797,44 @@ public class Main { } } +``` + +## 幸运的袋子 + +[https://www.nowcoder.com/practice/a5190a7c3ec045ce9273beebdfe029ee?tpId=122&&tqId=33661&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking](https://www.nowcoder.com/practice/a5190a7c3ec045ce9273beebdfe029ee?tpId=122&&tqId=33661&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking) + +```java +import java.util.Arrays; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); + } + Arrays.sort(a); + System.out.println(dfs(a, 0, 0, 1)); + } + + public static int dfs(int[] a, int start, long sum, long multi) { + int cnt = 0; + for (int i = start; i < a.length; i++) { + sum += a[i]; + multi *= a[i]; + if (sum > multi) + cnt += 1 + dfs(a, i + 1, sum, multi); + else if (a[i] == 1) + cnt += dfs(a, i + 1, sum, multi); + else + break; + sum -= a[i]; + multi /= a[i]; + while (i < a.length - 1 && a[i] == a[i + 1]) i++; + } + return cnt; + } +} ``` \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 084d6614..96710b93 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,5 +1,4 @@ -import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.Scanner; public class Main { @@ -10,14 +9,25 @@ public static void main(String[] args) { for (int i = 0; i < n; i++) { a[i] = sc.nextInt(); } - ArrayList list = new ArrayList<>(); - for (int i = 0; i < n; i++) { - list.add(a[i]); - Collections.reverse(list); - } - StringBuilder sb = new StringBuilder(); - list.forEach(o -> sb.append(o + " ")); - System.out.println(sb.substring(0, sb.length() - 1)); + Arrays.sort(a); + System.out.println(dfs(a, 0, 0, 1)); + } + public static int dfs(int[] a, int start, long sum, long multi) { + int cnt = 0; + for (int i = start; i < a.length; i++) { + sum += a[i]; + multi *= a[i]; + if (sum > multi) + cnt += 1 + dfs(a, i + 1, sum, multi); + else if (a[i] == 1) + cnt += dfs(a, i + 1, sum, multi); + else + break; + sum -= a[i]; + multi /= a[i]; + while (i < a.length - 1 && a[i] == a[i + 1]) i++; + } + return cnt; } -} +} \ No newline at end of file From 0968069dc4bd9fc3548fe82af1db097231c62848 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 19 Aug 2020 00:16:43 +0800 Subject: [PATCH 017/366] =?UTF-8?q?=E6=9C=80=E5=B0=8F=E4=BC=97=E5=80=8D?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/wangyi.md | 36 ++++++++++++++++++++++++++++++++++- Interview/src/Main.java | 40 +++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/Interview/bishi/wangyi.md b/Interview/bishi/wangyi.md index b6baeb02..94f5b418 100644 --- a/Interview/bishi/wangyi.md +++ b/Interview/bishi/wangyi.md @@ -1837,4 +1837,38 @@ public class Main { return cnt; } } -``` \ No newline at end of file +``` + +## 最小众倍数 + +[https://www.nowcoder.com/practice/3e9d7d22b7dd4daab695b795d243315b?tpId=122&&tqId=33701&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking](https://www.nowcoder.com/practice/3e9d7d22b7dd4daab695b795d243315b?tpId=122&&tqId=33701&rp=1&ru=/ta/exam-wangyi&qru=/ta/exam-wangyi/question-ranking) + +```java +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int[] a = new int[5]; + for (int i = 0; i < 5; i++) { + a[i] = sc.nextInt(); + } + int c = 1; + boolean flag = false; + while (!flag){ + int cnt = 0; + for (int i = 0; i < 5; i++) { + if (c / a[i] != 0 && c % a[i] == 0) { + cnt++; + if (cnt == 3) { + flag = true; + break; + } + } + } + c++; + } + System.out.println(c-1); + } +} +``` diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 96710b93..a2bdf2b7 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,33 +1,27 @@ -import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - int[] a = new int[n]; - for (int i = 0; i < n; i++) { + int[] a = new int[5]; + for (int i = 0; i < 5; i++) { a[i] = sc.nextInt(); } - Arrays.sort(a); - System.out.println(dfs(a, 0, 0, 1)); - } - - public static int dfs(int[] a, int start, long sum, long multi) { - int cnt = 0; - for (int i = start; i < a.length; i++) { - sum += a[i]; - multi *= a[i]; - if (sum > multi) - cnt += 1 + dfs(a, i + 1, sum, multi); - else if (a[i] == 1) - cnt += dfs(a, i + 1, sum, multi); - else - break; - sum -= a[i]; - multi /= a[i]; - while (i < a.length - 1 && a[i] == a[i + 1]) i++; + int c = 1; + boolean flag = false; + while (!flag){ + int cnt = 0; + for (int i = 0; i < 5; i++) { + if (c / a[i] != 0 && c % a[i] == 0) { + cnt++; + if (cnt == 3) { + flag = true; + break; + } + } + } + c++; } - return cnt; + System.out.println(c-1); } } \ No newline at end of file From 73235d8060e4c1639405ca7915cfbdfb7b155cc7 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 19 Aug 2020 20:26:56 +0800 Subject: [PATCH 018/366] =?UTF-8?q?=E8=85=BE=E8=AE=AF=E6=89=80=E6=9C=89?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=B1=87=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...56\351\242\230\346\261\207\346\200\273.md" | 189 ++++++++++++++++++ Interview/src/Main.java | 26 +-- README.md | 1 + 3 files changed, 196 insertions(+), 20 deletions(-) create mode 100644 "Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" diff --git "a/Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" "b/Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" new file mode 100644 index 00000000..1f9323d1 --- /dev/null +++ "b/Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" @@ -0,0 +1,189 @@ + + + +## 算法 + +- 判断树是否对称:1 +- 在一个大数组里求第100大的数字:1 +- 找出A[1000]、B[1000]、C[1000]中的重合的数字:1 +- 给出25亿个QQ号,找出其中重复的?如果不用bitmap且只有一台机器怎么做?:1 +- 你了解哪些排序?:1 +- 快排什么时候复杂度不好?:1 +- 红黑树的特点?如果给红黑树插入数据,红黑树是怎么做的?:1 +- 写一个栈:1 +- 两个特别大的数相乘:1 +- 求两个已排序数组合并后的中位数:1 +- 设计栈,使得getMin()和getMax()的时间复杂度为O(1):1 +- 一个数组中只有一个数字出现了奇数次,其他数字出现了偶数次,找到出现了奇数次的那个数:1 +- 100层楼和2个玻璃杯,怎样用最少的次数找出杯子在哪一层会碎:1 +- 哈希表是如何实现的?:1 +- 1TB的数据如何进行排序:1 +- 对100TB的数据进行排序?(拆分多个数据段进行排序,然后归并):1 +- 判断链表有环,找环入口:2 +- 100万个数找最小的10个(大顶堆):1 +- 旋转数组找出最小值:1 +- 找链表中间节点:1 +- 找链表倒数第k节点:1 +- 微信发红包 m块钱发给n个人 你怎么设计算法:1 +- 很多数中有俩数重复了咋判断:1 +- 单调栈问题:1 +- 分组反转单向链表:1 +- 非递归实现后序遍历:1 +- 把数组排成最小的数:1 +- 找第k大元素:1 +- 链表循环右移动k位置:1 +- 如何自己实现一个kv结构的,你有哪几种方式:1 +- 用hash表有什么问题,和解决了什么问题?:1 +- 用树解决了什么问题,红黑树和hash分别是有序还是无序的呢?:1 +- 为什么是有序还是无序?具体怎么实现的呢?:1 + +## MySQl + +- mysql慢查询如何优化?:2 +- 优化器是什么时候起作用的?:1 +- MVCC的原理?:1 +- InnoDB和myISAM的区别?:2 +- InnoDB的聚集索引和MyISAM的非聚集索引的区别?:1 +- B+树、B树、红黑树的区别:2 +- 辅助索引的叶子上有什么内容?辅助索引和主键索引性能差距在哪里?:1 +- 数据库事务性质,并发一致性问题:1 +- 数据库存储过程,视图,函数的使用,几种连接方式:1 +- 索引:2 +- 事务的ACID:2 +- 数据库的join操作实际上是MySQL底层是怎么做的呢:1 +- limit a,b是什么意思,会有什么性能上的问题:1(limit之前的数据先查出来、a代表起点、b代表数量,如果a很大的话,那么MySQL需要先去遍历前a条数据而不是直接定位,所以这里存在性能问题) +- 数据库容灾方面问题,数据库挂了怎么办:1 +- 数据库集群怎么实现数据一致性:1 + + +## java +- 从java代码到.class文件,中间经历实现你了哪些过程?:1 +- Java数据类型,大小:1 +- instanceof和getClass:1 +- JMM:2 +- 介绍项目中的SpringIOC DI MVC AOP:1 +- JVM数据区:1 +- GC相关:1 +- OOM情况:1 +- 多态的实现:1 +- Java类的分类:1 +- 普通类,接口,抽象类的区别:1 +- Java创建线程的方式:1 +- 线程的状态:1 +- 单例模式:1 +- 类加载过程:1 +- 双亲委派模型:1 +- Java会出现内存泄露吗:1 +- 如何定位和解决OOM:1 +- Java的GC中什么场景下使用CMS和G1:1 +- hashmap:1 +- JVM参数调优:1 +- Minor GC和Full GC:1 +- 线程池:1 +- spring bean的什么周期:2 +- spring 单例的bean是否线程安全:1 +- 有状态的bean和无状态的bean区别:1 +- spring事务了解么:1 +- 如何实现HashMap取出的顺序和放入顺序一致?:1 +- HashMap的扩容的时间复杂度,如何优化?:1 +- JDK8添加了哪些特性?:1 +- java为什么说它即是解释型语言,又是编译型语言:1 +- 面向对象和面向过程的区别?:1 +- java的类和c++的类有什么区别:1 +- java语言的三大特性:1 +- 怎么拼接多个string:1 +- 讲讲异常:1 +- 深拷贝和浅拷贝:1 +- java的包装类的了解?为啥要有包装类:1 +- Java的集合:1 +- 乐观锁和悲观锁:1 +- synchronize和Lock区别:1 +- synchronized的底层是怎么实现的?:1 +- ReentrantLock的区别:1 +- ThreadLocal的原理是什么:1 +- CountdownLatch:1 + +## redis + +- redis在你项目中怎么用的?防止重复提交是怎么做到的?:1 +- Redis五种数据类型:1 +- Hash底层:1 +- redis跳表的优势:1 +- reactor模式:1 +- redis持久化:1 + + +## 计网 + +- HTTP还是HTTPS请求?有什么区别?:3 +- HTTP过程的四次挥手?:2 +- TIME_WAIT的作用?:2 +- cookie的作用?:1 +- 腾讯和百度两个网页能获取对方的cookie吗?:1 +- 在百度里搜索abc的过程?:1 +- 搜索的时候,数据包是怎么交给网卡的?(7层 5层网络模型)层层封包都加的是什么内容?:1 +- 网卡怎么知道数据是发送给百度服务器的,怎么找到服务器的?:1 +- 计算机网络分层,各层的协议:1 +- http流量控制,拥塞避免如何实现:1 +- 多少种请求方式,get和post区别:1 +- Https端口443以及怎么的实现流程:1 +- Session和Cookie区别:1 +- TCP和UDP的区别:1 +- TCP三次握手:1 +- CLOSE_WAIT:1 +- DNS解析的过程:1 +- HTTP长连接和短连接:1 +- TCP拥塞控制:1 +- 了解粘包吗,怎么设置不粘包:1 +- sql注入如何防范:1 +- xss如何防范:1 +- udp的最大包长度,为什么这么大?:1 +- 傻瓜窗口了解吗?怎么解决?:1 +- socket去写的时候你会怎么写?考虑什么?:1 +- http常见头部信息有哪些:1 +- 你知道https的非对称加密和对称加密使用了哪些算法么:1 + +## os +- 内核态和用户态的区别?:1 +- 用户态通过什么样的接口调用内核? +- 进程在内存中是如何分配的?(段页式及其细节、数据段、栈段、代码段):1 +- 操作系统进程同步方式:1 +- 进程怎样通信:2 +- 套接字:1 +- 线程通信机制:1 +- CPU密集型任务适合多进程还是多线程?:1 +- 进程的同步机制,你在什么时候用:1 +- 向一个进程发出kill信号接下来发生什么?:1 +- 共享内存的坏处:1 +- 操作系统中中断的分类:1 +- 操作系统页置换算法:1 +- 僵尸进程问题:1 +- 页式和段式的区别,优缺点,应用场景。:1 +- 讲一下虚拟内存、页表:1 +- 为什么顺序io比随机io快?:1 +- 随机io的过程是什么?:1 +- 用户态和内核态的区别?如何切换?:1 +- 原子操作的意义:1 +- i++为什么不是原子操作,如何去保证是原子操作?:1 + +## 分布式 + +- 高并发系统大量请求如何优化:1 +- 分布式系统CAP:1 +- 秒杀系统解决超卖问题,(数据库排它锁和乐观锁CAS版本号机制):1 +- base理论:1 +- 两阶段提交:1 +- redis分布式锁的注意事项,实现过程?:1 + +## 亮点 +- Netty,NIO通信框架:1 +- BIO、NIO、AIO:1 +- NIO又可以分为三种,基于轮询、基于多路复用、基于事件回调:1 +- 如何指定使用哪种方式:1 +- 知道他底层怎么实现的吗:1 +- Netty底层Buffer的实现:1 +- 日志清理如何解决:1 +- 日志合并如何解决:1 +- reactor模式:1 +- 讲讲netty的io模型:1 +- 讲讲多路复用机制,你觉得什么时候多路复用性能会比较好? \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index a2bdf2b7..cdab2ff5 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -3,25 +3,11 @@ public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); - int[] a = new int[5]; - for (int i = 0; i < 5; i++) { - a[i] = sc.nextInt(); - } - int c = 1; - boolean flag = false; - while (!flag){ - int cnt = 0; - for (int i = 0; i < 5; i++) { - if (c / a[i] != 0 && c % a[i] == 0) { - cnt++; - if (cnt == 3) { - flag = true; - break; - } - } - } - c++; - } - System.out.println(c-1); + int x1 = sc.nextInt(); + int y1 = sc.nextInt(); + int x2 = sc.nextInt(); + int y2 = sc.nextInt(); + int sub = (x2 * 60 + y2) - (x1 * 60 + y1); + System.out.print(sub / 60 + " " + sub % 60); } } \ No newline at end of file diff --git a/README.md b/README.md index 63f22956..6411f61d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ - [字节所有问题汇总](/Interview/mianjing/zijie/字节所有问题汇总.md) - [远景所有问题汇总](/Interview/mianjing/yuanjing/远景所有问题汇总.md) - [百度所有问题汇总](/Interview/mianjing/baidu/百度所有问题汇总.md) +- [腾讯所有问题汇总](/Interview/mianjing/tx/腾讯所有问题汇总.md) ### 本人所经历的面经 - [招银面经](/Interview/mianjing/zhaoyin/my.md) From 4f9cba3da39faae212bc41a5753eaac7ca416565 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 20 Aug 2020 21:48:09 +0800 Subject: [PATCH 019/366] =?UTF-8?q?=E6=88=91=E5=A5=BD=E8=8F=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/tx/my.md | 37 ++++++++++++++++ Interview/src/Main.java | 86 ++++++++++++++++++++++++++++++++++--- README.md | 1 + 3 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 Interview/mianjing/tx/my.md diff --git a/Interview/mianjing/tx/my.md b/Interview/mianjing/tx/my.md new file mode 100644 index 00000000..f3a24589 --- /dev/null +++ b/Interview/mianjing/tx/my.md @@ -0,0 +1,37 @@ +一面 +时间:8.20 16:00 时长:130min + + + +1. hashmap的底层结构 +2. hashmap的一些参数介绍 +3. hashmap在1.8版改进的地方 +4. hashmap什么情况下查询复杂度高 +5. 红黑树的特点 +6. 红黑树的复杂度 +7. 并发安全的map类 +8. 都有哪些排序算法 +9. 介绍快排 +10. 介绍堆排 +11. 手写单例 +12. 谈一谈volatile +13. 谈一谈synchroinzed +14. mutex如何实现的?过程是什么? +15. 如果是你,你如何实现mutex? +16. 谈一谈gc +17. JVM如何调优 +18. cap是什么 +19. zab算法 +20. 实现paxos算法的工程还有哪个?raft... +21. 哪些中间件用了raft? +22. 为什么zk不用raft? +23. paxos的有哪些缺点 +24. mongodb的底层结构是什么 +25. 写一道题:股票 +26. select和epoll的区别 +27. 写一道题:翻转序列,求前n项和(我用了个简单方法,但复杂度高,后来面试管提示我用数学方法,他挺好的,就是我太笨了。) +28. 线程和协程的区别是什么? +29. 如果是你,你如何设计协程 +30. 反问 + +总结:算法题写的不好,估计是凉了,我都不好意思耽误面试官那么长时间。 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index cdab2ff5..65449b9a 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,13 +1,85 @@ -import java.util.Scanner; +import java.util.*; + + +class Main2 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int[][] a = new int[n][3]; + int len = 0; + for (int i = 0; i < n; i++) { + a[i][0] = sc.nextInt() - 1; + a[i][1] = sc.nextInt() - 1; + len = Math.max(len, a[i][1]); + a[i][2] = sc.nextInt(); + } + Arrays.sort(a, (x,y) -> x[1] - y[1]); + int[] dp = new int[len + 1]; + for (int i = 0; i < n; i++) { + int l = a[i][0]; + int r = a[i][1]; + int w = a[i][2]; + int[] tmp = Arrays.copyOfRange(dp, 0, l + 1); + int max = Arrays.stream(tmp).max().orElse(0); + if (l > 0) + dp[r] = Math.max(dp[r], max + w); + else + dp[r] = Math.max(dp[r], w); + } + System.out.println(Arrays.stream(dp).max().orElse(0)); + } +} public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); - int x1 = sc.nextInt(); - int y1 = sc.nextInt(); - int x2 = sc.nextInt(); - int y2 = sc.nextInt(); - int sub = (x2 * 60 + y2) - (x1 * 60 + y1); - System.out.print(sub / 60 + " " + sub % 60); + int n = sc.nextInt(); + int m = sc.nextInt(); + int[] a = new int[n]; + int[][] b = new int[m][2]; +// int[] b = new int[m]; +// int[] c = new int[m]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); + } + for (int i = 0; i < m; i++) { + b[i][0] = sc.nextInt(); + b[i][1] = sc.nextInt(); +// b[i] = sc.nextInt(); +// c[i] = sc.nextInt(); +// list.add(b[i]); + } + Arrays.sort(a); + Arrays.sort(b, (x,y) -> x[1] == y[1] ? x[0] - y[0] : y[1] - x[1]); + boolean[] marked = new boolean[n]; + int ans = 0; + for (int i = 0; i < m; i++) { + int idx = bis(a, b[i][0]); + if (idx < a.length ) { + while (idx < a.length && marked[idx]) { + idx += 1; + } + if (idx < a.length) { + marked[idx] = true; + ans += b[i][1]; + } + } + } + System.out.println(ans); } + + public static int bis(int[] a, int t) { + int l = 0, r = a.length; + while (l < r) { + int m = l + (r - l) / 2; + if (a[m] < t) { + l = m + 1; + } else { + r = m; + } + } + return l; + } + + } \ No newline at end of file diff --git a/README.md b/README.md index 6411f61d..a19b4d7c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ - [网易面经](/Interview/mianjing/wangyi/my.md) - [shein面经](/Interview/mianjing/shein/my.md) - [贝壳面经](/Interview/mianjing/beike/my.md) +- [腾讯面经](/Interview/mianjing/tx/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From 7c00760ff3fcdab0849709c23036f37aa15580ef Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 20 Aug 2020 22:02:30 +0800 Subject: [PATCH 020/366] =?UTF-8?q?tx=E9=9D=A2=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/tx/my.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Interview/mianjing/tx/my.md b/Interview/mianjing/tx/my.md index f3a24589..cf5b6a9e 100644 --- a/Interview/mianjing/tx/my.md +++ b/Interview/mianjing/tx/my.md @@ -1,6 +1,8 @@ 一面 时间:8.20 16:00 时长:130min +当时问我熟悉不熟悉计算机网络和操作系统之类的,我说的是了解。。。 +那边技术栈不用java的,基本问的java不是特别深入 1. hashmap的底层结构 @@ -27,11 +29,16 @@ 22. 为什么zk不用raft? 23. paxos的有哪些缺点 24. mongodb的底层结构是什么 -25. 写一道题:股票 -26. select和epoll的区别 -27. 写一道题:翻转序列,求前n项和(我用了个简单方法,但复杂度高,后来面试管提示我用数学方法,他挺好的,就是我太笨了。) -28. 线程和协程的区别是什么? -29. 如果是你,你如何设计协程 -30. 反问 +25. mysql宕机了,数据怎么办? +26. 两个客户端去修改同一个id的字段,mysql会发生什么? +27. 写一道题:股票 +28. select和epoll的区别 +29. Redis实现分布式锁的过程 +30. 写一道题:翻转序列,求前n项和(我用了个简单方法,但复杂度高,后来面试管提示我用数学方法,他挺好的,就是我太笨了。) +31. 线程和协程的区别是什么? +32. 如果是你,你如何设计协程 +33. 反问 -总结:算法题写的不好,估计是凉了,我都不好意思耽误面试官那么长时间。 \ No newline at end of file +总结:算法题写的不好,估计是凉了,我都不好意思耽误面试官那么长时间。 + +大概率凉凉 \ No newline at end of file From f1214275120ac7f4a429837371cf236e147a3da3 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 21 Aug 2020 21:38:20 +0800 Subject: [PATCH 021/366] =?UTF-8?q?=E9=98=BF=E9=87=8C=E6=89=80=E6=9C=89?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=B1=87=E6=80=BB.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/ali.md | 39 ++++++++ Interview/bishi/shunfeng.md | 89 +++++++++++++++++++ ...56\351\242\230\346\261\207\346\200\273.md" | 75 ++++++++++++++++ Interview/src/Main.java | 86 ++---------------- README.md | 1 + 5 files changed, 211 insertions(+), 79 deletions(-) create mode 100644 Interview/bishi/ali.md create mode 100644 Interview/bishi/shunfeng.md create mode 100644 "Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" diff --git a/Interview/bishi/ali.md b/Interview/bishi/ali.md new file mode 100644 index 00000000..f2a9bc80 --- /dev/null +++ b/Interview/bishi/ali.md @@ -0,0 +1,39 @@ + +这给我难的... + +智力题 + +```java +import java.util.Arrays; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int T = sc.nextInt(); + for (int i = 0; i < T; i++) { + int n = sc.nextInt(); + int[] a = new int[n]; + for (int j = 0; j < n; j++) { + a[j] = sc.nextInt(); + } + Arrays.sort(a); + System.out.println(min(a, n - 1)); + } + } + + public static long min(int[] a, int end) { + if (end + 1 == 1) + return 0; + if (end + 1 <= 2) + return a[1]; + if (end + 1 == 3) + return a[2] + a[1] + a[0]; + if (a[0] + a[end - 1] > 2 * a[1]) + return a[1] * 2 + a[0] + a[end] + min(a, end - 2); + else + return a[end] + a[0] + min(a, end - 2); + } +} + +``` \ No newline at end of file diff --git a/Interview/bishi/shunfeng.md b/Interview/bishi/shunfeng.md new file mode 100644 index 00000000..512850d8 --- /dev/null +++ b/Interview/bishi/shunfeng.md @@ -0,0 +1,89 @@ +```java +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int m = sc.nextInt(); + int[] a = new int[n]; + int[][] b = new int[m][2]; +// int[] b = new int[m]; +// int[] c = new int[m]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); + } + for (int i = 0; i < m; i++) { + b[i][0] = sc.nextInt(); + b[i][1] = sc.nextInt(); +// b[i] = sc.nextInt(); +// c[i] = sc.nextInt(); +// list.add(b[i]); + } + Arrays.sort(a); + Arrays.sort(b, (x,y) -> x[1] == y[1] ? x[0] - y[0] : y[1] - x[1]); + boolean[] marked = new boolean[n]; + int ans = 0; + for (int i = 0; i < m; i++) { + int idx = bis(a, b[i][0]); + if (idx < a.length ) { + while (idx < a.length && marked[idx]) { + idx += 1; + } + if (idx < a.length) { + marked[idx] = true; + ans += b[i][1]; + } + } + } + System.out.println(ans); + } + + public static int bis(int[] a, int t) { + int l = 0, r = a.length; + while (l < r) { + int m = l + (r - l) / 2; + if (a[m] < t) { + l = m + 1; + } else { + r = m; + } + } + return l; + } +} +``` + +```java +import java.util.*; + + +class Main2 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int[][] a = new int[n][3]; + int len = 0; + for (int i = 0; i < n; i++) { + a[i][0] = sc.nextInt() - 1; + a[i][1] = sc.nextInt() - 1; + len = Math.max(len, a[i][1]); + a[i][2] = sc.nextInt(); + } + Arrays.sort(a, (x,y) -> x[1] - y[1]); + int[] dp = new int[len + 1]; + for (int i = 0; i < n; i++) { + int l = a[i][0]; + int r = a[i][1]; + int w = a[i][2]; + int[] tmp = Arrays.copyOfRange(dp, 0, l + 1); + int max = Arrays.stream(tmp).max().orElse(0); + if (l > 0) + dp[r] = Math.max(dp[r], max + w); + else + dp[r] = Math.max(dp[r], w); + } + System.out.println(Arrays.stream(dp).max().orElse(0)); + } +} + + +``` \ No newline at end of file diff --git "a/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" new file mode 100644 index 00000000..b3b54db3 --- /dev/null +++ "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" @@ -0,0 +1,75 @@ + +## Java + +- JVM分区 +- 垃圾收集算法 +- synchronized原理 +- 双亲委派机制 +- 线程池参数 +- newFixedTheadPool底层,优缺点 +- springmvc +- @Autowired原理 +- springboot原理 +- @OnConditionalClass实现原理 +- @OnMissingBean没有bean会怎么样,会classNotFound +- HashMap 底层数据结构 +- HashMap 什么时候扩容?装载因子和临界值默认是多少?扩容成多大?为什么容量是2的幂次方? +- 线程安全的Map?分段锁是如何实现的?JDK 1.8之后有哪些优化? +- Lock和Synchronized区别 +- AQS实现 +- 锁优化 +- String StringBuilder StringBuffer区别,特点,场景 +- ConcurrentMap 源码 + +## 场景题 +- 一个8G的服务器,堆的大小应该设置成多少 +- 海量数据求频率最多的100个 +- spring一个事务中调用另外一个事务,另一个- 事务发生异常会怎么样 +- 一个父类加载器能不能加载一个子类加载器,为什么 +- select * from A where id in (select id from B)怎么优化 +- 一个16G的内存堆分配多少,采用什么垃圾收集器,为什么用cms不用g1,为什么(面试官一直问为什么使用cms或者使用g1,回答了这两个的优缺点之后还是不满意) +- 多线程解析一个超大文件怎么处理,如果文件切分的时候关键信息被分到了不同的解析线程中怎么办 +- hashset是如何判断两个对象相等的 +- 如何要两个对象相等equals和hashcode这两个方法要怎么重写 +- hash算法(最开始讲hash冲突算法,面试官说不是这个,我又说对hash值对质数取余,面试官也说不是这个,不知道他要我回答啥。。。) +- 不使用任何优化,直接访问数据库,如何优化 (提示 redo、undo log的开销)(这里应该还可以答:批处理、连接池等) +- 行锁锁的是索引,如果一个表没有主键怎么加锁?(锁表) +- hashmap 为什么不直接用红黑树,还要用链表? +- 红黑树的特性?各种操作的时间复杂度?最多旋转几次达到平衡? +- finally 中 return 会发生什么? +- 如何破坏双亲委派 +- 浏览器中用户信息和密码的安全有没有考虑过 +- 两个线程如何交替打印A和B +- 100万个数,数据类型是int,给你8核CPU,8G内存,如何求数组的和? +- 很多个不重复的数,假设用的数组来装,我希望你实现一个方法,每次调用这个方法给我随机返回100个不重复,记住是随机的。 + + +## MySQL +- 索引怎么优化 +- 为什么用B+树 +- Innodb 和 Myisam 的区别 +- 聚集索引和非聚集索引 +- 为什么不用哈希索引? +- 有几种事务隔离级别? +- Innodb 行锁是如何实现的? +- mysql怎么分页 +- 索引为什么查找快 + +## Redis +- redis基本数据类型 +- redis集群?切片 + + +## 计网 +- tcp三次握手 +- HTTP 和RPC区别?还有呢? +- JWT流程,安全?Session? +- 盐有单独保存起来么 + +## 分布式 +- 怎么实现分布式锁 +- redis分布式锁有什么缺点,怎么解决 +- 单体应用和微服务区别,为什么要有微服务?好处?还有呢? +- 微服务流程? +- 分布式缓存? +- 分布式session原理 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 65449b9a..83b71562 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,85 +1,13 @@ -import java.util.*; - - -class Main2 { - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - int[][] a = new int[n][3]; - int len = 0; - for (int i = 0; i < n; i++) { - a[i][0] = sc.nextInt() - 1; - a[i][1] = sc.nextInt() - 1; - len = Math.max(len, a[i][1]); - a[i][2] = sc.nextInt(); - } - Arrays.sort(a, (x,y) -> x[1] - y[1]); - int[] dp = new int[len + 1]; - for (int i = 0; i < n; i++) { - int l = a[i][0]; - int r = a[i][1]; - int w = a[i][2]; - int[] tmp = Arrays.copyOfRange(dp, 0, l + 1); - int max = Arrays.stream(tmp).max().orElse(0); - if (l > 0) - dp[r] = Math.max(dp[r], max + w); - else - dp[r] = Math.max(dp[r], w); - } - System.out.println(Arrays.stream(dp).max().orElse(0)); - } -} +import java.util.HashMap; +import java.util.Map; public class Main { public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - int m = sc.nextInt(); - int[] a = new int[n]; - int[][] b = new int[m][2]; -// int[] b = new int[m]; -// int[] c = new int[m]; - for (int i = 0; i < n; i++) { - a[i] = sc.nextInt(); - } - for (int i = 0; i < m; i++) { - b[i][0] = sc.nextInt(); - b[i][1] = sc.nextInt(); -// b[i] = sc.nextInt(); -// c[i] = sc.nextInt(); -// list.add(b[i]); - } - Arrays.sort(a); - Arrays.sort(b, (x,y) -> x[1] == y[1] ? x[0] - y[0] : y[1] - x[1]); - boolean[] marked = new boolean[n]; - int ans = 0; - for (int i = 0; i < m; i++) { - int idx = bis(a, b[i][0]); - if (idx < a.length ) { - while (idx < a.length && marked[idx]) { - idx += 1; - } - if (idx < a.length) { - marked[idx] = true; - ans += b[i][1]; - } - } - } - System.out.println(ans); - } + Map map = new HashMap<>(); + map.put(10, 20); + System.out.println(map.get(new Integer(10))); + System.out.println(map.get(new Long(10))); + System.out.println(new Long(10).hashCode() == new Integer(10).hashCode()); - public static int bis(int[] a, int t) { - int l = 0, r = a.length; - while (l < r) { - int m = l + (r - l) / 2; - if (a[m] < t) { - l = m + 1; - } else { - r = m; - } - } - return l; } - - } \ No newline at end of file diff --git a/README.md b/README.md index a19b4d7c..a1d68771 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - [远景所有问题汇总](/Interview/mianjing/yuanjing/远景所有问题汇总.md) - [百度所有问题汇总](/Interview/mianjing/baidu/百度所有问题汇总.md) - [腾讯所有问题汇总](/Interview/mianjing/tx/腾讯所有问题汇总.md) +- [阿里所有问题汇总](/Interview/mianjing/ali/阿里所有问题汇总.md) ### 本人所经历的面经 - [招银面经](/Interview/mianjing/zhaoyin/my.md) From ee7e4b8193a0b533ef82764217c507cf2d0fbbda Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 21 Aug 2020 21:55:03 +0800 Subject: [PATCH 022/366] =?UTF-8?q?=E9=98=BF=E9=87=8C=E6=89=80=E6=9C=89?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=B1=87=E6=80=BB-=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...56\351\242\230\346\261\207\346\200\273.md" | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git "a/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" index b3b54db3..aa59382d 100644 --- "a/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" +++ "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" @@ -20,6 +20,13 @@ - 锁优化 - String StringBuilder StringBuffer区别,特点,场景 - ConcurrentMap 源码 +- CMS +- 线程生命周期(状态) +- ReentrantLock与AQS同步框架 +- CAS原理是什么? +- synchronize原理是什么? +- springboot 和 spring 的区别是什么? +- spring中bean的初始化过程 ## 场景题 - 一个8G的服务器,堆的大小应该设置成多少 @@ -42,29 +49,71 @@ - 两个线程如何交替打印A和B - 100万个数,数据类型是int,给你8核CPU,8G内存,如何求数组的和? - 很多个不重复的数,假设用的数组来装,我希望你实现一个方法,每次调用这个方法给我随机返回100个不重复,记住是随机的。 - +- 四个引用的具体应用场景 +- 垃圾回收器具体应用场景 +- 可达性分析算法具体应用场景 +- 垃圾回收器参数调优(给具体场景 大概就是并发低延时) +- 高并发多连接jvm 线程池参数调优(顺着来) +- 高并发系统调优(再顺一下) +- 队列与栈的 应用场景 +- 点赞高并发怎么办? +- 消息中间件用过么?怎么自己设计一个消息中间件? +- 假设catch,final,catch中有return,那么final还会不会执行 +- 假设1亿的11位的手机号,运行空间128M,如果要进行排序,那么要怎么设计 +- jvm的设计角度,gc你怎么优化? +- 你怎么设计md5函数? +- cpu一直被占满,怎么排查? +- 让你自己设计一个垃圾回收器,尽可能提高回收效率,你会怎么设计?(ZGC和香浓多,整就完事了) +- MD5加密算法,不使用hashmap和映射,你自己来设计一个同类型的,给定字符串可以生成64位随机数的,你怎么设计 +- 利用java现有的东西,让你设计一个对象,实现类似synchronize的功能,使得多个线程不冲突,你如何设计?(ThreadLocal玩起来) +- synchronize锁定.class和锁定一个实例有什么区别? +- explain中的key字段的值等于ref时,有没有触发索引? +- 如何实现单点登录,如何实现权限控制,用户密码泄露后如何保证安全性 +- spring中循环依赖如何解决?如果让你来实现你怎么做? +- 如果我的服务器ip地址变了,客户端如何感知到呢? +- 轮询的负载均衡的缺点是什么?如何改进? +- 让你来实现真正的负载均衡,你如何做?(我回答记录每台服务器在最近的一段时间内接收的请求的数量,每次都把请求发送给数量最小的服务器,后来面试官提醒我还应当考虑每个请求耗费的时间) +- 秒杀项目中静态资源CDN怎么做? +- css文件能放到CDN上吗? +- 秒杀缓存如何与数据库的数据保持一致性? +- 通过广播的方式去更新每个节点的缓存,如果某个节点的缓存更新失败,那么如何排查是哪个节点呢? ## MySQL - 索引怎么优化 - 为什么用B+树 - Innodb 和 Myisam 的区别 -- 聚集索引和非聚集索引 +- 聚集索引和非聚集索引 创建以后的文件大小 - 为什么不用哈希索引? - 有几种事务隔离级别? - Innodb 行锁是如何实现的? - mysql怎么分页 - 索引为什么查找快 +- 慢查询如何优化 ## Redis - redis基本数据类型 - redis集群?切片 - +- 为什么用Redis,Redis宕机了怎么办,数据呢?怎么保持热点数据?过期机制,淘汰机制? +- redis是单线程么?为什么快?io模型什么怎么样的,具体是怎么个流程? ## 计网 - tcp三次握手 - HTTP 和RPC区别?还有呢? - JWT流程,安全?Session? - 盐有单独保存起来么 +- HTTP 和 HTTPS的区别 +- 讲一下ftp +- cookies +- http以及https 以及加密手段 +- 浏览器输入url后的一系列操作 +- HTTP状态码 +- 为什么用Jwt,cookie,session有什么好处? +- TCP怎么确保可靠性的?丢包和串包怎么办? +- DNS说一下? + +## 操作系统 +- 上下文切换 +- 内核态用户态 ## 分布式 - 怎么实现分布式锁 @@ -72,4 +121,12 @@ - 单体应用和微服务区别,为什么要有微服务?好处?还有呢? - 微服务流程? - 分布式缓存? -- 分布式session原理 \ No newline at end of file +- 分布式session原理 +- SpringCloud及其组件你了解哪些? + +## 算法 +- n个线程顺序循环打印0-100 +- 手写LinkedList的数据结构并写出add和remove算法 +- 微信红包算法实现 +- 链表如何找环 +- 有序链表合并 From 0530fd06f8e98276050f87f0f23525cef0bda869 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 21 Aug 2020 22:22:26 +0800 Subject: [PATCH 023/366] =?UTF-8?q?=E9=98=BF=E9=87=8C=E6=89=80=E6=9C=89?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=B1=87=E6=80=BB-=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...56\351\242\230\346\261\207\346\200\273.md" | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git "a/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" index aa59382d..aad72cbd 100644 --- "a/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" +++ "b/Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" @@ -27,6 +27,9 @@ - synchronize原理是什么? - springboot 和 spring 的区别是什么? - spring中bean的初始化过程 +- 谈一谈异常机制 +- Spring IOC和AOP +- Arrays.sort()底层原理 ## 场景题 - 一个8G的服务器,堆的大小应该设置成多少 @@ -77,6 +80,17 @@ - css文件能放到CDN上吗? - 秒杀缓存如何与数据库的数据保持一致性? - 通过广播的方式去更新每个节点的缓存,如果某个节点的缓存更新失败,那么如何排查是哪个节点呢? +- 消费者消费失败是怎么处理的 +- 如何保证消息消费的顺序性 +- 如何保证消息不被重复消费 +- 项目中要进行分布式扩展应该怎么做 +- 缓存和mysql数据一致性如何保证 +- 微信步数排行,假设百万级用户,怎么排序,实时更新(我说排序就是海量数据排序的方式分组排序再归并,然后实时更新的话也是组内更新组内排序,但是每组的分度值可能不一样,比如0-2000可能一组,但2000-4000可能要分五组之类的,因为步数特别少和特别多的都是少数。) +- 发生了OOM,应该怎么去分析解决?jvm调优 +- 什么时候发生线程的上下文切换? +- CAS是硬件实现还是软件实现 +- 除了wait和notifyall,还有什么办法实现类似的功能 +- 微信抢红包设计(只讲了类似多线程抢、Semphore,缓存)、海量文件找重复次数最多的个数(分治) ## MySQL - 索引怎么优化 @@ -89,12 +103,14 @@ - mysql怎么分页 - 索引为什么查找快 - 慢查询如何优化 +- mvcc原理 ## Redis - redis基本数据类型 - redis集群?切片 - 为什么用Redis,Redis宕机了怎么办,数据呢?怎么保持热点数据?过期机制,淘汰机制? - redis是单线程么?为什么快?io模型什么怎么样的,具体是怎么个流程? +- 缓存穿透、雪崩,redis持久化机制 ## 计网 - tcp三次握手 @@ -102,6 +118,8 @@ - JWT流程,安全?Session? - 盐有单独保存起来么 - HTTP 和 HTTPS的区别 +- https的加密证书怎么获取 +- 加密后客户端保留的是公钥还是私钥 - 讲一下ftp - cookies - http以及https 以及加密手段 @@ -114,6 +132,7 @@ ## 操作系统 - 上下文切换 - 内核态用户态 +- 多路复用IO模型,select,poll,epoll ## 分布式 - 怎么实现分布式锁 @@ -124,9 +143,12 @@ - 分布式session原理 - SpringCloud及其组件你了解哪些? + ## 算法 - n个线程顺序循环打印0-100 - 手写LinkedList的数据结构并写出add和remove算法 - 微信红包算法实现 - 链表如何找环 - 有序链表合并 +- 共计9个苹果,有2只猴子,一个猴子每次拿2个苹果,一个猴子每次拿3个苹果,如果剩余的苹果不够猴子每次拿的数量,则2只猴子停止拿苹果,请用java多线程模拟上面的描述,要求性能尽可能高效(这个题开始是用可重入锁写的,结束之后自己本地测试发现程序不会自动结束,后来改成用AtomicInteger和cas来实现了) +- 设计一个多线程打印程序,第i个线程只打印i-1数字,比如第1个线程打印数字0,第2个线程只打印数字1,依次类推。任意给定一个数字序列,比如3382019835830,能够使用该程序打印出来。 From f7bc42c2598840459c4d2546cb25f3a4da5a4609 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 21 Aug 2020 22:57:51 +0800 Subject: [PATCH 024/366] =?UTF-8?q?=E9=98=BF=E9=87=8C=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/ali.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Interview/bishi/ali.md b/Interview/bishi/ali.md index f2a9bc80..360de4ef 100644 --- a/Interview/bishi/ali.md +++ b/Interview/bishi/ali.md @@ -36,4 +36,32 @@ public class Main { } } +``` + + +```python + +# 容易想到的是过河方案就是最轻的人作为摆渡人,一趟一趟运 +# 还有一种是,最轻的人把第二轻的送到对岸,自己回来,再让最重的两个人过去,第二轻的再把船划回来 +# 一直比较这两种方案,直到人数小于3 +def first(): + T = int(input()) + for _ in range(T): + n = int(input()) + weights = list(map(int, input().split())) + sorted(weights) + res = 0 + while n > 3: + res += min(weights[1] * 2 + weights[0] + weights[-1], weights[0] * 2 + weights[-1] + weights[-2]) + weights.pop() + weights.pop() + n -=2 + if n == 1 or n == 3: + res += sum(weights) + elif n == 2: + res += max(weights) + print(res) + +first() + ``` \ No newline at end of file From dadae9e389985d69d6df044db3dbc270b97618fc Mon Sep 17 00:00:00 2001 From: DreamCats Date: Tue, 25 Aug 2020 15:44:08 +0800 Subject: [PATCH 025/366] =?UTF-8?q?=E7=8C=BF=E8=BE=85=E5=AF=BC=E6=89=80?= =?UTF-8?q?=E6=9C=89=E9=97=AE=E9=A2=98=E6=B1=87=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/bishi/tx.md | 141 +++++++++++++++++ ...56\351\242\230\346\261\207\346\200\273.md" | 147 ++++++++++++++++++ Interview/src/Main.java | 13 +- README.md | 1 + 4 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 Interview/bishi/tx.md create mode 100644 "Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" diff --git a/Interview/bishi/tx.md b/Interview/bishi/tx.md new file mode 100644 index 00000000..f0fb9456 --- /dev/null +++ b/Interview/bishi/tx.md @@ -0,0 +1,141 @@ +```java +class Main1 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int k = sc.nextInt(); + for (int i = 0; i < n; i++) { + int a = sc.nextInt(); + if (i != k - 1) { + System.out.print(a); + } + if (i != n - 1) + System.out.print(" "); + + } + } +} +``` + +```java +class Main2 { + static TreeSet ret = new TreeSet<>(); + static int k = 0; + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + char[] cs = s.toCharArray(); + Arrays.sort(cs); + k = sc.nextInt(); + boolean[] marked = new boolean[s.length()]; + dfs(0, new StringBuilder(), cs, marked); + for (String s1 : ret) { + System.out.println(s1); + return; + } + } + + public static void dfs(int start, StringBuilder sb, char[] cs, boolean[] marked) { + if (sb.length() == k){ + ret.add(sb.toString()); + return; + } + for (int i = start; i < cs.length; i++) { + if (i != 0 && cs[i] == cs[i - 1] && !marked[i - 1]) + continue; + marked[i] = true; + sb.append(cs[i]); + dfs(start+1, sb, cs, marked); + sb.deleteCharAt(sb.length() - 1); + marked[i] = false; + } + } + + +} +``` + +```java +class Main3 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int T = sc.nextInt(); + while (T-- > 0) { + long n = sc.nextLong(); + if (n == 1) + System.out.println(1); + else + System.out.println(maxVal(n)); + } + } + + public static long maxVal(long n) { + long l = 0, r = n; + long max = 0; + while (l < r) { + max = Math.max(max, (sum(l) + sum(r))); + l++; + r--; + } + return max; + } + + public static long sum(long n) { + long sum = 0; + while (n != 0) { + sum += n % 10; + n /= 10; + } + return sum; + } +} +``` + +```java +class Main4 { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int[] a = new int[n]; + for (int i = 0; i < n; i++) { + a[i] = sc.nextInt(); + } + System.out.println(minCount(a)); + } + public static int minCount(int[] a) { + if (a.length == 1) + return a[0]; + if (a.length == 2) + return Math.max(a[0], a[1]); + boolean[] marked = new boolean[a.length]; + int stopCnt = 0; + int cnt = 1; + while (stopCnt < marked.length - 1) { + for (int i = 0; i < a.length; i++) { + if (marked[i]) + continue; + if (cnt == 1 && a[i] == cnt) { + marked[i] = true; + stopCnt++; + }else if (i < a.length - 1 && a[i] == cnt && a[i + 1] == cnt) { + int j = i; + while (j < a.length - 1 && a[j] == a[j + 1] && a[j] == cnt) { + marked[i] = true; + marked[i + 1] = true; + stopCnt += 2; + j += 2; + } + + }else if (a[i] == cnt) { + marked[i] = true; + stopCnt++; + } + if (stopCnt > a.length - 1) + return cnt; + } + cnt++; + } + return cnt; + } +} +``` \ No newline at end of file diff --git "a/Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" "b/Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" new file mode 100644 index 00000000..ed4904b9 --- /dev/null +++ "b/Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" @@ -0,0 +1,147 @@ + + +## 算法 + +- 括号匹配(lc:22):1 +- 对角线遍历(lc:498) +- 累加数(lc:306) +- 二叉树中序遍历的后继节点 +- 判断—棵二叉树是否是对称的 +- 有若干[30,50,100]优惠券,求给定一个数值求最优的方案。例如∶价格是40=>[30]80 =>[30,50].110=>[30,30,50] +- 搜索旋转数组 +- 带精度的sqrt +- 矩阵中的最长递增路(lc:329) +- 删除单向链表中重复的数字 +- 给一个以字符串表示的非负整数num,移除这个数中的k为数字,使得剩下的数字最小(单调栈) +- 给定一个二叉搜索树,并给出他的前序序列,输出中序序列,时间复杂度O(n),并给出证明 +- 非递归中序遍历 +- 非递归后序遍历 +- 单链表代表一个大整数,给它低位加上一个数,返回加上后的链表 +- 链表压缩,a->a->a->d->d->b->z->z->null,转换为3a->2d->1b->2z->null (写了一个生成新的链表,又要求重写,在原链表上修改) +- 给一棵树,当一个节点的左节点下的两个节点都是1,或者是右节点下的两个节点都是1时,这个节点满足条件。返回所有满足查找条件的节点 +- 求树的直径 +- 判断一棵树是否为二叉平衡树 +- 大数相乘 +- 两个降序链表的第k大元素 +- 二叉树的深度 +- 两个字符串的最长公共子串 +- 给一个队列,借助两个栈,将队列中的元素排序 +- 字符串排序 首先按照长度排序,然后按照字符串排序。I am a an student.------->I a am an student +- 两个字符串是否由相同字符构成 +- N皇后 +- 升序链表转成二叉平衡搜索树 +- 给定一个字符矩阵,里边只有O和X,把所有被X包裹的O变为X +- 一个单向链表,根据value的奇偶性拆成2个链表,并且返回的两个链表是升序的 +- 数组求每一个数的下一个比它大的数字(先写了O(n2),写完想起来了O(n)的栈) +- 给一个数,求出树中祖先节点和其后代节点差值的最大值(思路对了,手撕没过) +- 整数数组拼接成最小数 +- 顺时针递增打印数组 +- 最长回文串 +- 最大正方形面积 +- 合并k个有序链表 +- 区间列表的交集(lc:986) +- 判断两个字符串是否是相似字符串,相似的含义是只允许交换一次字符串的两个位置,使得和第二个字符串相等,那么他们就是相似的。 +```java +ab, ba => true +abax, abax => true +abc, abc => false +aac, abc => false +1 字符串长度是否相等 +2 排序后字符串是否相等 +3 双指针判断相同位置不同字符的对数是否等于2 +4 不同字符为0时时,是否有重复字符 +``` +- 删除倒数第K个节点 +- Z字打印二叉树 +- 非严格递增数组1,2,3,3,3,4,5,找到目标数字所在的第一个位置,无则输出-1,如3,输出2,要求O(log n);(变种二分) +- 从l到r反转链表 +- 最小路径和 +- 输入两个数组,从两个数组中各取一个值,输出两个值的和为0的所有组合并需要去重。 +```java +输入: nums1={1 ,-1,0,0},nums2={-1,-1,0,1} ,输出:{{1,-1},{0,0}} +``` +- topK +- 求一个长度为n的无序数组中,第m大的数 topk? +- 找二叉查找树的两个节点相差的最小值(非递归中序+对比相邻节点的差) +- 二叉树剪枝 +- 二叉树指定层的节点个数 +- 双向链表奇偶位置节点拆分 +- 反转链表,迭代+递归 +- 字符串相加 +- 整数拆分 +- 给一个字符串数组,把由相同字母组成的字符串分入同组 +- 同样给了一个整型数组 [100,2,1,4,3,3,101,200] 从这个数组找到连续元素的长度,比如1,2,3,4 最长输出4。 +- 分割数组(lc:915) +- LeetCode求两个链表和,从头到尾相加即可 +- 一个链表,给定一个目标值,比目标值大的节点去到链表后面,要求不改变相对顺序 比如1 3 2 1 3 2 1 给定目标值2,返回链表1 2 1 2 1 3 33. +- 最长公共子序列 +- 判断二叉树是不是BST +- 求一个数的平方根,精确到0.01。 +- 重排链表(lc:143) +- 利用循环链表写一个队列 + + + +## Java +- 介绍一下垃圾收集器 +- g1和cms有什么区别 +- 为什么g1可以在保证用户吞吐量的情况下进行垃圾收集 +- Java中实现加锁的方式 +- sychronized及锁升级过程 +- 底层AQS实现原理 +- ConcurrentHashMap +- 线程状态 +- 线程池 +- 深拷贝,浅拷贝 +- 说说线程安全 +- Lock和Synchronized的区别 +- 逃逸分析 +- JVM内存区域,这些区域会内存溢出吗,栈什么情况下会溢出,堆呢 +- 堆区的垃圾回收机制,为什要用分代策略? +- 为什么老年代不采用复制算法? +- SpringBoot自动装配的原理 +- Spring中Aop是如何实现的? +- Cglib和JDK的动态代理有什么区别? +- Spring是怎么实现Ioc的? +- Java的反射机制了解吗? +- 反射机制中获得实例的方式有几种?分别是什么? +- getClass和forName得到的Class实例是同一个实例还是不同的实例? +- 讲一下ClassLoader? +- String,StringBuilder,StringBuffer +- HashMap + +## mysql +- mysql建立索引的原则 +- 聚簇索引和非聚簇索引区别 +- B+树结构、为什么不用B树或二叉树 +- 事务四大特性 +- 事务隔离级别 +- 用mysql实现一个分布式锁 +- MVCC具体原理 + +## 计网 +- http讲一下 +- get和post的区别 +- get是否可以有请求体 +- 手写http request报文和 http response报文 +- 四次挥手过程 +- close_wait作用 +- TCP有什么特点? +- TCP具体的可靠性指什么?怎么确保数据的正确性? +- 流量控制和阻塞控制的区别是什么? +- Socket跟其他的方式有什么不一样? +- OSI七层网络模型 +- 网络层有哪些协议? +- ICMP解释一下,有什么作用?除了判断是否能正常通信还有什么作用? + +## mq +- 如果有几百万消息堆积,如何处理 +- 如何保证消息消费的可靠性,顺序性 +- 消息队列方式有什么优缺点? +- 消息队列为什么比共享内存慢?(需要额外的复制) +- 共享内存有什么缺点?它怎么保证同步操作? + + + +## 其他 +- 令牌桶 diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 83b71562..42501d61 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,13 +1,10 @@ -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; public class Main { public static void main(String[] args) { - Map map = new HashMap<>(); - map.put(10, 20); - System.out.println(map.get(new Integer(10))); - System.out.println(map.get(new Long(10))); - System.out.println(new Long(10).hashCode() == new Integer(10).hashCode()); - + List a = new ArrayList<>(); + List b = new ArrayList<>(); + System.out.println(a.getClass() == b.getClass()); } } \ No newline at end of file diff --git a/README.md b/README.md index a1d68771..a84dd572 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [百度所有问题汇总](/Interview/mianjing/baidu/百度所有问题汇总.md) - [腾讯所有问题汇总](/Interview/mianjing/tx/腾讯所有问题汇总.md) - [阿里所有问题汇总](/Interview/mianjing/ali/阿里所有问题汇总.md) +- [猿辅导所有问题汇总](/Interview/mianjing/yuanfudao/猿辅导所有问题汇总.md) ### 本人所经历的面经 - [招银面经](/Interview/mianjing/zhaoyin/my.md) From ea73d34a233fa5c56c62acbbf68024aabe317f0d Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 26 Aug 2020 10:24:48 +0800 Subject: [PATCH 026/366] =?UTF-8?q?shein=20=E4=BA=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/shein/my.md | 10 ++++++++++ Interview/src/Main.java | 25 +++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Interview/mianjing/shein/my.md b/Interview/mianjing/shein/my.md index 9d804b67..9123cfa5 100644 --- a/Interview/mianjing/shein/my.md +++ b/Interview/mianjing/shein/my.md @@ -14,3 +14,13 @@ 10. 什么会锁整张表 11. Explain都有啥玩意 12. 如果是你,如何慢查询优化 + +二面 +时间:8.24 19:50 时长:25min + +1. 自我介绍 +2. 简单说一下微服务 +3. 为什么用索引 +4. 什么情况下用索引 +5. 家庭情况 +6. 反问:如何看待消息队列产生 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 42501d61..7039faa4 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,10 +1,27 @@ import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class Main { - public static void main(String[] args) { - List a = new ArrayList<>(); - List b = new ArrayList<>(); - System.out.println(a.getClass() == b.getClass()); + public int[][] merge(int[][] intervals) { + StringBuilder sb = new StringBuilder(); + sb.cha + } +} + + +class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x){val = x;} +} + +class ListNode { + int val; + ListNode next; + + public ListNode(int val) { + this.val = val; } } \ No newline at end of file From f83a49e6900155d5a1b051eeee4c6c23753cd28c Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 27 Aug 2020 15:42:59 +0800 Subject: [PATCH 027/366] =?UTF-8?q?=E8=B0=88=E8=B0=88AQS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sad/\350\260\210\350\260\210AQS.md" | 66 ++++++++++++++++++- Interview/src/Main.java | 58 ++++++++++++++-- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git "a/Interview/sad/\350\260\210\350\260\210AQS.md" "b/Interview/sad/\350\260\210\350\260\210AQS.md" index ef2208d8..f29d306d 100644 --- "a/Interview/sad/\350\260\210\350\260\210AQS.md" +++ "b/Interview/sad/\350\260\210\350\260\210AQS.md" @@ -1 +1,65 @@ -> 这个先留着,这个内容有点多,待我这周结束之后来写。 \ No newline at end of file +> 这个先留着,这个内容有点多,待我这周结束之后来写。 + +面试官:AQS是什么? + +我:AbstractQueuedSynchronizer抽象同步队列。说白了,就是个FIFO双向队列。其内部通过节点head和tail记录对首和队尾元素。 + +**state**:在AQS中维持了一个单一的状态信息state,可以通过**getState**、**setState**、**compareAndSetState**函数修改其值。 + +- **ReentrantLock**:state可以用来表示当前线程获取锁的可重入次数。 + +- **ReentrantReadWriteLock**:state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数。 +- **semaphore**:state用来表示当前可用信号的个数。 +- **CountDownlatch**:state用来表示计数器当前的值。 + +对于AQS来讲,线程同步的关键是对状态值**state**进行操作。 + +在独占方式下获取和释放资源使用的方法: + +```java +void acquire(int arg); +void acquireInterruptibly(int arg); +boolean release(int arg); +``` + +在共享方式获取和释放资源使用的方法: + +```java +void acquireShared(int arg); +void acquireSharedInterruptibly(int arg); +boolean releaseShared(int arg); +``` + +使用独占方式获取的资源是与**具体线程绑定**的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时会**发现当前该资源不是自己持有的**,就会在**获取失败后被阻塞**。(比如ReentrantLock) + +对应的共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的资源,则当前线程只需要使用CAS方式进行获取即可。(比如semaphore) + +看一下**acquire**方法: + +```java +// tryAcquire 具体的子类去实现,并维护state的状态 +public final void acquire(int arg) { + if (!tryAcquire(arg) && + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果失败标记状态,入队 + selfInterrupt(); +} +``` + +看一下**release**方法: + +```java +// tryRelease 具体的子类是实现,并设置state的状态 +public final boolean release(int arg) { + if (tryRelease(arg)) { + Node h = head; + if (h != null && h.waitStatus != 0) + unparkSuccessor(h); // 调用unpark唤醒队列的线程,并调用tryAcquire尝试,看是否需要,如果不需要,继续挂起 + return true; + } + return false; +} +``` + +**acquireShared和releaseShared**和上面的方法的思想差不多 + + diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 7039faa4..e77d1c15 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,14 +1,37 @@ import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class Main { - public int[][] merge(int[][] intervals) { - StringBuilder sb = new StringBuilder(); - sb.cha + public static void main(String[] args) { + int[] nums = {3, 2, 1, 6, 5, 4}; + ListNode node = ListNodeTools.getListNode(nums); + ListNodeTools.printListNode(node); + reorderList(node); + ListNodeTools.printListNode(node); } -} + public static void reorderList(ListNode head) { + List list = new ArrayList<>(); + ListNode p = head; + while (p != null) { + list.add(p.val); + p = p.next; + } + int l = 0, r = list.size() - 1; + int idx = 0; + int[] nums = new int[list.size()]; + while (l < r) { + nums[idx++] = list.get(l++); + nums[idx++] = list.get(r--); + } + p = head; + idx = 0; + while (p != null) { + p.val = nums[idx++]; + p = p.next; + } + } +} class TreeNode { int val; @@ -24,4 +47,27 @@ class ListNode { public ListNode(int val) { this.val = val; } -} \ No newline at end of file +} + + +class ListNodeTools { + public static ListNode getListNode(int[] nums) { + ListNode dummy = new ListNode(-1); + ListNode pre = dummy; + for (int num : nums) { + pre.next = new ListNode(num); + pre = pre.next; + } + return dummy.next; + } + + public static void printListNode(ListNode node) { + String s = ""; + ListNode p = node; + while (p != null) { + s += p.val + "->"; + p = p.next; + } + System.out.println(s+"null"); + } +} From 9b0bb0ef6847c26ed6c3f4aeaccfb8cb9e35f735 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 27 Aug 2020 16:10:38 +0800 Subject: [PATCH 028/366] =?UTF-8?q?=E8=B0=88=E8=B0=88ReentrantLock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\350\260\210\350\260\210ReentrantLock.md" | 106 ++++++++++++++++++ README.md | 1 + 2 files changed, 107 insertions(+) create mode 100644 "Interview/sad/\350\260\210\350\260\210ReentrantLock.md" diff --git "a/Interview/sad/\350\260\210\350\260\210ReentrantLock.md" "b/Interview/sad/\350\260\210\350\260\210ReentrantLock.md" new file mode 100644 index 00000000..188259e3 --- /dev/null +++ "b/Interview/sad/\350\260\210\350\260\210ReentrantLock.md" @@ -0,0 +1,106 @@ +> 经常拿synchronized和ReentrantLock做比较,那就来看看这个锁的一些重点吧 + +面试官:了解ReentrantLock嘛? + +我:当然,它实现了Lock接口,同时调用内部类sync继承的AQS,先说一下**state**:它代表获取该锁的**可重入次数**,在默认下,state的值为0表示**当前锁没有被任何线程持有**。当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,并且记录该锁的持有者为当前线程。若该线程第二次获取该锁后,状态值被设置2。 + +我们来看一下lock方法: + +```java +public void lock() { + sync.lock(); // 委托给sync了 +} +``` + +而且它具有非公平锁还是公平锁的特性。比如,我们可以看构造方法 + +```java +// 这不,由fair来决定是公平的还是非公平的 +public ReentrantLock(boolean fair) { + sync = fair ? new FairSync() : new NonfairSync(); +} +``` + +- 非公平锁: + +```java +final void lock() { + if (compareAndSetState(0, 1)) // cas设置state的状态,0->1 + setExclusiveOwnerThread(Thread.currentThread()); // 设置独占 + else + acquire(1); // 否则尝试,如果还是当前线程,就state累加,若不是,则挂起 +} +// 看看 acquire +public final void acquire(int arg) { + if (!tryAcquire(arg) && + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); +} +// 看看重写的tryAcquire +protected final boolean tryAcquire(int acquires) { + return nonfairTryAcquire(acquires); +} +// 重点来了 +final boolean nonfairTryAcquire(int acquires) { + final Thread current = Thread.currentThread(); + int c = getState(); + if (c == 0) { // 0->acquires + if (compareAndSetState(0, acquires)) { + setExclusiveOwnerThread(current); + return true; + } + } + else if (current == getExclusiveOwnerThread()) { // 否则判断该资源是否被该线程持有 + int nextc = c + acquires; // 持有,则+acquires + if (nextc < 0) // overflow + throw new Error("Maximum lock count exceeded"); + setState(nextc); + return true; + } + return false; +} +``` + +- 公平锁和上面的差不多,就多了一个这样的判断: + +```java +if (c == 0) { + if (!hasQueuedPredecessors() && // 队列中是否轮到该线程了 + compareAndSetState(0, acquires)) { + setExclusiveOwnerThread(current); + return true; + } +} +``` + +我们来看一下unlock()方法: + +```java +public void unlock() { + sync.release(1); +} +// release看看 +public final boolean release(int arg) { + if (tryRelease(arg)) { // 依然调 + Node h = head; + if (h != null && h.waitStatus != 0) + unparkSuccessor(h); + return true; + } + return false; +} +// 看看sync重写的 +protected final boolean tryRelease(int releases) { + int c = getState() - releases;// 并不会设置为0,而是减releases + if (Thread.currentThread() != getExclusiveOwnerThread()) + throw new IllegalMonitorStateException(); + boolean free = false; + if (c == 0) { // 如果状态为0了,则free为true + free = true; + setExclusiveOwnerThread(null); // 并且将持有该资源的线程设置为null + } + setState(c); // cas操作 + return free; +} +``` + diff --git a/README.md b/README.md index a84dd572..0c278395 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ - [谈谈CAS](/Interview/sad/谈谈CAS.md) - [谈谈ThreadLocal](/Interview/sad/谈谈ThreadLocal.md) - [谈谈AQS](/Interview/sad/谈谈AQS.md) +- [谈谈ReentrantLock](/Interview/sad/谈谈ReentrantLock.md) - [谈谈死锁](/Interview/sad/谈谈死锁.md) - [生产者消费者模型](/Interview/sad/生产者消费者模型.md) - [类文件结构](/Interview/sad/类文件结构.md) From b276341993df8642e81dd9ba2d8894fa9caae88b Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 27 Aug 2020 17:03:59 +0800 Subject: [PATCH 029/366] =?UTF-8?q?=E8=B0=88=E8=B0=88ReentrantReadWriteLoc?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\210\350\260\210ReentrantReadWriteLock.md" | 184 ++++++++++++++++++ README.md | 1 + 2 files changed, 185 insertions(+) create mode 100644 "Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" diff --git "a/Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" "b/Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" new file mode 100644 index 00000000..a6390b18 --- /dev/null +++ "b/Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" @@ -0,0 +1,184 @@ +> 聊一聊读写锁 + +面试官:了解AQS的读写锁嘛,知道其原理嘛? + +我:刚好了解,我们知道,在一些读多写少的场景中,若是用ReentrantLock效率显然不高,于是ReentrantReadWriteLock问世。 + +老规矩: + +```java +// 维护了readlock和writelock +private final ReentrantReadWriteLock.ReadLock readerLock; +/** Inner class providing writelock */ +private final ReentrantReadWriteLock.WriteLock writerLock; +/** Performs all synchronization mechanics */ +final Sync sync; // 同样的是sync 继承aqs + +// 可惜的是state依然是一个, 但是不慌 +// 高16位表示读状态,低16位表示获取到写锁的线程的可重入锁 +static final int SHARED_SHIFT = 16; +// 共享锁的读锁的状态单位值65536 +static final int SHARED_UNIT = (1 << SHARED_SHIFT); +// 共享锁线程最大个数65536 +static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; +// 排它锁写锁掩码,二进制 15个1 +static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; +// 返回读锁线程数 +static int sharedCount(int c) { return c >>> SHARED_SHIFT; } +// 返回写锁可重入个数 +static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } +``` + +直奔主题:写锁的获取与释放 + +**lock:** + +```java +// lock +public void lock() { + sync.acquire(1); +} +// acquire +public final void acquire(int arg) { + if (!tryAcquire(arg) && // 老规矩了 + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); +} +// tryAcquire +protected final boolean tryAcquire(int acquires) { + Thread current = Thread.currentThread(); + int c = getState(); + int w = exclusiveCount(c); // 得到低16位的值 + // 如果c!=0, 说明资源已经被其他读锁或者写锁的线程所获取 + if (c != 0) { + // w==0 代表低16位位0,那么高16位不为0,那么获取了读锁 + if (w == 0 || current != getExclusiveOwnerThread()) + return false; + // 到这一步,已经是写锁了,那么判断可重入次数 + if (w + exclusiveCount(acquires) > MAX_COUNT) + throw new Error("Maximum lock count exceeded"); + // Reentrant acquire + // 更新state + setState(c + acquires); + return true; + } + // 是否第一个写锁获取线程 + if (writerShouldBlock() || + !compareAndSetState(c, c + acquires)) + return false; + setExclusiveOwnerThread(current); + return true; +} +``` + +**unlock:** + +```java +// unlock +public void unlock() { + sync.release(1); +} +// 不提了 +public final boolean release(int arg) { + if (tryRelease(arg)) { + Node h = head; + if (h != null && h.waitStatus != 0) + unparkSuccessor(h); + return true; + } + return false; +} +// 重点 +protected final boolean tryRelease(int releases) { + if (!isHeldExclusively()) + throw new IllegalMonitorStateException(); + int nextc = getState() - releases; // 依然是减 + boolean free = exclusiveCount(nextc) == 0; // 判断是否为0 + if (free) // 移除该写锁持有的线程 + setExclusiveOwnerThread(null); + setState(nextc); // 更新 + return free; +} +``` + +读锁的获取和释放: + +**lock:** + +```java +protected final int tryAcquireShared(int unused) { + Thread current = Thread.currentThread(); + int c = getState(); + // 判断是否被写锁占用 + if (exclusiveCount(c) != 0 && + getExclusiveOwnerThread() != current) + return -1; + // 获取读锁的数量 + int r = sharedCount(c); + // 尝试获取锁,多个读线程只有一个会成功,不成功的进入fullTryAcqureShared进行重试 + if (!readerShouldBlock() && + r < MAX_COUNT && + compareAndSetState(c, c + SHARED_UNIT)) { + // 第一个线程获取读锁 + if (r == 0) { + firstReader = current; + firstReaderHoldCount = 1; + // 如果当前线程是第一个获取读锁的线程 + } else if (firstReader == current) { + firstReaderHoldCount++; + } else { + // 记录最后一个获取读锁的线程的线程或记录其他线程读锁的可重入数 + HoldCounter rh = cachedHoldCounter; + if (rh == null || rh.tid != getThreadId(current)) + cachedHoldCounter = rh = readHolds.get(); + else if (rh.count == 0) + readHolds.set(rh); + rh.count++; + } + return 1; + } + // 自旋获取 + return fullTryAcquireShared(current); +} +``` + +**unlock:** + +```java +public final boolean releaseShared(int arg) { + if (tryReleaseShared(arg)) { + doReleaseShared(); // 释放所有 + return true; + } + return false; +} +protected final boolean tryReleaseShared(int unused) { + Thread current = Thread.currentThread(); + if (firstReader == current) { + // assert firstReaderHoldCount > 0; + if (firstReaderHoldCount == 1) + firstReader = null; + else + firstReaderHoldCount--; + } else { + HoldCounter rh = cachedHoldCounter; + if (rh == null || rh.tid != getThreadId(current)) + rh = readHolds.get(); + int count = rh.count; + if (count <= 1) { + readHolds.remove(); + if (count <= 0) + throw unmatchedUnlockException(); + } + --rh.count; + } + // 循环直到自己的读计数-1.CAS更新成功 + for (;;) { + int c = getState(); + int nextc = c - SHARED_UNIT; + if (compareAndSetState(c, nextc)) + return nextc == 0; + } +} +``` + diff --git a/README.md b/README.md index 0c278395..4439e71b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ - [谈谈ThreadLocal](/Interview/sad/谈谈ThreadLocal.md) - [谈谈AQS](/Interview/sad/谈谈AQS.md) - [谈谈ReentrantLock](/Interview/sad/谈谈ReentrantLock.md) +- [谈谈ReentrantReadWriteLock](/Interview/sad/谈谈ReentrantReadWriteLock.md) - [谈谈死锁](/Interview/sad/谈谈死锁.md) - [生产者消费者模型](/Interview/sad/生产者消费者模型.md) - [类文件结构](/Interview/sad/类文件结构.md) From 81ccc27c927d3a798e6ac5ec6cc6ac1f6fdd2acf Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 27 Aug 2020 20:38:51 +0800 Subject: [PATCH 030/366] =?UTF-8?q?=E8=B0=88=E8=B0=88CountDownLatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\350\260\210\350\260\210CountDownLatch.md" | 47 +++++++++++++++++++ README.md | 1 + 2 files changed, 48 insertions(+) create mode 100644 "Interview/sad/\350\260\210\350\260\210CountDownLatch.md" diff --git "a/Interview/sad/\350\260\210\350\260\210CountDownLatch.md" "b/Interview/sad/\350\260\210\350\260\210CountDownLatch.md" new file mode 100644 index 00000000..d1e758aa --- /dev/null +++ "b/Interview/sad/\350\260\210\350\260\210CountDownLatch.md" @@ -0,0 +1,47 @@ +> 我们经常在主线程中开启多个线程去并行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。知道你们要说join,但是join不够灵活。 + +面试官:讲讲CountDownLatch原理 + +我:我试试 + +首先状态变量state:**state用来表示计数器当前的值**,当线程调用CountDownLatch对象的await方法后, 当前线程会被阻塞,直到下面的情况之一发生才返回:当所有线程都调用了CountDownLatch对象的countDown方法后,也就是**计数器的值为0**时:其他线程调用了**当前线程的interrupt()方法中断了当前线程**,当前线程就会抛出InterruptedException异常。 + +所以,我们看一下await方法: + +```java +public final void acquireSharedInterruptibly(int arg) + throws InterruptedException { + if (Thread.interrupted()) // 线程可中断 + throw new InterruptedException(); + if (tryAcquireShared(arg) < 0) // 如果等于-1, 说明还在挂起 + doAcquireSharedInterruptibly(arg); +} +protected int tryAcquireShared(int acquires) { + return (getState() == 0) ? 1 : -1; // 如果为0,则返回1,不为0,则返回-1 +} +``` + +看一下countDown方法: + +```java +public final boolean releaseShared(int arg) { + if (tryReleaseShared(arg)) { + doReleaseShared(); // 激活被阻塞的线程 + return true; + } + return false; +} +protected boolean tryReleaseShared(int releases) { // 尝试释放锁 + // Decrement count; signal when transition to zero + for (;;) { + int c = getState(); + if (c == 0) + return false; // 如果等于0, 返回false,不用释放 + int nextc = c-1; + if (compareAndSetState(c, nextc)) // 更新state + return nextc == 0; // nextc如果等于0了,说明资源释放成功,但是不管成功与否,都会退出循环 + // 并且去激活被await阻塞的线程 + } +} +``` + diff --git a/README.md b/README.md index 4439e71b..675661fb 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ - [谈谈AQS](/Interview/sad/谈谈AQS.md) - [谈谈ReentrantLock](/Interview/sad/谈谈ReentrantLock.md) - [谈谈ReentrantReadWriteLock](/Interview/sad/谈谈ReentrantReadWriteLock.md) +- [谈谈CountDownLatch](/Interview/sad/谈谈CountDownLatch.md) - [谈谈死锁](/Interview/sad/谈谈死锁.md) - [生产者消费者模型](/Interview/sad/生产者消费者模型.md) - [类文件结构](/Interview/sad/类文件结构.md) From eaa24ab4d10c01582ec264fe1059aab41e9c4861 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 27 Aug 2020 20:58:24 +0800 Subject: [PATCH 031/366] =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E5=B8=AE=E9=9D=A2?= =?UTF-8?q?=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/zuoyebang/my.md | 24 ++++++++++++++++++++++++ README.md | 1 + 2 files changed, 25 insertions(+) create mode 100644 Interview/mianjing/zuoyebang/my.md diff --git a/Interview/mianjing/zuoyebang/my.md b/Interview/mianjing/zuoyebang/my.md new file mode 100644 index 00000000..89b4cee5 --- /dev/null +++ b/Interview/mianjing/zuoyebang/my.md @@ -0,0 +1,24 @@ +一面 +时间:8.27 19:00 时长:45min + +1. 自我介绍 +2. 谈一谈Java的面向对象的特性 +3. 谈一谈多态 +4. 讲一讲并发的同步方法 +5. 讲一下synchroniezd和lock的区别 +6. 什么是乐观锁和悲观锁 +7. juc都有哪些? +8. AQS的底层原理 +9. MySQL的索引是什么? +10. 如果优化慢查询 +11. 如何优化数据库 +12. jdbc的线程池 +13. MySQL,Mongodb和ES各自的场景 +14. 讲一下缓存穿透,缓存雪崩以及方案 +15. 谈一谈SpringIOC和AOP +16. 控制反转前后的优缺点 +17. 谈一谈微服务 +18. 谈一谈中间件 +19. MQ如何的特性 +20. 什么是幂等性,如何解决 +21. 反问 \ No newline at end of file diff --git a/README.md b/README.md index 675661fb..cb68b3e5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ - [shein面经](/Interview/mianjing/shein/my.md) - [贝壳面经](/Interview/mianjing/beike/my.md) - [腾讯面经](/Interview/mianjing/tx/my.md) +- [作业帮面经](/Interview/mianjing/zuoyebang/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From 0841c4fa4bc9019b84d0c31b3e82e4239c5ab2ed Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 28 Aug 2020 18:50:34 +0800 Subject: [PATCH 032/366] =?UTF-8?q?=E7=8C=BF=E8=BE=85=E5=AF=BC=E9=9D=A2?= =?UTF-8?q?=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/yuanfudao/my.md | 16 +++++++++++++++ Interview/src/Main.java | 31 ------------------------------ Interview/src/Solution.java | 2 +- README.md | 1 + 4 files changed, 18 insertions(+), 32 deletions(-) create mode 100644 Interview/mianjing/yuanfudao/my.md diff --git a/Interview/mianjing/yuanfudao/my.md b/Interview/mianjing/yuanfudao/my.md new file mode 100644 index 00000000..8f69f0dd --- /dev/null +++ b/Interview/mianjing/yuanfudao/my.md @@ -0,0 +1,16 @@ +一面: +时间 8.28:15:00 时长50min + +1. 自我介绍 +2. 介绍一下项目 +3. MySQL的索引 +4. 慢查询优化 +5. Redis的缓存穿透和雪崩 +6. Dubbo的负载均衡策略 +7. zk和redis实现分布式锁的原理 +8. MQ的特性 +9. RocketMQ事务消息的模型 +10. 如何维持幂等性 +11. final的关键字的作用 +12. final关键字修饰引用类型,那么在GC有什么特点 +13. 写题:非对称的对称二叉树 diff --git a/Interview/src/Main.java b/Interview/src/Main.java index e77d1c15..63086d2b 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,36 +1,5 @@ -import java.util.ArrayList; -import java.util.List; - public class Main { - public static void main(String[] args) { - int[] nums = {3, 2, 1, 6, 5, 4}; - ListNode node = ListNodeTools.getListNode(nums); - ListNodeTools.printListNode(node); - reorderList(node); - ListNodeTools.printListNode(node); - } - public static void reorderList(ListNode head) { - List list = new ArrayList<>(); - ListNode p = head; - while (p != null) { - list.add(p.val); - p = p.next; - } - int l = 0, r = list.size() - 1; - int idx = 0; - int[] nums = new int[list.size()]; - while (l < r) { - nums[idx++] = list.get(l++); - nums[idx++] = list.get(r--); - } - p = head; - idx = 0; - while (p != null) { - p.val = nums[idx++]; - p = p.next; - } - } } class TreeNode { diff --git a/Interview/src/Solution.java b/Interview/src/Solution.java index 1645e027..852d7050 100644 --- a/Interview/src/Solution.java +++ b/Interview/src/Solution.java @@ -1,3 +1,3 @@ public class Solution { - + } diff --git a/README.md b/README.md index cb68b3e5..003afc55 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ - [贝壳面经](/Interview/mianjing/beike/my.md) - [腾讯面经](/Interview/mianjing/tx/my.md) - [作业帮面经](/Interview/mianjing/zuoyebang/my.md) +- [猿辅导面经](/Interview/mianjing/yuanfudao/my.md) ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) From 7f1e8297cb2062f79df60b7094502e0d19fc30ad Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 28 Aug 2020 18:59:10 +0800 Subject: [PATCH 033/366] =?UTF-8?q?=E7=8C=BF=E8=BE=85=E5=AF=BC=E9=9D=A2?= =?UTF-8?q?=E7=BB=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/yuanfudao/my.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Interview/mianjing/yuanfudao/my.md b/Interview/mianjing/yuanfudao/my.md index 8f69f0dd..6d4b2eb5 100644 --- a/Interview/mianjing/yuanfudao/my.md +++ b/Interview/mianjing/yuanfudao/my.md @@ -7,10 +7,11 @@ 4. 慢查询优化 5. Redis的缓存穿透和雪崩 6. Dubbo的负载均衡策略 -7. zk和redis实现分布式锁的原理 -8. MQ的特性 -9. RocketMQ事务消息的模型 -10. 如何维持幂等性 -11. final的关键字的作用 -12. final关键字修饰引用类型,那么在GC有什么特点 -13. 写题:非对称的对称二叉树 +7. 分布式一致性哈希的原理 +8. zk和redis实现分布式锁的原理 +9. MQ的特性 +10. RocketMQ事务消息的模型 +11. 如何维持幂等性 +12. final的关键字的作用 +13. final关键字修饰引用类型,那么在GC有什么特点 +14. 写题:非对称的对称二叉树 From 294923b97a1b2b7efdb4a22e1a7d1b8809355650 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 29 Aug 2020 01:00:20 +0800 Subject: [PATCH 034/366] =?UTF-8?q?=E7=B1=BB=E5=8A=A0=E8=BD=BD=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...73\345\212\240\350\275\275\345\231\250.md" | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git "a/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" index 2b18ec46..42aceebc 100644 --- "a/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" +++ "b/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" @@ -28,3 +28,49 @@ 4. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 5. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。 +面试官:如何打破双亲委派模型 + +我:需要重写ClassLoader类的loadClass()方法: + +```java +// 其实重写该方法就行,但是打破可能会报错,系统找不到路径 +// 父类的加载(Object)也会交由我们自自定义的类加载器加载。而很明显在我们自定义的加载目录下是不会有Object.class这个文件的。 +protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (getClassLoadingLock(name)) { + // First, check if the class has already been loaded + Class c = findLoadedClass(name); + if (c == null) { + long t0 = System.nanoTime(); + try { + if (parent != null) { + c = parent.loadClass(name, false); // 调用父类的加载器 递归 + } else { + c = findBootstrapClassOrNull(name); + } + } catch (ClassNotFoundException e) { + // ClassNotFoundException thrown if class not found + // from the non-null parent class loader + } + + if (c == null) { + // If still not found, then invoke findClass in order + // to find the class. + 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; + } + } +``` + From 1bb4da9d843cc3e2bbf0272ddf666647b9993064 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 29 Aug 2020 20:40:57 +0800 Subject: [PATCH 035/366] =?UTF-8?q?JVM=E5=86=85=E5=AD=98=E5=8C=BA=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...05\345\255\230\345\214\272\345\237\237.md" | 59 +++++++++++++++++++ Interview/src/Main.java | 40 ++++++++++++- README.md | 1 + 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 "Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" diff --git "a/Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" new file mode 100644 index 00000000..4ca3f599 --- /dev/null +++ "b/Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" @@ -0,0 +1,59 @@ +> JVM这一块,经常还是经常被问到的 + +面试官:讲一讲JVM内存区域 + +我:行,先放两张图 + +![1.8之前](http://media.dreamcat.ink/uPic/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B-1.8%E4%B9%8B%E5%89%8D.png) + +![1.8](http://media.dreamcat.ink/uPic/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B-1.8.png) + +总体来说,粗略的分为**堆和栈**,那么**栈是线程私有的**,而**堆是线程共享的**。那么**栈**又问分为**程序计数器**,**虚拟机栈**,**本地方法栈**。堆稍后再说,当然还有**方法区**,稍后单独说。 + +1. **程序计数器** + +- **字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制**,如:顺序执行、选择、循环、异常处理。 +- 在多线程的情况下,**程序计数器用于记录当前线程执行的位置**,**从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了**。 +- **程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域**,它的生命周期随着线程的创建而创建,随着线程的结束而死亡 + +2. **虚拟机栈** + +- 说白了,通俗的讲,主要是**对象中的方法产生的各种"材料"**。 +- 因此,虚拟机栈存放的是**局部变量表**、**操作数栈**、**动态链接**、**方法出口**。 +- 局部变量表存8**大基本数据类型以及引用类型**。 +- 当然,栈也会非常error: + - **StackOverFlowError**: 若 J**ava 虚拟机栈的内存大小不允许动态扩展**,**那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候**,就抛出 StackOverFlowError 异常。 + - **OutOfMemoryError**:若 **Java 虚拟机栈的内存大小允许动态扩展**,**且当线程请求栈时内存用完了**,**无法再动态扩展了**,此时抛出 OutOfMemoryError 异常。 + +3. **本地方法栈**:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而**本地方法栈则为虚拟机使用到的 Native 方法服务**,JDK源码中很多本地方法哦。 + +4. 这里单独说一下**方法区**: + +**方法区与 Java 堆一样,是各个线程共享的内存区域**,它用于存储已被虚拟机加载的**类信息**、**常量**、**静态变量**、即时编译器编译后的代码等数据。不过随着版本的变化,会发生变化。 + +- 1.6:运行时常量池在Perm Gen区(也就是方法区)中;字符串常量池在**运行时常量池**中。 +- 1.7:运行时常量池依然在Perm Gen区(也就是方法区)中在JDK7版本中,永久代的转移工作就已经开始了,将譬如**符号引用转移到了native heap**;**字面量转移到了java heap**;**类的静态变量转移到了java heap**。但是运行时常量池依然还存在,只是很多内容被转移,其只存着这些被转移的引用;字符串常量池被分配到了**Java堆的主要部分**。也就是字符串常量池从运行时常量池分离出来了。 +- 1.8:JVM已经将**运行时常量池从方法区中移了出来**,在**Java 堆(Heap)中开辟了一块区域存放运行时常量池**。同时永久代被移除,**以元空间代替**。**元空间并不在虚拟机中,而是使用本地内存**;**字符串常量池存在于Java堆中**。 + +方法区,依然会发生error,因为在之前的版本中,**当一个类启动的时候,也会加载很多class文件**,那么也会充满整个方法区,当满的时候,也会error的,当然,在以前的版本中,字符串常量池在方法区中,而**使用String.intern()方法,依然会占满空间并error**。 + +5. **直接内存**并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现(如**DirectByteBuffer**)。本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 + +6. **堆**:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 + +- 分为四区,分别为eden区、s0("From)区、s1("To")和tentired +- 在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。 +- 当Eden区满了的时候,minor GC触发 +- 经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收 +- 在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的相同存活的对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象(重点) +- 再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。 + +**Minor GC和Full GC触发条件**: + +- Minor GC触发条件:当Eden区满时,触发Minor GC。 +- Full GC触发条件: + 1. 调用System.gc时,系统建议执行Full GC,但是不必然执行 + 2. 老年代空间不足 + 3. **方法区**空间不足 + 4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存 + 5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 63086d2b..f073fb9a 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,5 +1,43 @@ -public class Main { +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +public class Main { + private static int values = 0; + public static void main(String[] args) { + ReentrantLock lock = new ReentrantLock(); + Condition conA = lock.newCondition(); + Condition conB = lock.newCondition(); + new Thread(() -> { + try { + lock.lock(); + while (values <= 10) { + System.out.println(Thread.currentThread().getName() + " " + values++); + conB.signal(); + conA.await(); + } + conB.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "偶数").start(); + new Thread(() -> { + try { + lock.lock(); + while (values <= 10) { + System.out.println(Thread.currentThread().getName() + " " + values++); + conA.signal(); + conB.await(); + } + conA.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "奇数").start(); + } } class TreeNode { diff --git a/README.md b/README.md index 003afc55..dc918e7a 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ - [类文件结构](/Interview/sad/类文件结构.md) - [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) +- [JVM内存区域](/Interview/sad/JVM内存区域.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From cafd648a50f3dbbb2b136df755fa867bb9392e8e Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 29 Aug 2020 20:44:29 +0800 Subject: [PATCH 036/366] =?UTF-8?q?=E5=A4=9A=E7=BA=BF=E7=A8=8B=E7=BC=96?= =?UTF-8?q?=E7=A8=8B=E9=A2=98.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...13\347\274\226\347\250\213\351\242\230.md" | 416 ++++++++++++++++++ Interview/src/Test/Main.java | 41 -- Interview/src/Test/Test.java | 12 - README.md | 3 + 4 files changed, 419 insertions(+), 53 deletions(-) create mode 100644 "Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" delete mode 100644 Interview/src/Test/Main.java delete mode 100644 Interview/src/Test/Test.java diff --git "a/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" "b/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" new file mode 100644 index 00000000..8f8c454b --- /dev/null +++ "b/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" @@ -0,0 +1,416 @@ +#### 一个多线程的问题,用三个线程,顺序打印字母A-Z,输出结果是1A 2B 3C 1D 2E…打印完毕最后输出一个Ok。 + +```java +public class Test { + private static char c = 'A'; + private static int i = 0; + + public static void main(String[] args) { + Runnable r = new Runnable() { + @Override + public void run() { + synchronized (this) { + try { + int id = Integer.parseInt(Thread.currentThread().getName()); + while (i < 26) { + if (i % 3 == id - 1) { + System.out.println("线程id:" + id + " " + (char) c++); + i++; + notifyAll(); + } else { + wait(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + new Thread(r, "1").start(); + new Thread(r, "2").start(); + new Thread(r, "3").start(); + } +} + +``` + +#### 每个线程把自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示 + +LockSupport + +```java +public class Main { + static Thread threadA, threadB, threadC; + public static void main(String[] args) { + threadA = new Thread(() -> { + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + LockSupport.unpark(threadB); + LockSupport.park(); + } + }, "A"); + + threadB = new Thread(() -> { + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + LockSupport.unpark(threadC); + LockSupport.park(); + } + }, "B"); + + threadC = new Thread(() -> { + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + LockSupport.unpark(threadA); + LockSupport.park(); + } + }, "C"); + + threadA.start(); + threadB.start(); + threadC.start(); + } +} +``` + +ReentrantLock + +```java +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class Main { + public static void main(String[] args) { + ReentrantLock lock = new ReentrantLock(); + Condition conA = lock.newCondition(); + Condition conB = lock.newCondition(); + Condition conC = lock.newCondition(); + + // A + new Thread(() -> { + try { + lock.lock(); + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + conB.signal(); + conA.await(); + } + conB.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "A").start(); + + // B + new Thread(() -> { + try { + lock.lock(); + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + conC.signal(); + conB.await(); + } + conC.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "B").start(); + + // C + new Thread(() -> { + try { + lock.lock(); + for (int i = 0; i < 10; i++) { + System.out.print(Thread.currentThread().getName()); + conA.signal(); + conC.await(); + } + conA.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "C").start(); + } +} +``` + +#### 两线程奇偶数打印 + +```java +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class Main { + static int value = 0; + public static void main(String[] args) { + ReentrantLock lock = new ReentrantLock(); + Condition conA = lock.newCondition(); + Condition conB = lock.newCondition(); + // A + new Thread(() -> { + try { + lock.lock(); + while (value <= 100) { + System.out.println(Thread.currentThread().getName() + " " + value++); + conB.signal(); + conA.await(); + } + conB.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "A").start(); + + // B + new Thread(() -> { + try { + lock.lock(); + while (value <= 100) { + System.out.println(Thread.currentThread().getName() + " " + value++); + conA.signal(); + conB.await(); + } + conA.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "B").start(); + + } +} +``` + +#### 启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10。直到75 + +```java +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class Main { + static int value = 1; + public static void main(String[] args) { + ReentrantLock lock = new ReentrantLock(); + Condition conA = lock.newCondition(); + Condition conB = lock.newCondition(); + // A + new Thread(() -> { + try { + lock.lock(); + while (value <= 75) { + System.out.print(Thread.currentThread().getName() + " "); + for (int i = 0; i < 5; i++) { + System.out.print(value++ + " "); + } + System.out.println(""); + conB.signal(); + conA.await(); + } + conB.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "A").start(); + + // B + new Thread(() -> { + try { + lock.lock(); + while (value <= 75) { + System.out.print(Thread.currentThread().getName() + " "); + for (int i = 0; i < 5; i++) { + System.out.print(value++ + " "); + } + System.out.println(""); + conA.signal(); + conB.await(); + } + conA.signal(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + }, "B").start(); + + } +} +``` + +#### 如何确保三个线程顺序执行? + +join + +```java +public class ThreadTest1 { +// T1、T2、T3三个线程顺序执行 +public static void main(String[] args) { +    Thread t1 = new Thread(new Work(null)); +    Thread t2 = new Thread(new Work(t1)); +    Thread t3 = new Thread(new Work(t2)); +    t1.start(); +    t2.start(); +    t3.start(); + +} +static class Work implements Runnable { +    private Thread beforeThread; +    public Work(Thread beforeThread) { +        this.beforeThread = beforeThread; +    } +    public void run() { +        if (beforeThread != null) { +            try { +                beforeThread.join(); +                System.out.println("thread start:" + Thread.currentThread().getName()); +            } catch (InterruptedException e) { +                e.printStackTrace(); +            } +        } else { +            System.out.println("thread start:" + Thread.currentThread().getName()); +        } +    } + } +} +``` + +CountDownLatch + +```java +public class ThreadTest2 { + +// T1、T2、T3三个线程顺序执行 +public static void main(String[] args) { +    CountDownLatch c0 = new CountDownLatch(0); //计数器为0 +    CountDownLatch c1 = new CountDownLatch(1); //计数器为1 +    CountDownLatch c2 = new CountDownLatch(1); //计数器为1 + +    Thread t1 = new Thread(new Work(c0, c1)); +    //c0为0,t1可以执行。t1的计数器减1 + +    Thread t2 = new Thread(new Work(c1, c2)); +    //t1的计数器为0时,t2才能执行。t2的计数器c2减1 + +    Thread t3 = new Thread(new Work(c2, c2)); +    //t2的计数器c2为0时,t3才能执行 + +    t1.start(); +    t2.start(); +    t3.start(); + +} + +//定义Work线程类,需要传入开始和结束的CountDownLatch参数 +static class Work implements Runnable { +    CountDownLatch c1; +    CountDownLatch c2; + +    Work(CountDownLatch c1, CountDownLatch c2) { +        super(); +        this.c1 = c1; +        this.c2 = c2; +    } + +    public void run() { +        try { +            c1.await();//前一线程为0才可以执行 +            System.out.println("thread start:" + Thread.currentThread().getName()); +            c2.countDown();//本线程计数器减少 +        } catch (InterruptedException e) { +        } + +    } + } +} +``` + +blockingQueue + +```java +public class ThreadTest4 { +// T1、T2、T3三个线程顺序执行 +public static void main(String[] args) { +    //blockingQueue保证顺序 +    BlockingQueue blockingQueue = new LinkedBlockingQueue(); +    Thread t1 = new Thread(new Work()); +    Thread t2 = new Thread(new Work()); +    Thread t3 = new Thread(new Work()); + +    blockingQueue.add(t1); +    blockingQueue.add(t2); +    blockingQueue.add(t3); + +    for (int i=0;i<3;i++) { +        Thread t = null; +        try { +            t = blockingQueue.take(); +        } catch (InterruptedException e) { +            e.printStackTrace(); +        } +        t.start(); +        //检测线程是否还活着 +        while (t.isAlive()); +    } +} + +static class Work implements Runnable { + +    public void run() { +        System.out.println("thread start:" + Thread.currentThread().getName()); +    } + } +} +``` + +CachedThreadPool + +```java + public class ThreadTest3 { +    // T1、T2、T3三个线程顺序执行 +   public static void main(String[] args) { +    FutureTask future1= new FutureTask(new Work(null)); +    Thread t1 = new Thread(future1); + +    FutureTask future2= new FutureTask(new Work(future1)); +    Thread t2 = new Thread(future2); + +    FutureTask future3= new FutureTask(new Work(future2)); +    Thread t3 = new Thread(future3); + +    t1.start(); +    t2.start(); +    t3.start(); +} + + static class Work  implements Callable { +    private FutureTask beforeFutureTask; +    public Work(FutureTask beforeFutureTask) { +        this.beforeFutureTask = beforeFutureTask; +    } +    public Integer call() throws Exception { +        if (beforeFutureTask != null) { +            Integer result = beforeFutureTask.get();//阻塞等待 +            System.out.println("thread start:" + Thread.currentThread().getName()); +        } else { +            System.out.println("thread start:" + Thread.currentThread().getName()); +        } +        return 0; +    } + } +} +``` + +[https://blog.csdn.net/Evankaka/article/details/80800081](https://blog.csdn.net/Evankaka/article/details/80800081) + diff --git a/Interview/src/Test/Main.java b/Interview/src/Test/Main.java deleted file mode 100644 index ad0da18a..00000000 --- a/Interview/src/Test/Main.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @program JavaBooks - * @description: 牛客 - * @author: mf - * @create: 2020/07/24 22:24 - */ - -package Test; - - -public class Main { - public static void main(String[] args) { -// Scanner sc = new Scanner(System.in); - - } - - -} - - -/** - * nk没有Pair的包 - * 自己提前写一个,方便使用 - */ -class Pair { - private K key; - private V value; - - public Pair(K key, V value) { - this.key = key; - this.value = value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } -} diff --git a/Interview/src/Test/Test.java b/Interview/src/Test/Test.java deleted file mode 100644 index 9dfb2bc1..00000000 --- a/Interview/src/Test/Test.java +++ /dev/null @@ -1,12 +0,0 @@ -package Test; - -import java.util.ArrayList; - -public class Test { - public static void main(String[] args) { - ArrayList list = new ArrayList<>(); - list.add(2); - int[] array = list.stream().mapToInt(Integer::valueOf).toArray(); - - } -} \ No newline at end of file diff --git a/README.md b/README.md index dc918e7a..25a4a50f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,9 @@ - [推荐:一写算法套路模版](https://github.com/labuladong/fucking-algorithm) > 有些套路挺实用的,推荐阅读... +- [手写多线程编程面试题](/Interview/myself/多线程编程题.md) +> 还在总结,但是都一样的套路... + ## 笔试题汇总(持续...) - [LC-SQL](/Interview/mianjing/sql/sql.md) - [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) From 1d2bff49237ed19cd2334997fb781a1db0502cd3 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 29 Aug 2020 21:30:04 +0800 Subject: [PATCH 037/366] =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...33\345\273\272\350\277\207\347\250\213.md" | 73 +++++++++++++++++++ README.md | 1 + 2 files changed, 74 insertions(+) create mode 100644 "Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" diff --git "a/Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" "b/Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" new file mode 100644 index 00000000..cc586a27 --- /dev/null +++ "b/Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" @@ -0,0 +1,73 @@ +面试官:类加载过程,你之前给我讲过,那么创建对象的过程你知道吗? + +我:我似乎知道。 + +上图![对象创建的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) + +1. **类加载检查** + +虚拟机遇到一条 **new** 指令时,首先将去检查这个指令的参数**是否能在常量池中定位到这个类的符号引用**,并且检查这个符号引用代表的**类是否已被加载过**、**解析和初始化过**。如果没有,那必须先执行相应的类加载过程。 + +2. **分配内存** + +在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。 + +- **指针碰撞** + - **堆规整(没有内存碎片)** + - 复制算法 + - GC:Serial、ParNew +- **空闲列表** + - **堆内存不规整的情况下** + - 虚拟机会维护一个**列表**,该列表会**记录哪些内存块是可用的**,在分配的时候,找一块儿足够大的内存块来划分给对象实例,最后更新列表激励 + - GC:CMS + +- **并发问题** + - **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** + - **TLAB:** 为**每一个线程预先在 Eden 区分配一块儿内存**,JVM 在给线程中的对象分配内存时,**首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配** + +3. **初始化零值** + +内存分配完成后,虚拟机需要将分配到的内存空间都**初始化为零值(不包括对象头)**,这一步操作保证了对象的实例字段在 Java 代码中**可以不赋初始值就直接使用**,程序能访问到这些字段的数据类型所对应的零值。 + +4. **设置对象头** + +初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 + +5. **指向init方法** + +在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,**方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行**方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 + +面试官:刚才说了内存布局,给我讲一下 + +我:好的,内存布局分别为**对象头,实例数据,对其填充** + +1. **对象头** + +**Hotspot 虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈**希码、GC 分代年龄、锁状态标志**等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 + +2. **实例数据** + +**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 + +3. **对齐填充** + +**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 + +面试官:给我讲讲对象的访问方式 + +我:明白,两种:使用句柄和使用直接指针 + +1. **句柄**: + +![使用句柄](http://media.dreamcat.ink/uPic/使用句柄.png) + +如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了**对象实例数据**与**类型数据**各自的具体地址信息; + +2. **直接指针**: + +![直接指针](http://media.dreamcat.ink/uPic/直接指针.png) + +如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。 + +话说:这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是**稳定**的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 **reference 本身不需要修改**。使用直接指针访问方式最大的好处就是**速度快**,它节省了一次指针定位的时间开销。 + diff --git a/README.md b/README.md index 25a4a50f..b1322551 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ - [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) - [JVM内存区域](/Interview/sad/JVM内存区域.md) +- [对象的创建过程](/Interview/sad/对象的创建过程.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From bea341060f38d2e15de94b6b99a45ee9d58160e3 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sat, 29 Aug 2020 23:43:25 +0800 Subject: [PATCH 038/366] =?UTF-8?q?=E5=9E=83=E5=9C=BE=E5=9B=9E=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...03\345\234\276\345\233\236\346\224\266.md" | 126 ++++++++++++++++++ README.md | 1 + 2 files changed, 127 insertions(+) create mode 100644 "Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" diff --git "a/Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" new file mode 100644 index 00000000..33132ff8 --- /dev/null +++ "b/Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" @@ -0,0 +1,126 @@ +> 这里可以主要讲CMS和G1,简单谈一些其他的 + +面试官:知道JVM垃圾回收机制吗? + +我:知道,首先说一下垃圾回收算法,因为其中的一个分代收集,所以让堆又分为年轻代和老年代。 + +垃圾回收算法: + +1. **标记-清除** + +该算法分为**“标记”和“清除”**阶段:首先**标记出所有需要回收的对象**,在标记完成后**统一回收所有被标记的对象**。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: + +- **效率问题** +- **空间问题(标记清除后会产生大量不连续的碎片)** + +2. **标记-整理** + +根据**老年代的特点**提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是**让所有存活的对象向一端移动**,然后直接清理掉端边界以外的内存。(老年代一般存入的是大对象,时间比较久的对象) + +3. **复制** + +为了**解决效率**问题,“复制”收集算法出现了。它可以将**内存分为大小相同的两块**,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。**这样就使每次的内存回收都是对内存区间的一半进行回收**。(堆的年轻代又分为Eden、s0和s1) + +4. **分代** + +**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** + +一般情况: + +- 大多数情况下,**对象在新生代中 eden 区分配**。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC. +- **大对象直接进入老年代**,大对象就是需要**大量连续内存空间的对象**(比如:字符串、数组)。频繁复制降低性能。 +- 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1. 对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被**晋升到老年代**中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 + +面试官:如何判断对象死亡? + +我:有两种策略,其一为**引用计数法**,其二为**可达性分析**。 + +1. **引用计数法** + +给对象中添加一个引用计数器,每当有一个地方**引用它**,**计数器就加 1**;**当引用失效**,**计数器就减 1**;任何时候**计数器为 0 的对象就是不可能再被使用的**。 + +**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** + +2. **可达性分析** + +这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,**从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的**。 + +哪些可以作为GC Roots的根: + +- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中应用的对象。 +- 本地方法栈中JNI(native方法)引用的对象 +- 方法区中的类静态属性引用的对象 +- 方法区中常量引用的对象 + +面试官:如何枚举根节点? + +我:以上引用作用GC Roots的根,如果**方法区和大,要逐个检查这里面的引用,那么必然会消耗很多时间**,而且枚举根节点需要停顿的。在HotSpot的实现中,是使用一组称为**OopMap的数据结构**来达到这个目的的,在类加载完成的时候,**HotSpot就把对象内什么偏移量是什么类型的数据计算出来**,在JIT编译过程中,**也会在特定的位置记录下栈和寄存器中哪些位置是引用**。这样,GC在扫描时就可以直接得这心信息了。 + +但一个很现实的问题:**可能导致引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那么会需要大量的额外空间,这样GC成本很高,安全点由此而来**。 + +实际上,在JIT编译过程中,在**特定的位置记录下栈和寄存器哪些位置是引用**,实际上这些位置就是**安全点**,意思就是说,**程序执行时并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停**。 + +Safepoint机制保证了程序执行时,在**不太长的时间内就会遇到可进入GC的Safepoint**,但**如果线程处于Sleep或者Blocked状态**,这时候线程**无法响应JVM的中断请求**,JVM也显然不太可能等待线程重新被分配CPU时间,这种情况就需要**安全域**来解决。**安全域是指在一段代码片段中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。这时候安全点就被扩展到了Safe Region**。 + +面试官:给我讲讲垃圾收集器吧 + +我:当然没问题,有一张有趣的图 + +![垃圾收集器](http://media.dreamcat.ink/uPic/iShot2020-08-29下午11.17.40.png) + +小插曲:咱们知道,堆分为新生代和老年代,那么从这张图可以看出,新生代有Serial、ParNew和Parallel Scavenge而老年代自然也有Serial Old和Parallel Old,新生代和老年代都有串并行收集器,能互相搭配,但看CMS就很特殊,它是老年代的收集器,能从图中可以看出来,它不稳定呀,居然用Serial Old当备胎,而且为了能搭配CMS的并行垃圾收集器,就给它造了一个ParNew,哈哈哈(开个玩笑)。G1暂且不说,横跨新生和老年。在它这一块不分家,一把抓。 + +我就简单说一下串并行垃圾收集器,太古老了,面试官也不想听。 + +你像Serial和ParNew呀,其实在STW的时候,一个是**单线程**,一个是**多线程**回收垃圾。而ParNew和Parallel Scavenge的区别仅仅是**吞吐量**,后者重在吞吐量上(高效率利用CPU)。所以,**Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。而ParNew是在Server 模式下的虚拟机的首要选择之一。以上垃圾收集器新生代采用复制,而老年代采用标记-整理。** + +**CMS垃圾收集器**: + +**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。** + +**CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。** + +从名字中的**Mark Sweep**这两个词可以看出,CMS 收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤: + +- 初始标记:暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; +- 并发标记:同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 +- 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 +- 并发清除:开启用户线程,同时 GC 线程开始对为标记的区域做清扫。 + + +从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点: + +- **对 CPU 资源敏感;** +- **无法处理浮动垃圾;** +- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。** + +因此,为了解决以上缺点,**G1**就出现了: + +- **将整个Java堆划分为多个大小相等的独立区域(Region)**,虽然还保留新生代和老年代的概念,但**新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合** + +- 并行与并发: G1 能充分利用 **CPU、多核**环境下的硬件优势,使用多个 CPU来缩短 Stop-The-World 停顿时间。 +- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。 +- 空间整合:G1 从整体来看是基于**“标记整理”**算法实现的收集器;从局部上来看是基于**“复制”算法**实现的。 +- 可预测停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型。G1跟踪**各个Region里面的垃圾堆积的价值大小**(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个**优先列表**,每次根据允许的收集时间,**优先回收价值最大的Region**。 + +**G1的跨代引用**: + +在G1收集器中,**Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用**,虚拟机都是使用**Remembered Set(RS)**来避免全堆扫描的。**G1中每个Region都有一个与之对应的RS**,虚拟机发现程序**对Reference类型的数据进行写操作**时,会产生**一个Write Barrier暂时中断操作**,**检查Reference引用的对象是否处于不同的Region之间**(在分代的例子中就是检查是否老年代中的对象引用了新生代中方的对象)如果是,便**通过CardTable(每个Region块又细分了2000多个卡表,记录一波我引用了哪个对象)把相关引用信息记录到被引用对象所属的Region的RS之中**。当进行内存回收时,**在GC根节点的枚举范围中加入RS即可保证不对全堆扫描,也不会又遗漏**。 + +当然G1有也大致的四个过程: + +- 初始标记:仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改**TAMS(Nest Top Mark Start)**的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要**停顿线程**,但耗时很短。 +- 并发标记:从GC Root 开始对堆中对象进行**可达性分析**,找到存活对象,此阶段耗时较长,但**可与用户程序并发执行**。 +- 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在**线程的Remembered Set Logs**里面,最终标记阶段需要**把Remembered Set Logs的数据合并到Remembered Set中**,这阶段需要**停顿线程**,但是**可并行执行**。 +- 筛选回收:首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 + +在这里,简单做一个CMS和G1的比较: + +1. CMS收集器是**获取最短回收停顿时间**为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的(只有初始标记和重新标记会STW)。但**是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降**。 +2. CMS仅作用于老年代,是基于**标记清除算法**,所以清理的过程中**会有大量的空间碎片**。 +3. CMS收集器**无法处理浮动垃圾**,**由于CMS并发清理阶段用户线程还在运行**,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留在下一次GC时将其清理掉。 +4. G1是一款面向服务端应用的垃圾收集器,**适用于多核处理器、大内存容量的服务端系统**。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU或核心来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。 +5. **从JDK 9开始,G1成为默认的垃圾回收器**。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。 +6. G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。 +7. G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 **G1 中维护记忆集的成本较高**,带来了更高的执行负载,影响效率。**所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB**。 + diff --git a/README.md b/README.md index b1322551..cc249192 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ - [类加载器](/Interview/sad/类加载器.md) - [JVM内存区域](/Interview/sad/JVM内存区域.md) - [对象的创建过程](/Interview/sad/对象的创建过程.md) +- [垃圾回收](/Interview/sad/垃圾回收.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 1367812d465d079dea7f9e3f40f8787c983be5b0 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Sun, 30 Aug 2020 14:27:07 +0800 Subject: [PATCH 039/366] =?UTF-8?q?=E9=80=83=E9=80=B8=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\203\351\200\270\345\210\206\346\236\220.md" | 17 +++++++++++++++++ README.md | 1 + 2 files changed, 18 insertions(+) create mode 100644 "Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" diff --git "a/Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" "b/Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" new file mode 100644 index 00000000..a8ddcd24 --- /dev/null +++ "b/Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" @@ -0,0 +1,17 @@ +> 这一块知识还是要知道的呀,它是Java虚拟机中比较前沿优化的技术。 + +面试官:你了解逃逸分析吗? + +我:算是了解。逃逸分析的基本行为就是分析**对象动态作用域**:当一个对**象在方法中被定义后,它可能被外部方法引用,例如作为调用参数传递到其他方法中,称为方法逃逸**。甚至还有可能**被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸**。如果能证明**一个对象不会逃逸到方法或线程之外**,也就是**别的方法或线程无法通过任何途径访问到这个对象**,则可能为这个变量进行一些高效的优化: + +1. 栈上分配 + +Java虚拟机中,**如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存**将会是一个很不错的主意,**对象所占用的内存空间就可以随栈帧出栈而销毁**。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那**大量的对象就会随着方法的结束而自动销毁了**,垃圾收集系统的压力将会小很多。 + +2. 同步消除 + +**线程同步本身是一个相对耗时的过程**,**如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问**,那这个变量的**读写肯定就不会有竞争**,对这个变量实施的同步措施也就可以消除。 + +3. 标量替换 + +标量是指一**个数据已经无法再分解成更小的数据来表示了**,Java虚拟机的原始数据类型都不能再进一步分解,它们就可以称为标量。如果逃逸分析证明**一个对象不会被外部访问**,并且**这个对象可以被拆散的话**,那程序真正执行的时候将**可能不创建这个对象**,而改**为直接创建它的若干个被这个方法使用的成员变量来代替**。除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配到物理机器高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。 \ No newline at end of file diff --git a/README.md b/README.md index cc249192..832f0474 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ - [JVM内存区域](/Interview/sad/JVM内存区域.md) - [对象的创建过程](/Interview/sad/对象的创建过程.md) - [垃圾回收](/Interview/sad/垃圾回收.md) +- [逃逸分析](/Interview/sad/逃逸分析.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From ce5e403a88a90f330fa922466f783279b250a6c0 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Tue, 1 Sep 2020 22:13:44 +0800 Subject: [PATCH 040/366] =?UTF-8?q?=E4=B8=BA=E4=BB=80=E4=B9=88=E5=A2=9E?= =?UTF-8?q?=E5=BC=BAfor=E5=88=A0=E9=99=A4=E5=85=83=E7=B4=A0=E4=BC=9A?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/src/Main.java | 42 ++--------------------------------------- README.md | 1 + 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/Interview/src/Main.java b/Interview/src/Main.java index f073fb9a..524569b2 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,44 +1,6 @@ -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; -public class Main { - private static int values = 0; - public static void main(String[] args) { - ReentrantLock lock = new ReentrantLock(); - Condition conA = lock.newCondition(); - Condition conB = lock.newCondition(); - new Thread(() -> { - try { - lock.lock(); - while (values <= 10) { - System.out.println(Thread.currentThread().getName() + " " + values++); - conB.signal(); - conA.await(); - } - conB.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "偶数").start(); - new Thread(() -> { - try { - lock.lock(); - while (values <= 10) { - System.out.println(Thread.currentThread().getName() + " " + values++); - conA.signal(); - conB.await(); - } - conA.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "奇数").start(); - } -} + + class TreeNode { int val; diff --git a/README.md b/README.md index 832f0474..3c8cc76b 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ - [谈谈String](/Interview/sad/谈谈String.md) - [谈谈Java值传递](/Interview/sad/谈谈Java值传递.md) - [谈谈集合](/Interview/sad/谈谈集合.md) +- [为什么增强for删除元素会异常](https://juejin.im/post/6844903794795347981) - [线程与进程的区别](/Interview/sad/线程与进程的区别.md) - [线程的创建方式](/Interview/sad/线程的创建方式.md) - [谈谈线程池](/Interview/sad/谈谈线程池.md) From 975d11fc67d1e7afdd1cf357ffaf6a556b9b9cb7 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 2 Sep 2020 09:39:33 +0800 Subject: [PATCH 041/366] =?UTF-8?q?InnoDB=E5=92=8CMyISAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/mianjing/yuanfudao/my.md | 2 +- "Interview/sad/InnoDB\345\222\214MyISAM.md" | 64 +++++++++++++++++++++ Interview/src/Main.java | 5 ++ README.md | 5 +- 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 "Interview/sad/InnoDB\345\222\214MyISAM.md" diff --git a/Interview/mianjing/yuanfudao/my.md b/Interview/mianjing/yuanfudao/my.md index 6d4b2eb5..b25fb388 100644 --- a/Interview/mianjing/yuanfudao/my.md +++ b/Interview/mianjing/yuanfudao/my.md @@ -14,4 +14,4 @@ 11. 如何维持幂等性 12. final的关键字的作用 13. final关键字修饰引用类型,那么在GC有什么特点 -14. 写题:非对称的对称二叉树 +14. 写题:非递归的对称二叉树 diff --git "a/Interview/sad/InnoDB\345\222\214MyISAM.md" "b/Interview/sad/InnoDB\345\222\214MyISAM.md" new file mode 100644 index 00000000..c03b50a3 --- /dev/null +++ "b/Interview/sad/InnoDB\345\222\214MyISAM.md" @@ -0,0 +1,64 @@ +> 这个问题简单回答一下即可 + +面试官:MySQL的引擎都有哪些? + +我:我知道,MySQL内部可以分为服务层和存储引擎层两部分:**服务层包括连接器、查询缓存、分析器、优化器、执行器等;存储引擎层负责数据的存储和提取**。我就说一下自己了解的InnoDB和MyISAM引擎 + +**InnoDB**: + +- 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 +- 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ (Next-Key Locking)**防止幻影读**。 +- 主索引是**聚簇索引**,在**索引中保存了数据**,从而避免直接读取磁盘,因此对查询性能有很大的提升。 +- 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 +- 支持真正的**在线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 + +**MyISAM**: + +- 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 +- 提供了大量的特性,包括**压缩表、空间数据索引**等。 +- **不支持事务**。 +- **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 + +我一般还会回答一个**索引文件**上的区别 + + + +**MyISAM**: +1. MyISAM**索引文件和数据文件是分离**的,**索引文件仅保存数据记录的地址**,同样使用B+Tree作为索引结构,叶节点的**data域存放的是数据记录的地址** +2. 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 +3. MyISAM中索引检索的算法为**首先按照B+Tree搜索算法搜索索引**,**如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录** + +**InnoDB**: +1. **InnoDB的数据文件本身就是索引文件**,这棵树的叶节点**data域保存了完整的数据记录**(聚集索引) +2. InnoDB的**辅助索引data域存储相应记录主键的值而不是地址** +3. **聚集索引这种实现方式使得按主键的搜索十分高效**,**但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录**。 + +其实个人还知道一点,分页查询的时候还有一点区别,这点区别也是根据索引文件的区别来的。 + +咱们知道,使用limit分页查询,offset越大,性能越差,比如: + +```sql +-- 以真实的生产环境的6万条数据的一张表为例,比较一下优化前后的查询耗时: +-- 传统limit,文件扫描 +select * from table order by id limit 50000,2; +受影响的行: 0 +时间: 0.171s + +-- 子查询方式,索引扫描 +select * from table +where id >= (select id from table order by id limit 50000 , 1) +limit 2; +受影响的行: 0 +时间: 0.035s + +-- JOIN分页方式 +select * from table as t1 +join (select id from table order by id limit 50000, 1) as t2 +where t1.id <= t2.id order by t1.id limit 2; +受影响的行: 0 +时间: 0.036s +``` + +原因:因为 MySQL 并非是跳过偏移量直接去取后面的数据,而是先把偏移量+要取的条数,然后再把前面偏移量这一段的数据抛弃掉再返回的。比如上面的(50000,2),每次取2条,还要经过回表,发现不是想要的,舍弃。那肯定非常耗时间,而通过子查询通过id索引,只查询id,使用到了innodb的索引覆盖, 在内存缓冲区中进行检索,没有回表查询. 然后再用id >= 条件,进一步的缩小查询范围.这样就大大提高了效率。 + +而MyISAM,是直接索引是分离的,通过索引文件查到的数据记录地址,不需要回表,直接对应数据记录,效率也很高。 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index 524569b2..e794162e 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,5 +1,10 @@ +public class Main { + public static void main(String[] args) { + final int a = 3; + } +} class TreeNode { diff --git a/README.md b/README.md index 3c8cc76b..5d9f9234 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,11 @@ - [类文件结构](/Interview/sad/类文件结构.md) - [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) -- [JVM内存区域](/Interview/sad/JVM内存区域.md) +- [JVM内存区域](/Interview/sad/JVM内存区域.md +- [逃逸分析](/Interview/sad/逃逸分析.md)) - [对象的创建过程](/Interview/sad/对象的创建过程.md) - [垃圾回收](/Interview/sad/垃圾回收.md) -- [逃逸分析](/Interview/sad/逃逸分析.md) +- [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 4360917ba28bc153251be5a02f83e31d4b1a98e8 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 2 Sep 2020 09:55:35 +0800 Subject: [PATCH 042/366] =?UTF-8?q?MySQL=E7=9A=84ACID=E5=92=8C=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...24\347\246\273\347\272\247\345\210\253.md" | 68 +++++++++++++++++++ README.md | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 "Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" diff --git "a/Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" "b/Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" new file mode 100644 index 00000000..7048090d --- /dev/null +++ "b/Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" @@ -0,0 +1,68 @@ +> 这个问题经常问,但是问我的比较少,其实MySQL这一块,问的最多还是优化问题 + +面试官:聊聊ACID是什么 + +我:分别是:**原子性(Atomicity)**、**一致性(Consistency)**、**隔离性(Isolation)**和**持久性(Durability)**。 + +1. 原子性: + +根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态!比如:如果不保证原子性,OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~。可能会聊undolog + +2. 一致性: + +根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!**比如:A给B转100,B不能因为通过这个事务增加了150吧?或者A扣了150吧? + +3. 隔离性: + +根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 + +4. 持久性: + +根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。这里可能会让你聊redolog + +面试官:并发事务带来的问题都有哪些 + +我:**脏读、不可重复读和幻读(实际上还有一个丢弃修改)** + +1. **脏读** + +第一个事务首先读取变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取为100,此时第一个事务做了回滚。最终第二个事务读取的变量和数据库的不一样。 + +2. **丢弃修改** + +T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,**T2 的修改覆盖了 T1 的修改**。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 + +3. **不可重复读** + +T2 读取一个数据,T1 对该数据做了修改并提交。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 + +4. **幻读** + +T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和第一次读取的结果不同。(和不可重复读的区别:一个是变量变化,一个是范围变化) + +面试官:数据库的隔离界别? + +我: + +首先,MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ**(可重读) + +其次,**这里需要注意的是**:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是**Next-Key Lock 锁**算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 + +因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 + +1. 未提交读 + +事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** + +2. 提交读 + +对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** + +3. 可重复读 + +就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** + +4. 可串行读 + +在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** + diff --git a/README.md b/README.md index 5d9f9234..b0b97257 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,12 @@ - [类文件结构](/Interview/sad/类文件结构.md) - [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) -- [JVM内存区域](/Interview/sad/JVM内存区域.md +- [JVM内存区域](/Interview/sad/JVM内存区域.md) - [逃逸分析](/Interview/sad/逃逸分析.md)) - [对象的创建过程](/Interview/sad/对象的创建过程.md) - [垃圾回收](/Interview/sad/垃圾回收.md) - [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) +- [MySQL的ACID和隔离级别](/Interview/sad/MySQL的ACID和隔离级别.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From f6af48ad349a688064b15fffaa884fbac61c728a Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 2 Sep 2020 10:47:43 +0800 Subject: [PATCH 043/366] MVCC,redolog,undolog,binlog --- Interview/sad/MVCC,redolog,undolog,binlog.md | 17 +++++++++++++++++ README.md | 1 + 2 files changed, 18 insertions(+) create mode 100644 Interview/sad/MVCC,redolog,undolog,binlog.md diff --git a/Interview/sad/MVCC,redolog,undolog,binlog.md b/Interview/sad/MVCC,redolog,undolog,binlog.md new file mode 100644 index 00000000..3e37ad91 --- /dev/null +++ b/Interview/sad/MVCC,redolog,undolog,binlog.md @@ -0,0 +1,17 @@ +面试官:分别讲一下MySQL的几大文件,你懂的 + +我:我不懂,ok,好的。 + +- undoLog 也就是我们常说的**回滚日志文件** 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由**引擎层的InnoDB引擎实现**,是**逻辑日志**,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,**undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事务用到该版本的信息时才可以清理相应undolog**。它保存了事务发生之前的数据的一个版本,用于回滚,**同时可以提供多版本并发控制下的读(MVCC)**,也即非锁定读。 +- redoLog 是重做日志文件是**记录数据修改之后的值**,**用于持久化到磁盘中**。redo log包括两部分:**一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的**;二是**磁盘上的重做日志文件(redo log file)**,该部分日志是持久的。由引**擎层的InnoDB引擎实现**,是**物理日志**,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。 +- binlog由**Mysql的Server层实现**,是**逻辑日志**,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:**事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复**。 +- MVCC多版本并发控制是MySQL中基于**乐观锁理论实现隔离级别**的方式,用于**读已提交和可重复读**取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:**最近修改该行数据的事务ID**,**指向该行(undolog表中)回滚段的指针**。**Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表**。 + +**binlog和redolog的区别**: + +1. redolog是在**InnoDB存储引擎层产生**,而**binlog是MySQL数据库的上层服务层产生**的。 +2. 两种日志记录的内容形式不同。MySQL的**binlog是逻辑日志**,其记录是**对应的SQL语句**。而**innodb存储引擎层面的重做日志是物理日志**。 +3. 两种日志与记录写入磁盘的时间点不同,**binlog日志只在事务提交完成后进行一次写入**。而**innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的**。 +4. **binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用**。 +5. **binlog可以作为恢复数据使用,主从复制搭建**,**redolog作为异常宕机或者介质故障后的数据恢复使用**。 + diff --git a/README.md b/README.md index b0b97257..fe4b5b47 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ - [垃圾回收](/Interview/sad/垃圾回收.md) - [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) - [MySQL的ACID和隔离级别](/Interview/sad/MySQL的ACID和隔离级别.md) +- [MVCC,redolog,undolog,binlog](/Interview/sad/MVCC,redolog,undolog,binlog.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 188c45f28c0a67441aeef0152170d1fed8a65421 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 2 Sep 2020 17:22:49 +0800 Subject: [PATCH 044/366] =?UTF-8?q?MySQL=E7=B4=A2=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sad/MySQL\347\264\242\345\274\225.md" | 148 ++++++++++++++++++ Interview/src/Main.java | 6 - README.md | 1 + 3 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 "Interview/sad/MySQL\347\264\242\345\274\225.md" diff --git "a/Interview/sad/MySQL\347\264\242\345\274\225.md" "b/Interview/sad/MySQL\347\264\242\345\274\225.md" new file mode 100644 index 00000000..e121d4c6 --- /dev/null +++ "b/Interview/sad/MySQL\347\264\242\345\274\225.md" @@ -0,0 +1,148 @@ +> 这一块,最好能知道怎么优化 + +面试官:索引介绍一下 + +我:ok,先说一下**索引类型**: + +- FULLTEXT:即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 +- HASH:由于HASH的唯一及类似键值对的形式,很适合作为索引。 HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 +- BTREE:BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 +- RTREE:RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 + +再说一下**索引种类**: + +- 普通索引:仅加速查询 +- 唯一索引:加速查询 + 列值唯一(可以有null) +- 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 +- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 +- 全文索引:对文本的内容进行分词,进行搜索 +- 索引合并:使用多个单列索引组合搜索 +- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 +- 聚簇索引:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) + +其次说**索引结构**: + +**MyISAM**: +1. MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址,同样使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址 +2. 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 +3. MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录 + +**InnoDB**: +1. InnoDB的数据文件本身就是索引文件,这棵树的叶节点data域保存了完整的数据记录(聚集索引) +2. InnoDB的辅助索引data域存储相应记录主键的值而不是地址 +3. 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。 + +补充一下**为什么InnoDB索引是B+**: + +- Hash索引:Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描 +- 二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。 +- 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。 +- 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。 +- B+树:在B树的基础上,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**;此外将叶节点使用指针连接成链表,范围查询更加高效。B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点, + +补充B树: + +B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让**查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成**。B树,概括来说是一个**一般化的二叉查找树**(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。 + +- 关键字集合分布在整颗树中; +- 任何一个关键字出现且只出现在一个结点中; +- 搜索有可能在非叶子结点结束; +- 其搜索性能等价于在关键字全集内做一次二分查找; + +查询什么时候不走**索引**: + +1. **模糊查询 %like** +2. **索引列参与计算,使用了函数** +3. **非最左前缀顺序** +4. **where对null判断** +5. **where不等于** +6. or操作有至少一个字段没有索引 +7. 需要回表的查询结果集过大(超过配置的范围) +8. **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** + +**索引最左原则:** + +**举例子**: +如果索引列分别为A,B,C,顺序也是A,B,C: + +- 那么查询的时候,如果查询【A】【A,B】 【A,B,C】,那么可以通过索引查询 +- 如果查询的时候,采用【A,C】,那么C这个虽然是索引,但是由于中间缺失了B,因此C这个索引是用不到的,只能用到A索引 +- 如果查询的时候,采用【B】 【B,C】 【C】,由于没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了 +- 如果查询的时候,采用范围查询,并且是最左前缀,也就是第一列索引,那么可以用到索引,但是范围后面的列无法用到索引(比如,a>= 3 and b = 4 and c = 5; A走索引,bc不走)(比如,a = 3 and b >= 4 and c = 5; a和b走,c不走) + +**组合索引的底层其实按照第一个索引排序,从排序里面查第二个索引,以此类推。如果第一个索引失效,或者没有经过第一个索引,后面没发在前面的基础上查询。** + +**为什么使用索引?** + +- 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 +- 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 +- 帮助服务器**避免排序和临时表**。 +- 将**随机IO变为顺序IO**。 +- 可以**加速表和表之间的连接**,特别是在实现数据的参考完整性方面特别有意义。 + +但是使用索引要看一条准则--- 那就是读写比例,我们知道索引的缺点: + +- 当对表中的数据进行增加、删除和修改的时候,**索引也要动态的维护**,这样就降低了数据的维护速度。 +- 索引需要**占物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 +- **创建索引和维护索引要耗费时间**,这种时间随着数据量的增加而增加 + +**你想,如果某个场景,发送10条请求,9条写,1条读。 加索引岂不是在浪费效率和空间?** + +面试官:聊聊**explain** + +我:好的,不过这一块内容好多,我只说几个关键的吧 + +1. id : 表示SQL执行的顺序的标识,SQL从大到小的执行 +2. select_type:表示查询中每个select子句的类型 +3. table:显示这一行的数据是关于哪张表的,有时不是真实的表名字 +4. type:表示MySQL在表中找到所需行的方式,又称“访问类型”。常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好) +5. possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用 +6. Key:key列显示MySQL实际决定使用的键(索引),如果没有选择索引,键是NULL。 +7. key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的) +8. ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值 +9. rows: 表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,理论上行数越少,查询性能越好 +10. Extra:该列包含MySQL解决查询的详细信息 + +面试官:慢查询优化 + +我:我试试 + +打开慢查询日志 + +1. 先运行看看是否真的很慢,注意设置SQL_NO_CACHE +2. where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高 +3. explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询) +4. order by limit 形式的sql语句让排序的表优先查(这里要注意如果数据量大,要注意了) +5. 了解业务方使用场景 +6. 加索引时参照建索引的几大原则 +7. 观察结果,不符合预期继续从0分析 + + + +咱们知道,使用limit分页查询,offset越大,性能越差,比如: + +```sql +-- 以真实的生产环境的6万条数据的一张表为例,比较一下优化前后的查询耗时: +-- 传统limit,文件扫描 +select * from table order by id limit 50000,2; +受影响的行: 0 +时间: 0.171s + +-- 子查询方式,索引扫描 +select * from table +where id >= (select id from table order by id limit 50000 , 1) +limit 2; +受影响的行: 0 +时间: 0.035s + +-- JOIN分页方式 +select * from table as t1 +join (select id from table order by id limit 50000, 1) as t2 +where t1.id <= t2.id order by t1.id limit 2; +受影响的行: 0 +时间: 0.036s +``` + +原因:因为 MySQL 并非是跳过偏移量直接去取后面的数据,而是先把偏移量+要取的条数,然后再把前面偏移量这一段的数据抛弃掉再返回的。比如上面的(50000,2),每次取2条,还要经过回表,发现不是想要的,舍弃。那肯定非常耗时间,而通过子查询通过id索引,只查询id,使用到了innodb的索引覆盖, 在内存缓冲区中进行检索,没有回表查询. 然后再用id >= 条件,进一步的缩小查询范围.这样就大大提高了效率。 + +而MyISAM,是直接索引是分离的,通过索引文件查到的数据记录地址,不需要回表,直接对应数据记录,效率也很高。 \ No newline at end of file diff --git a/Interview/src/Main.java b/Interview/src/Main.java index e794162e..64e030cb 100644 --- a/Interview/src/Main.java +++ b/Interview/src/Main.java @@ -1,10 +1,4 @@ -public class Main { - - public static void main(String[] args) { - final int a = 3; - } -} class TreeNode { diff --git a/README.md b/README.md index fe4b5b47..ed556a4e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ - [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) - [MySQL的ACID和隔离级别](/Interview/sad/MySQL的ACID和隔离级别.md) - [MVCC,redolog,undolog,binlog](/Interview/sad/MVCC,redolog,undolog,binlog.md) +- [MySQL索引](/Interview/sad/MySQL索引.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 2f7be1662098d7480539ffbe0656f788b324f796 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Wed, 2 Sep 2020 23:03:30 +0800 Subject: [PATCH 045/366] =?UTF-8?q?MySQL=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...23\346\236\204\344\274\230\345\214\226.md" | 90 +++++++++++++++++++ README.md | 1 + 2 files changed, 91 insertions(+) create mode 100644 "Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" diff --git "a/Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" "b/Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" new file mode 100644 index 00000000..cc6c2b96 --- /dev/null +++ "b/Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" @@ -0,0 +1,90 @@ +> 一个是索引优化,一个是结构优化。。。。 + +面试官:先来个**三范式** + +我:好的 + +1. 第一范式:第一范式就是原子性,字段不可再分割; + +- 数据库表中的所有字段都具有单一属性(即字段不能再分割了) +- 单一属性的列是由基本数据类型所构成的 +- 设计出来的表都是简单的二维表 + +2. 第二范式:第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言) +3. 第三范式:所有非主键字段和主键字段之间不能产生传递依赖 + +[例子](https://www.jianshu.com/p/3e97c2a1687b) + +**反范式设计**:反范式化就是为了性能和读取效率的考虑而适当的对数据库设计范式的要求进行违反,**而允许存在少量的数据冗余,也就是使用空间来换取(查询)时间**;比如举个例子:**比如订单表中应该保留当前购买商品的价格、商品的名称(商品的价格是会变动的,这很危险)** + +因此,简单说一下**范式化设计的优缺点**: + +优点: + +- 可以尽量的减少数据冗余 +- 范式化的更新操作比反范式化更快 +- 范式化的表通常比反范式化更小 + +缺点: + +- 对于查询需要关联多个表 +- 更难进行索引优化 + +**反范式化设计的优缺点**: + +优点: + +- 可以减少表的关联 +- 可以更好的进行索引优化 + +缺点: + +- 存在数据冗余和数据维护异常 +- 对数据修改需要更多的成本 + +**如何选择varchar和char类型**: + +- varchar用于存储变长字符串,只占用的必要的存储空间。 +- char类型是定长的,char类型的最大宽度为255 +- 场景:varchar适用于存储很少被更新的字符串列;char适合存储长度近似的值,适合存储短字符串,适合存储经常更新的字符串 + +**分库分表**: + +1. **垂直分表** + +也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。 一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。 + +2. **垂直分库** + +垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。 切分后,要放在多个服务器上,而不是一个服务器上。为什么? 我们想象一下,一个购物网站对外提供服务,会有用户,商品,订单等的CRUD。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。按垂直分库后,如果还是放在一个数据库服务器上, 随着用户量增大,这会让单个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间,内存,tps等非常吃紧。所以我们要拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈。 + +3. **水平分表** + +针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用。 + +切分规则: + +- RANGE:从0到10000一个表,10001到20000一个表; +- HASH取模:一个商场系统,一般都是将用户,订单作为主表,然后将和它们相关的作为附表,这样不会造成跨库事务之类的问题。 取用户id,然后hash取模,分配到不同的数据库上。 +- 地理区域:比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此。 +- 时间:按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。 + +4. **水平分库分表** + +将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。 + +**分库分表带来的问题:** + +1. **事务支持** + +分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 + +2. **join** + +TODO 分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。 粗略的解决方法: 全局表:基础数据,所有库都拷贝一份。 字段冗余:这样有些字段就不用join去查询了。 系统层组装:分别查询出所有,然后组装起来,较复杂。 + +**分库分表中间件:** + +Mycat 和 ShardingSphere(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 3 款产品)。 + +[对比](https://my.oschina.net/u/4318872/blog/4281049) \ No newline at end of file diff --git a/README.md b/README.md index ed556a4e..d69c95a2 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ - [MySQL的ACID和隔离级别](/Interview/sad/MySQL的ACID和隔离级别.md) - [MVCC,redolog,undolog,binlog](/Interview/sad/MVCC,redolog,undolog,binlog.md) - [MySQL索引](/Interview/sad/MySQL索引.md) +- [MySQL数据库结构优化](/Interview/sad/MySQL数据库结构优化.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 9169d96376280fc367b6f187bd14c034f4f34d07 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 09:47:19 +0800 Subject: [PATCH 046/366] =?UTF-8?q?MySQL=E7=9A=84=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sad/MySQL\347\232\204\351\224\201.md" | 61 +++++++++++++++++++ README.md | 1 + 2 files changed, 62 insertions(+) create mode 100644 "Interview/sad/MySQL\347\232\204\351\224\201.md" diff --git "a/Interview/sad/MySQL\347\232\204\351\224\201.md" "b/Interview/sad/MySQL\347\232\204\351\224\201.md" new file mode 100644 index 00000000..35b48f04 --- /dev/null +++ "b/Interview/sad/MySQL\347\232\204\351\224\201.md" @@ -0,0 +1,61 @@ +> MySQL的锁,其实跟Java差不了,一个思想。 + +面试官:MySQL的锁,介绍一下 + +我: + +MyISAM:MyISAM只有表锁,其中又分为共享读锁和独占写锁。 +- MyISAM表的读操作,不会阻塞其他用户对同一个表的读请求,但会阻塞对同一个表的写请求。 +- MyISAM表的写操作,会阻塞其他用户对同一个表的读和写操作。 +- MyISAM表的读、写操作之间、以及写操作之间是串行的。 + +Innodb行锁:共享锁,排他锁 +- 对于UPDATE、DELETE、INSERT语句,Innodb会自动给涉及的数据集加排他锁(X);对于普通SELECT语句,Innodb不会加任何锁。 +```sql +//显示共享锁(S) : + SELECT * FROM table_name WHERE .... LOCK IN SHARE MODE + //显示排他锁(X): + SELECT * FROM table_name WHERE .... FOR UPDATE. +``` +- 记录锁(Record Locks):记录锁是封锁记录,记录锁也叫行锁,注意:行锁是针对索引的,如果表中没有索引,那么就会锁整张表 +- 间隙锁(GAP)对于键值在条件范围内但并不存在的记录,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。 +- 临键锁(Next-Key Lock):(Record Locks+GAP),锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 + +面试官:给你张表怎么用cas实现高并发下的update操作 + +我: + +第一种: + +```xml +// cas, 期望值和数据表中的旧值一致,才更新。 +# newStock = oldStock-desStock; + + UPDATE t_order SET stock=#{newStock} WHERE id=#{orderId} AND stock=#{oldStock} + +``` + +```java +// orderId:订单id +// getStock:库存:旧值 +// desStock:可以是期望值,但这里预减值 +int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock); +``` + +第二种: + +```xml +// 用版本号 +// 我期望的版本号, 和旧版本号一致才更新,并且版本号累加... + + UPDATE t_order SET stock=stock-#{desStock}, version=version+1 + WHERE id=#{orderId} AND version=#{oldVersion} + +``` +如 +```java +// orderId:订单id +// getVersion:获取数据库版本号,旧版本 +// desStock:可以是期望值,但这里预减值 +int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock); +``` \ No newline at end of file diff --git a/README.md b/README.md index d69c95a2..258e8efd 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ - [MVCC,redolog,undolog,binlog](/Interview/sad/MVCC,redolog,undolog,binlog.md) - [MySQL索引](/Interview/sad/MySQL索引.md) - [MySQL数据库结构优化](/Interview/sad/MySQL数据库结构优化.md) +- [MySQL的锁](/Interview/sad/MySQL的锁.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From bba94d173c34809be5a597486a4cc0f9519ee25d Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 10:24:10 +0800 Subject: [PATCH 047/366] =?UTF-8?q?Redis=E7=9A=84=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Interview/sad/MVCC,redolog,undolog,binlog.md | 15 ++++ ...is\347\232\204\346\250\241\345\236\213.md" | 68 +++++++++++++++++++ README.md | 1 + 3 files changed, 84 insertions(+) create mode 100644 "Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" diff --git a/Interview/sad/MVCC,redolog,undolog,binlog.md b/Interview/sad/MVCC,redolog,undolog,binlog.md index 3e37ad91..9e485a3b 100644 --- a/Interview/sad/MVCC,redolog,undolog,binlog.md +++ b/Interview/sad/MVCC,redolog,undolog,binlog.md @@ -15,3 +15,18 @@ 4. **binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用**。 5. **binlog可以作为恢复数据使用,主从复制搭建**,**redolog作为异常宕机或者介质故障后的数据恢复使用**。 +**MVCC的缺点:** + +MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录**都需要额外的存储空间,需要做更多的行维护和检查工作**。 要知道的,MVCC机制下,会在更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本。 而undo log这个关键的东西,**记载的内容是串行化的结果,记录了多个事务的过程,不属于多版本共存**。 这么一看,似乎mysql的mvcc也并没有所谓的多版本共存 + +**读写分离原理**: + +主库(master)将变更写**binlog**日志,然后从库(slave)连接到主库之后,从库有一个**IO线程**,将主库的binlog日志**拷贝到自己本地**,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。 + +这里有一个非常重要的一点,就是从库同步主库数据的过程是**串行化**的,也就是说**主库上并行**的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 + +而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 + +所以mysql实际上在这一块有两个机制,一个是**半同步复制**,用来解决主库数据丢失问题;一个是**并行复制**,用来解决主从同步延时问题。 + +所谓并行复制,指的是从库**开启多个线程,并行读取relay log中不同库的日志**,然后并行重放不同库的日志,这是库级别的并行。 \ No newline at end of file diff --git "a/Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" "b/Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" new file mode 100644 index 00000000..efc0452c --- /dev/null +++ "b/Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" @@ -0,0 +1,68 @@ +面试官:Redis为什么快? + +我:内心:不知道为什么一直问这个问题。 + +1. 纯内存操作 +2. 单线程操作,避免了**频繁的上下文切换** +3. 合理高效的数据结构 +4. 采用了**非阻塞I/O多路复用**机制 + +实际上Redis服务器是一个事件驱动程序,分为**文件事件**和**时间事件**,就主要讲一下文件事件。 + +Redis基于Reactor模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器 + +- 文件事件处理器使用**I/O多路复用程序来同时监听多个套接字**,并根据套接字目前执行的任务来为套接字**关联不同的事件处理器** +- 当被监听的套接字准备好执行连接**应答、读取、写入、关闭**等操作时,与操作相对于的文件事件就会产生,这时**文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件**。 +- 简单点:**就是一堆套接字请求,被一个叫做I/O多路复用程序监听,通过文件事件分派器一个一个和事件处理器绑定在一起去处理**。 + +I/O多路复用程序是有常见的select、epoll等系统调用所实现的。有个小故事,自行理解BIO、NIO、select、poll、epoll等 + +故事情节为:**老李去买火车票,三天后买到一张退票。参演人员(老李,黄牛,售票员,快递员),往返车站耗费1小时。** + +**往返车站可以看成系统调用,调用一次一小时** + +### 1. 阻塞I/O模型 +老李去火车站买票,排队三天买到一张退票。 + +耗费:在车站吃喝拉撒睡 3天,其他事一件没干。 + +### 2. 非阻塞I/O模型 +老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。 + +耗费:往返车站6次,路上6小时,其他时间做了好多事。 + +2比1多了个自己轮训调用 + +### 3. I/O复用模型 +1. select/poll + +老李去火车站买票,委托黄牛,然后每隔6小时电话**黄牛**询问,黄牛三天内买到票,然后老李去火车站交钱领票。 + +耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次 + +实际上,就是自己不断调select(像个船一样,装了很多描述符)询问哪些描述符可读可写,比如又一个可读了,咱就调用可读系统调用就ok了 + +2. epoll + +老李去火车站买票,委托黄牛,**黄牛买到后即通知老李去领**,然后老李去火车站交钱领票。 +耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话 + +实际上,自己不用管了,当有可读的时候,直接中断你,然后你自己去读 + +### 4. 信号驱动I/O模型 + +老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。 + +耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话 + +不要黄牛了,省了这个单线程,系统通知你,你收到以后自己去读 + +### 5. 异步I/O模型 +老李去火车站,告诉售票员要买票,售票员买到票之后,打电话通知老李把票放在某某储物箱,老李根据储物箱地址自己去取票。 + +耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话 + +只需要注册一次,得到消息之后,就去另外一个地址上取走票 + +黄牛是多路复用,他不仅可以帮你买票,还可以其他人买票,还可以买飞机票,高铁票等。 + diff --git a/README.md b/README.md index 258e8efd..5c6d0dbe 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ - [MySQL索引](/Interview/sad/MySQL索引.md) - [MySQL数据库结构优化](/Interview/sad/MySQL数据库结构优化.md) - [MySQL的锁](/Interview/sad/MySQL的锁.md) +- [Redis的模型](/Interview/sad/Redis的模型.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From f6d043e85e8e87b2cb5481fc4d0b08d91b1a892e Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 10:52:04 +0800 Subject: [PATCH 048/366] =?UTF-8?q?Redis=E6=95=B0=E6=8D=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...60\346\215\256\347\273\223\346\236\204.md" | 44 +++++++++++++++++++ README.md | 1 + 2 files changed, 45 insertions(+) create mode 100644 "Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" diff --git "a/Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000..2d338fdf --- /dev/null +++ "b/Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,44 @@ +#### String +String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; **常规计数:微博数,粉丝数**等。 + +#### Hash +Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来**存储用户信息,商品信息**等等。 + +简单说一下结构 + +- 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 +- Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 +- Redis使用MurmurHash2算法来计算键的哈希值。 +- 哈希表使用链地址法来解决键冲突。 + +注意:这里和Java的HashMap不同的rehash过程 +1. Redis的rehash过程是扩展和收缩,而且还是渐进式的rehash +2. Redis的字典有两个哈希表ht[0]和ht[1] +3. 为字典的ht[1]哈希表分配空间,如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used *2的2^n;如果执行的是收缩操作,那么ht[1]的大小第一个大于等于ht[0].used的2^n。(举个例子,ht[0]的长度为10,那么扩展就是2^5的32,如果是压缩的话2^4=16) +4. 如果ht[0]的键值非常多的话,一次性转移过去,是一个非常耗时的操作哦,因此并非一次性,采取渐进式rehash转移。 + +#### List +list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如**微博的关注列表,粉丝列表, 消息列表**等功能都可以用Redis的 list 结构来实现。 + +Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 + +另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 + +#### Set +set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 + +当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 + +比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如**共同关注、共同粉丝、共同喜好**等功能。这个过程也就是求交集的过程,具体命令如下:`sinterstore key1 key2 key3`将交集存在key1内 + + +#### Zset +和set相比,sorted set增加了一个**权重参数score**,使得集合中的元素能够按score进行**有序**排列。 + +举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种**礼物排行榜**,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 + +跳跃表,暂时先放一个链接[https://zhuanlan.zhihu.com/p/53975333](https://zhuanlan.zhihu.com/p/53975333) + +- 简单来说跳跃表是一种有序数据结构,它通过在**每个节点中维持多个指向其他节点的指针**,从而达到快速访问节点的目的。 +- 跳跃表平均O(longN),最坏O(N)复杂度的节点查找 +- 跳跃表有个层的概念:层带有两个属性:**前进指针和跨度**,前进指针用于**访问位于表尾方向的其他节点**,而跨度则记录了**前进指针所指向节点和当前节点的距离**。一般情况下,层越多,查找效率越高。 \ No newline at end of file diff --git a/README.md b/README.md index 5c6d0dbe..a9f1ced2 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ - [MySQL数据库结构优化](/Interview/sad/MySQL数据库结构优化.md) - [MySQL的锁](/Interview/sad/MySQL的锁.md) - [Redis的模型](/Interview/sad/Redis的模型.md) +- [Redis数据结构](/Interview/sad/Redis数据结构.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From b558dfdc40ba927e7fdf2f0e7b95ca896517895d Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 11:01:47 +0800 Subject: [PATCH 049/366] =?UTF-8?q?Redis=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...is\346\214\201\344\271\205\345\214\226.md" | 41 +++++++++++++++++++ README.md | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 "Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" diff --git "a/Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" "b/Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" new file mode 100644 index 00000000..3b77e4c8 --- /dev/null +++ "b/Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" @@ -0,0 +1,41 @@ +面试官:Redis的持久化了解嘛? + +我:了解,Redis的持久化分为两种:**RDB和AOF** + +**RDB**: + +**RDB**是一种**快照存储持久化**方式,具体就是将`Redis`某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为`dump.rdb`。在`Redis`服务器启动时,会重新加载`dump.rdb`文件的数据到内存当中恢复数据。 + +优点: +1. RDB会生成多个数据文件,**每个数据文件都代表了某一个时刻中redis的数据**,这种多个数据文件的方式,非常适合**做冷备**。 +2. RDB对redis对外提供读写服务的时候,影像非常小,因为redis 主进程只需要fork一个子进程出来,让子进程对磁盘io来进行rdb持久化 +3. **RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快**。 + +缺点: +1. **如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好**,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。 +2. RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒 + +![rdb](http://media.dreamcat.ink/uPic/iShot2020-08-12下午04.56.18.png) + +**AOF**: + +**AOF**:把所有的**对Redis的服务器进行修改的命令都存到一个文件里,命令的集合**。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。 + +优点: +1. **AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒**,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。 +2. **AOF以appen-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高**。 +3. AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。 + +缺点: +1. 对于同一份文件**AOF文件比RDB数据快照要大**。 +2. AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的 +3. **数据恢复比较慢,不适合做冷备**。 + +![AOF](http://media.dreamcat.ink/uPic/iShot2020-08-12下午04.57.10.png) + +**如何选择:** + +如何选择: + +综合AOF和RDB两种持久化方式,**用AOF来保证数据不丢失,作为恢复数据的第一选择**;**用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,可以使用RDB进行快速的数据恢复**。 + diff --git a/README.md b/README.md index a9f1ced2..23e78b50 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ - [类加载过程](/Interview/sad/类加载过程.md) - [类加载器](/Interview/sad/类加载器.md) - [JVM内存区域](/Interview/sad/JVM内存区域.md) -- [逃逸分析](/Interview/sad/逃逸分析.md)) +- [逃逸分析](/Interview/sad/逃逸分析.md) - [对象的创建过程](/Interview/sad/对象的创建过程.md) - [垃圾回收](/Interview/sad/垃圾回收.md) - [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) @@ -80,6 +80,7 @@ - [MySQL的锁](/Interview/sad/MySQL的锁.md) - [Redis的模型](/Interview/sad/Redis的模型.md) - [Redis数据结构](/Interview/sad/Redis数据结构.md) +- [Redis持久化](/Interview/sad/Redis持久化.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 3e948474499cdc727f9c968d088fc7aa72ee6387 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 16:07:57 +0800 Subject: [PATCH 050/366] =?UTF-8?q?Redis=E5=86=85=E5=AD=98=E6=B7=98?= =?UTF-8?q?=E6=B1=B0=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...30\346\261\260\346\234\272\345\210\266.md" | 21 +++++++++++++++++++ README.md | 1 + 2 files changed, 22 insertions(+) create mode 100644 "Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" diff --git "a/Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" "b/Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" new file mode 100644 index 00000000..5c41450c --- /dev/null +++ "b/Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" @@ -0,0 +1,21 @@ +面试官:先讲一下Redis的过期时间 + +我: + +定期删除+惰性删除 + +- 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! +- 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! + +面试官:如果定期删除漏掉了很多过期 key,然后你也没及时去查, 也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? + +我:**redis 内存淘汰机制。** + +redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略: + +- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 +- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰 +- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 +- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰 +- allkeys-random:从数据集中任意选择数据淘汰 +- no-enviction(驱逐):禁止驱逐数据 diff --git a/README.md b/README.md index 23e78b50..f18462d8 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ - [Redis的模型](/Interview/sad/Redis的模型.md) - [Redis数据结构](/Interview/sad/Redis数据结构.md) - [Redis持久化](/Interview/sad/Redis持久化.md) +- [Redis内存淘汰机制](/Interview/sad/Redis内存淘汰机制.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From a1b5907458ab65c979d8fdc8d640f7b6bb8d9fc2 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 16:19:18 +0800 Subject: [PATCH 051/366] =?UTF-8?q?Redis=E7=BC=93=E5=AD=98=E7=A9=BF?= =?UTF-8?q?=E9=80=8F=E5=92=8C=E9=9B=AA=E5=B4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...17\345\222\214\351\233\252\345\264\251.md" | 21 +++++++++++++++++++ README.md | 1 + 2 files changed, 22 insertions(+) create mode 100644 "Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" diff --git "a/Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" "b/Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" new file mode 100644 index 00000000..e3e362d2 --- /dev/null +++ "b/Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" @@ -0,0 +1,21 @@ +> 感觉这个被问烂了 + +面试官:聊聊什么是缓存穿透和雪崩 + +我:ok + +**缓存穿透**: + +一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +1. 在接口做校验 +2. 存null值(缓存击穿加锁) +3. 布隆过滤器拦截: 将所有可能的查询key先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。 + +**缓存雪崩:** + +缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +1. 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉 +2. 缓存时间不一致,给缓存的失效时间,加上一个随机值,避免集体失效 +3. 限流降级策略:有一定的备案,比如个性推荐服务不可用了,换成热点数据推荐服务 \ No newline at end of file diff --git a/README.md b/README.md index f18462d8..a7500ff0 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ - [Redis数据结构](/Interview/sad/Redis数据结构.md) - [Redis持久化](/Interview/sad/Redis持久化.md) - [Redis内存淘汰机制](/Interview/sad/Redis内存淘汰机制.md) +- [Redis缓存穿透和雪崩](/Interview/sad/Redis缓存穿透和雪崩.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From e2a61857993187b4a1aee8df891b3bd4602a03ac Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 16:21:45 +0800 Subject: [PATCH 052/366] =?UTF-8?q?Redis=E5=B9=B6=E5=8F=91=E7=AB=9E?= =?UTF-8?q?=E4=BA=89key=E7=9A=84=E8=A7=A3=E5=86=B3=E6=96=B9=E6=A1=88?= =?UTF-8?q?=E8=AF=A6=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a7500ff0..7612a0ba 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ - [Redis持久化](/Interview/sad/Redis持久化.md) - [Redis内存淘汰机制](/Interview/sad/Redis内存淘汰机制.md) - [Redis缓存穿透和雪崩](/Interview/sad/Redis缓存穿透和雪崩.md) +- [Redis与MySQL双写一致性方案](https://www.cnblogs.com/rjzheng/p/9041659.html) +- [Redis并发竞争key的解决方案详解](https://juejin.im/post/6844903846750191630) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From f397ec84268b33b2c3d92b6e27d991b4c4cbf5cc Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 16:23:42 +0800 Subject: [PATCH 053/366] =?UTF-8?q?=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7612a0ba..63a9505d 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ - [Redis缓存穿透和雪崩](/Interview/sad/Redis缓存穿透和雪崩.md) - [Redis与MySQL双写一致性方案](https://www.cnblogs.com/rjzheng/p/9041659.html) - [Redis并发竞争key的解决方案详解](https://juejin.im/post/6844903846750191630) +- [Redis分布式锁](https://juejin.im/post/5cc165816fb9a03202221dd5#heading-4) +- [补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) +- [数据库实现分布式锁](https://blog.csdn.net/u013256816/article/details/92854794) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From cfc8b1edd3277ed7e01ee4efa0d5988ec6ca4e74 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 3 Sep 2020 16:41:54 +0800 Subject: [PATCH 054/366] =?UTF-8?q?CAP=E5=92=8CBASE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Interview/sad/CAP\345\222\214BASE.md" | 17 +++++++++++++ ...06\345\270\203\345\274\217\351\224\201.md" | 25 +++++++++++++++++++ README.md | 3 ++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 "Interview/sad/CAP\345\222\214BASE.md" create mode 100644 "Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" diff --git "a/Interview/sad/CAP\345\222\214BASE.md" "b/Interview/sad/CAP\345\222\214BASE.md" new file mode 100644 index 00000000..d729d5ee --- /dev/null +++ "b/Interview/sad/CAP\345\222\214BASE.md" @@ -0,0 +1,17 @@ +面试官:知道分布式中的CAP嘛? + +我:知道 + +- C(一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。 +- A(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。 +- P(分区容错性):当出现网络分区后,系统能够继续工作。打个比方,集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。 +- CP:对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。 +- AP:对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。 + +面试官:BASE呢? + +我:简单 + +BASE是BasicallyAvailable(基本可用)、Softstate(软状态)和Eventuallyconsistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展-基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。 +- 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。 +- 最终一致:**最终一致是指经过一段时间后,所有节点数据都将会达到一致**。BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。 \ No newline at end of file diff --git "a/Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 00000000..6603a675 --- /dev/null +++ "b/Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,25 @@ +> 毕竟判断和绑定座位(或者下单)非原子性,为了降低锁的粒度,可以将判断和绑定座位锁在一个事务里。集群:Redisson + +- Key为xx_座位号,过期时间为随机1-5s(用setex的命令,该命令是key和过期时间是原子性的) +- 每次先Redis中判断该key存在不存在,如果存在,要么阻塞,要么就返回给用户,座位已被选择。 +- 如果不存在,先上锁,然后再判断和绑定座位(或者下单)。其实这里有个隐藏的问题。如果绑定座位非常耗时,超过了过期时间1-5s,就凉凉了。其实这里设置过期时间,就是防止一直因为某种原因阻塞而不释放锁 +- 前三步,少了个签证value,如果不设置,那么当锁过期了,业务逻辑才走完,准备删除的时候,B客户端获取到了该锁,但是A把B的key锁删除了,然而B还不知道。 +- 因此,要解决这个问题,可以设置value签证,结束的时候判断一次,该value是不是自己的value,这样就不会误删。 + +#### RedLock算法流程 + +首先有这样的问题: +1. 客户端 A 从 Master 上获取锁。 +2. 在锁未被复制到某 Slave 节点的时候,Master 节点 Down 掉了。 +3. 某 Slave 节点成为新的 Master。 +4. 客户端 B 可从新 Master 上获取锁。 + +假设有5个实例 + +1. 比如过期时间为TTL:10min +2. 记录当前时间:比如T1 = 12:00 +3. 客户端分别向5个实例获取锁,比如申请锁的时间依次为:12:01...12:05,最后获取实例的锁为T2:12:05(获取锁的超时时间要远远小于过期时间,防止死等。) +4. 如果获取锁的实例大于3个(过半机制),那么就相当于获取到锁了,该锁的真正的有效时间为TTL-(T2-T1) = 5min +5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 + +[https://juejin.im/post/5cc165816fb9a03202221dd5](https://juejin.im/post/5cc165816fb9a03202221dd5) \ No newline at end of file diff --git a/README.md b/README.md index 63a9505d..fa3b931f 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,10 @@ - [Redis缓存穿透和雪崩](/Interview/sad/Redis缓存穿透和雪崩.md) - [Redis与MySQL双写一致性方案](https://www.cnblogs.com/rjzheng/p/9041659.html) - [Redis并发竞争key的解决方案详解](https://juejin.im/post/6844903846750191630) -- [Redis分布式锁](https://juejin.im/post/5cc165816fb9a03202221dd5#heading-4) +- [Redis分布式锁](/Interview/sad/Redis分布式锁.md) - [补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) - [数据库实现分布式锁](https://blog.csdn.net/u013256816/article/details/92854794) +- [CAP和BASE](/Interview/sad/CAP和BASE.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From f2a29f21209469f661df69a7bc9e20fd529bbdce Mon Sep 17 00:00:00 2001 From: thebigpeng <13258118132@163.com> Date: Sun, 25 Oct 2020 12:31:24 +0800 Subject: [PATCH 055/366] 'java' --- Collections/test.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Collections/test.md diff --git a/Collections/test.md b/Collections/test.md new file mode 100644 index 00000000..58970232 --- /dev/null +++ b/Collections/test.md @@ -0,0 +1,3 @@ +## **test** + +这是一个测试文档。 \ No newline at end of file From 1c98ee0638823ee3d3628c910b1a6985b5f261d3 Mon Sep 17 00:00:00 2001 From: thebigpeng <13258118132@163.com> Date: Sun, 25 Oct 2020 12:33:37 +0800 Subject: [PATCH 056/366] 'java' --- Collections/test.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Collections/test.md diff --git a/Collections/test.md b/Collections/test.md deleted file mode 100644 index 58970232..00000000 --- a/Collections/test.md +++ /dev/null @@ -1,3 +0,0 @@ -## **test** - -这是一个测试文档。 \ No newline at end of file From 367ef2b9b3e24aa73f7f197255f4846fcdb67b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Tue, 29 Dec 2020 21:46:58 +0800 Subject: [PATCH 057/366] update README --- .DS_Store | Bin 0 -> 10244 bytes Interview/.DS_Store | Bin 0 -> 10244 bytes Interview/codes/.DS_Store | Bin 0 -> 14340 bytes Interview/mianjing/.DS_Store | Bin 0 -> 18436 bytes Interview/mianjing/ali/.DS_Store | Bin 0 -> 6148 bytes Interview/src/Main.java | 42 +++++++++++++++++++++++++++++++ Interview/src/Solution.java | 3 --- README.md | 2 +- SwordOffer/.DS_Store | Bin 0 -> 6148 bytes 9 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 .DS_Store create mode 100644 Interview/.DS_Store create mode 100644 Interview/codes/.DS_Store create mode 100644 Interview/mianjing/.DS_Store create mode 100644 Interview/mianjing/ali/.DS_Store create mode 100644 SwordOffer/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a5169ff3914d5f81ddb3cc38f41f55db0c9f47f6 GIT binary patch literal 10244 zcmeHMPj3@P6n_)avKs^0KvIa{u)?ZZi5yHFN-qf4fr16dZXAU~A(&lz;;h)rSi5Vx zNeHs|07x96>J8|zhaP(a4)6)!g7^q<>46(>X4WQMJBLaL0h)_%vj2(sY=)gv%0EojlEDOq*2S^<|#$1dYg;Evw zRM`WGRY{y;5T%auwpfmsi?O3nsRL2!K;kEpI71=w>8NMRS~a(5F7 zz#ziBeOvw>He8>(IQz_P_+A)gGSA7-uHmn~-fftsVeU1T%d5Oyj-03&=AB?k^h!>& z>c;XM=YCIi+IPfj3%tTH`r9K+KNpj ze|JrNWm!FAM^bzC?i;nn_8%AWDKyG&~pp?n|iYqH2jTS1_Xjl!n z@0WP!MIL91+h!N9g!lpq5nU^=6^>7AA>qY_SQgIHAVlobUs!a4z+rWoUtjhem-qzXE zm%UoFI!D=ApSCC~1{a-(ssQ)^PQN{njC9Vi@#CY3hA9Ifn>~Sna3Yfk1k;%O?w8rC z&0Dwc{eJ(!!#^KAmRit@g)!CdwNFs$BSlBT=N<`tuQU+{BT$AmMnHfuP=poy!!v(d zEPM)Q{SGev|Ihk?#%dq`+|I zLQ8iw1Db&^i2*WvrhF!k7IiOKlE#UT2V literal 0 HcmV?d00001 diff --git a/Interview/.DS_Store b/Interview/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..778c0b98f573629955df5ed0af7e29330ddab807 GIT binary patch literal 10244 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 OQ=QIkE@x`y|9=CP=3sLG literal 0 HcmV?d00001 diff --git a/Interview/mianjing/.DS_Store b/Interview/mianjing/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..25f7246f56832ad62e0d890c0f23f86c7c12ab2b GIT binary patch literal 18436 zcmeHP-*4MQ9RFV0G%?$#yRF*_JVZoDNGnoDE5^&TkPWdip@yX@E!}AC#7#{dU$C8~ zKPbvyfW#w{_z#eH;01|iB=`%EkP!a>5@ zz5ASf0RU2N*KYun03eT^RG@G=?<<^IMq5Q6IpDJ=imAyDi;{ zwU!m5)&m*5A(qj1kcAeQQU6gLo%^HSvP{>ToBK;uNoH(3JCVyPFP%Go;pItn>f)uT z%Tv?S(^sxeUz?eEbwjtupb^vuW`})B~;4Xh`D?L7r4We><6cp-Zr2qF1%FQoO-XxRg@2L|;3iw_0NOqK%K<^Pf3`U%U(1N^!|AWMPla_Ot0j<1T6tCE0X5U!5(1=bv8DUe+*xjGQ84kWlT z2^b3Tua0^Jzd4W+m-18gfb4;I4@_cJ4k-c+r~-wy-f@w?GyVs^_4wcKq{sf0ymCQ# zR7FBm^)#>TmNjRceXD5RwjPw1DYe3~rmrAnLPmFo+MZ_GhT|)nbpx9`{KT~Dv{PoB z(e=+$H*VZo+-&Y29DaH9 z)wkR-5<~}JW<}Fu{34jimnoi;=Dsr1pXNpxVRD&nNY>+&H$rm*{#q;zpt&I#RxqwJ zn)?x~zz!V1H}D<&06)X8@F)BO|B)O~`_kM`x%URrT*0AHl8f33qPp!<;4PGOEYY+g zyN{_7-LZ9?grU2SQw4%wsSaEY_pinmM_6hE3ySM zhr+d7dJktC1Lq`Xvh*%ER6unWQEdW?)<#+p9WikzG|L%w_=IR6nnTQ=<_tUHOaaMJ zawW^j?E$u2HiqM@1!h_!QefUt(MXQ1VVoHljm~6j4IC*%qAci$UlSRAYFDlzCJu#S zIi`-(m>S}>50VW;@=ls(hdKl%N+c(p!?UmyI8#7!-sd>%<)b=(vXIufMa)o%>ZGGO z=28LMIcWN*_xRI=v?5zDb0}QP+4Bg^o&|>rsE)k}}Z-%N!EdN#7JII8=z#5e}HvIDLvN zI+>@Q;J003`~Tj5`u_i98Yk~s_JHhxXX*hmez$tJj7@vHXGATw*RFth6EjP!U6;#H z1q=RGJZ#Ih;@!i{+WG&9fO3<4X9>$)F0q8^%r^oa|Bb^a?rD+y|2h72M6SIKzxMh+ DNfIHq literal 0 HcmV?d00001 diff --git a/Interview/mianjing/ali/.DS_Store b/Interview/mianjing/ali/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 map = new HashMap<>(); + for (int i = 0; i < n; i++) { + int id = sc.nextInt(); + int score = sc.nextInt(); + if (map.containsKey(id)) { + map.put(id, map.get(id) + score); + } else { + map.put(id, score); + } + } + + List> list = new ArrayList<>(map.entrySet()); + // 按照value排序 + Collections.sort(list, (o1, o2) -> { + return o2.getValue() - o1.getValue(); + }); + for (int i = 0; i < m; i++) { + System.out.println(list.get(i).getKey()); + } + + } + +} class TreeNode { int val; @@ -39,3 +75,9 @@ public static void printListNode(ListNode node) { System.out.println(s+"null"); } } + + + + + + diff --git a/Interview/src/Solution.java b/Interview/src/Solution.java index 852d7050..e69de29b 100644 --- a/Interview/src/Solution.java +++ b/Interview/src/Solution.java @@ -1,3 +0,0 @@ -public class Solution { - -} diff --git a/README.md b/README.md index fa3b931f..ba0e47b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # JavaBooks ## 引言 > - [个人博客](http://dreamcat.ink/) -> - [在线阅读](https://dreamcater.gitee.io/javabooks/) +> - [在线阅读](http://dsystem.dreamcat.ink/#/) > - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) ## 常用网站 diff --git a/SwordOffer/.DS_Store b/SwordOffer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..42e90748dae01042ec0c1410cbb07359e98c4119 GIT binary patch literal 6148 zcmeHK-A)rh6g~r0TM$|Pk!Z59F);xXFh=x(uojG>7gU#ED`44ehq7_I)9h}kP;2@C z##@cg-~)K&o$(P&yzvFR@XBk?{FEQ7TpFWul9}(!`J0_@vokvYKr*$`41fUuNo*Vw zJ=pz1xSzKU=~9+%M8a!CRo7=Ojy}58j%aPr3}^=aHU{{&+YJ$fP;~2uzhB>r;_2y6 zq^mn|;^e7hDwRz2r8ewsR<~m(u0dn)8Y*u)RH+i|NytD+Oclr?>~S^!UKEvRlQN z;RA!HbvvXqSSyEKX?bfaqH(TZ^hd>z`F@r~UhJ_Tf5@9(kC5-Bm{yj8Erv7hAiv91 z9u}?AFhcCe&*hykbb>O?>~8puOa0{_!{V4#cqFfe3(1C*R}wSXRj23$<@EeF$zj^c zwv|g#F(54!fVB0ZCG@00RsyF&Q>nCdPkECt2{+*`*su+CcnNRdExdyd@EN|scak8Z zYeV`Vc@f%jFxuv(O^9~mH|at zviI01?bHlt2L49|`2JvG<7g>tNfcWLcH#pDg)NEbK_M~~QKk}g z#UL^r?WT&e6t*PFbRg>TLDbAd-B5^_9qXGS9Ec@R_nHCCz)=SHzdwq>&;MJ$zyFIM z-Dw6i1OFxiB(Z2OW^irlZ0%SjKWj~Fo7lMUxRykbf}J{!g~E^Gacn}+XL1G6QrMD+ R78Ly>AZXB?X5f!9@B;?i%%T7Q literal 0 HcmV?d00001 From 90bfa80003d54dc61a0a718da6db73aeeca001a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Tue, 29 Dec 2020 22:23:29 +0800 Subject: [PATCH 058/366] delete other --- java_pid39413.hprof | Bin 16921133 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 java_pid39413.hprof diff --git a/java_pid39413.hprof b/java_pid39413.hprof deleted file mode 100644 index a656d1bdef5513049161cd6e933832bb7c461823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16921133 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

vo)_C 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

ffW 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 j+ii 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 jI7Kq46Fc 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 zngrczd|VSGr6dKpf#bVE5(O`OqH$eXDPNb+%;K`k_?r1@!(~#MIi~?5V*1*mO3qDc zCcHCwO-&`&gcJWuE2I{-t|or{E2Xh9GcYD*z6>j4(5d2>#CHA5)iLqr%X7!V%*gyX z9h#B1!j6}^_=rYE;s_D5(D)R6W@Gh2&uZX=RBGHZaPh%+z0(D8_@yAT+--xAM+%~C zP33*;+e{wxv3NWp`>xH@&zmWoKcO8EA;#16Ym^{>jH;=EtzH4tr>_@AN$@VUbQALu64VS@p&&IxM zZ`N#H8w)e_?~RR_yZkZn=B{O9V&=|OV`Aoe^Tx(ZvO6=op!%fy7vsOgP1;^pn-c$# z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!) z36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@ zkN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg z011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kN^pg011!)36KB@kih@P4jl*p z00000`M)+of&&K*95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) 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 z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oU)H*`Z?_06-W9f~JBpJFpNNvl{7H;VMfnS4V&V z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1b%_tY2Hfpj+|uuAB6w`0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfWTvi4g>%I004meUmGF8fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! 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 Vnlr6=GNopZ!8rIE^J=CH{~O|rnaThF From c38a0c96824ee038854219d22bd16b04c7e6d59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Thu, 31 Dec 2020 16:15:52 +0800 Subject: [PATCH 059/366] update readme.md --- README.md | 47 +---------------------------------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/README.md b/README.md index ba0e47b5..6f192535 100644 --- a/README.md +++ b/README.md @@ -43,52 +43,7 @@ ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) - -- [hashcode、equals](/Interview/sad/hashcode、equals.md) -- [谈谈异常机制](/Interview/sad/谈谈异常机制.md) -- [谈谈反射机制](/Interview/sad/谈谈反射机制.md) -- [谈谈多态](/Interview/sad/谈谈多态.md) -- [谈谈String](/Interview/sad/谈谈String.md) -- [谈谈Java值传递](/Interview/sad/谈谈Java值传递.md) -- [谈谈集合](/Interview/sad/谈谈集合.md) -- [为什么增强for删除元素会异常](https://juejin.im/post/6844903794795347981) -- [线程与进程的区别](/Interview/sad/线程与进程的区别.md) -- [线程的创建方式](/Interview/sad/线程的创建方式.md) -- [谈谈线程池](/Interview/sad/谈谈线程池.md) -- [谈谈volatile](/Interview/sad/谈谈volatile.md) -- [谈谈synchronized](/Interview/sad/谈谈synchronized.md) -- [谈谈CAS](/Interview/sad/谈谈CAS.md) -- [谈谈ThreadLocal](/Interview/sad/谈谈ThreadLocal.md) -- [谈谈AQS](/Interview/sad/谈谈AQS.md) -- [谈谈ReentrantLock](/Interview/sad/谈谈ReentrantLock.md) -- [谈谈ReentrantReadWriteLock](/Interview/sad/谈谈ReentrantReadWriteLock.md) -- [谈谈CountDownLatch](/Interview/sad/谈谈CountDownLatch.md) -- [谈谈死锁](/Interview/sad/谈谈死锁.md) -- [生产者消费者模型](/Interview/sad/生产者消费者模型.md) -- [类文件结构](/Interview/sad/类文件结构.md) -- [类加载过程](/Interview/sad/类加载过程.md) -- [类加载器](/Interview/sad/类加载器.md) -- [JVM内存区域](/Interview/sad/JVM内存区域.md) -- [逃逸分析](/Interview/sad/逃逸分析.md) -- [对象的创建过程](/Interview/sad/对象的创建过程.md) -- [垃圾回收](/Interview/sad/垃圾回收.md) -- [InnoDB和MyISAM](/Interview/sad/InnoDB和MyISAM.md) -- [MySQL的ACID和隔离级别](/Interview/sad/MySQL的ACID和隔离级别.md) -- [MVCC,redolog,undolog,binlog](/Interview/sad/MVCC,redolog,undolog,binlog.md) -- [MySQL索引](/Interview/sad/MySQL索引.md) -- [MySQL数据库结构优化](/Interview/sad/MySQL数据库结构优化.md) -- [MySQL的锁](/Interview/sad/MySQL的锁.md) -- [Redis的模型](/Interview/sad/Redis的模型.md) -- [Redis数据结构](/Interview/sad/Redis数据结构.md) -- [Redis持久化](/Interview/sad/Redis持久化.md) -- [Redis内存淘汰机制](/Interview/sad/Redis内存淘汰机制.md) -- [Redis缓存穿透和雪崩](/Interview/sad/Redis缓存穿透和雪崩.md) -- [Redis与MySQL双写一致性方案](https://www.cnblogs.com/rjzheng/p/9041659.html) -- [Redis并发竞争key的解决方案详解](https://juejin.im/post/6844903846750191630) -- [Redis分布式锁](/Interview/sad/Redis分布式锁.md) -- [补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) -- [数据库实现分布式锁](https://blog.csdn.net/u013256816/article/details/92854794) -- [CAP和BASE](/Interview/sad/CAP和BASE.md) +- [test](/Interview/classify/README.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) From 86e506ca6633a714b909ce47ac4e83c3788d5f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Thu, 31 Dec 2020 16:17:41 +0800 Subject: [PATCH 060/366] update classify --- .DS_Store | Bin 10244 -> 10244 bytes Interview/.DS_Store | Bin 10244 -> 12292 bytes Interview/classify/.DS_Store | Bin 0 -> 14340 bytes Interview/classify/README.md | 101 +++++++ ...\343\200\201hashcode\345\222\214equals.md" | 24 +- Interview/classify/basis/IO.md | 11 + Interview/classify/basis/Object.md | 13 + .../classify/basis/String.md | 54 ++-- Interview/classify/basis/final.md | 22 ++ Interview/classify/basis/static.md | 5 + .../\345\200\274\344\274\240\351\200\222.md" | 22 +- ...15\345\260\204\346\234\272\345\210\266.md" | 47 +++- ...72\346\234\254\347\261\273\345\236\213.md" | 57 ++++ ...71\350\261\241\347\211\271\346\200\247.md" | 255 ++++++++++++++++++ .../\345\272\217\345\210\227\345\214\226.md" | 14 + ...02\345\270\270\344\275\223\347\263\273.md" | 31 ++- .../basis/\346\263\233\345\236\213.md" | 13 + ...61\346\265\205\346\213\267\350\264\235.md" | 9 + ...10\350\260\210\351\233\206\345\220\210.md" | 30 ++- .../classify/dis/CAP\345\222\214BASE.md" | 9 +- Interview/classify/dis/Dubbo.md | 133 +++++++++ Interview/classify/dis/RocketMQ.md | 169 ++++++++++++ Interview/classify/dis/Sentinel.md | 35 +++ Interview/classify/dis/zookeeper.md | 40 +++ ...64\346\200\247\347\256\227\346\263\225.md" | 39 +++ ...03\345\274\217\344\272\213\345\212\241.md" | 74 +++++ ...03\345\274\217\346\246\202\345\277\265.md" | 9 + ...06\350\277\207\346\273\244\345\231\250.md" | 7 + ...20\346\265\201\347\256\227\346\263\225.md" | 34 +++ ...05\345\255\230\345\214\272\345\237\237.md" | 28 +- ...03\345\234\276\345\233\236\346\224\266.md" | 14 +- ...33\345\273\272\350\277\207\347\250\213.md" | 38 +-- ...73\345\212\240\350\275\275\345\231\250.md" | 7 +- ...40\350\275\275\350\277\207\347\250\213.md" | 14 +- ...07\344\273\266\347\273\223\346\236\204.md" | 20 +- ...03\351\200\270\345\210\206\346\236\220.md" | 2 + .../classify/mysql/ACID.md | 33 ++- .../mysql/Innodb\344\270\216MyISAM.md" | 18 +- ...23\346\236\204\344\274\230\345\214\226.md" | 6 +- ...45\345\277\227\346\226\207\344\273\266.md" | 4 +- ...344\270\200\346\235\241SQL\347\232\204.md" | 20 ++ .../mysql/MySQL\347\232\204\351\224\201.md" | 8 + .../mysql/\347\264\242\345\274\225.md" | 23 +- Interview/classify/net/DNS.md | 42 +++ .../classify/net/HTTP\345\222\214HTTPS.md" | 212 +++++++++++++++ .../classify/net/TCP\345\222\214UDP.md" | 152 +++++++++++ ...21\347\273\234\346\250\241\345\236\213.md" | 49 ++++ ...06\345\270\203\345\274\217\351\224\201.md" | 11 +- ...is\346\214\201\344\271\205\345\214\226.md" | 13 +- ...60\346\215\256\347\273\223\346\236\204.md" | 17 +- .../redis/Redis\346\250\241\345\236\213.md" | 7 + ...30\346\261\260\346\234\272\345\210\266.md" | 4 +- ...23\345\255\230\351\233\252\345\264\251.md" | 0 Interview/classify/spring/Bean.md | 49 ++++ .../classify/spring/Ioc\345\222\214AOP.md" | 59 ++++ Interview/classify/spring/SpringBoot.md | 49 ++++ Interview/classify/spring/SpringMVC.md | 13 + .../spring/Spring\344\272\213\345\212\241.md" | 48 ++++ ...41\347\220\206\346\226\271\345\274\217.md" | 22 ++ .../classify/sys/\346\255\273\351\224\201.md" | 20 ++ ...33\347\250\213\350\260\203\345\272\246.md" | 8 + ...32\346\213\237\345\206\205\345\255\230.md" | 13 + ...13\344\270\216\347\272\277\347\250\213.md" | 54 ++++ ...13\351\227\264\351\200\232\344\277\241.md" | 57 ++++ ...56\346\215\242\347\256\227\346\263\225.md" | 7 + .../classify/thread/AQS.md | 6 +- ...00\344\272\233\351\227\256\351\242\230.md" | 158 +++++++++++ .../classify/thread/CAS.md | 39 ++- .../classify/thread/CountDownLatch.md | 6 + ...05\345\255\230\346\250\241\345\236\213.md" | 3 + ...01\347\232\204\344\273\213\347\273\215.md" | 98 +++++++ .../classify/thread/ReentrantLock.md | 7 + .../classify/thread/ReentrantReadWriteLock.md | 6 + .../classify/thread/ThreadLocal.md | 6 + .../classify/thread/synchronized.md | 14 +- .../classify/thread/volatile.md | 18 +- .../thread/\346\255\273\351\224\201.md" | 9 +- ...14\346\266\210\350\264\271\350\200\205.md" | 1 - .../\347\272\277\347\250\213\346\261\240.md" | 9 +- ...33\345\273\272\346\226\271\345\274\217.md" | 173 ++++++++++++ ...13\345\222\214\347\272\277\347\250\213.md" | 44 ++- ...33\345\273\272\346\226\271\345\274\217.md" | 97 ------- ...10\350\260\210\345\244\232\346\200\201.md" | 114 -------- 83 files changed, 2853 insertions(+), 353 deletions(-) create mode 100644 Interview/classify/.DS_Store create mode 100644 Interview/classify/README.md rename "Interview/sad/hashcode\343\200\201equals.md" => "Interview/classify/basis/==\343\200\201hashcode\345\222\214equals.md" (77%) create mode 100644 Interview/classify/basis/IO.md create mode 100644 Interview/classify/basis/Object.md rename "Interview/sad/\350\260\210\350\260\210String.md" => Interview/classify/basis/String.md (55%) create mode 100644 Interview/classify/basis/final.md create mode 100644 Interview/classify/basis/static.md rename "Interview/sad/\350\260\210\350\260\210Java\345\200\274\344\274\240\351\200\222.md" => "Interview/classify/basis/\345\200\274\344\274\240\351\200\222.md" (82%) rename "Interview/sad/\350\260\210\350\260\210\345\217\215\345\260\204\346\234\272\345\210\266.md" => "Interview/classify/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" (50%) create mode 100644 "Interview/classify/basis/\345\237\272\346\234\254\347\261\273\345\236\213.md" create mode 100644 "Interview/classify/basis/\345\257\271\350\261\241\347\211\271\346\200\247.md" create mode 100644 "Interview/classify/basis/\345\272\217\345\210\227\345\214\226.md" rename "Interview/sad/\350\260\210\350\260\210\345\274\202\345\270\270\346\234\272\345\210\266.md" => "Interview/classify/basis/\345\274\202\345\270\270\344\275\223\347\263\273.md" (88%) create mode 100644 "Interview/classify/basis/\346\263\233\345\236\213.md" create mode 100644 "Interview/classify/basis/\346\267\261\346\265\205\346\213\267\350\264\235.md" rename "Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" => "Interview/classify/col/\350\260\210\350\260\210\351\233\206\345\220\210.md" (95%) rename "Interview/sad/CAP\345\222\214BASE.md" => "Interview/classify/dis/CAP\345\222\214BASE.md" (95%) create mode 100644 Interview/classify/dis/Dubbo.md create mode 100644 Interview/classify/dis/RocketMQ.md create mode 100644 Interview/classify/dis/Sentinel.md create mode 100644 Interview/classify/dis/zookeeper.md create mode 100644 "Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" create mode 100644 "Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" create mode 100644 "Interview/classify/dis/\345\210\206\345\270\203\345\274\217\346\246\202\345\277\265.md" create mode 100644 "Interview/classify/dis/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md" create mode 100644 "Interview/classify/dis/\351\231\220\346\265\201\347\256\227\346\263\225.md" rename "Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" => "Interview/classify/jvm/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" (80%) rename "Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" => "Interview/classify/jvm/\345\236\203\345\234\276\345\233\236\346\224\266.md" (96%) rename "Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" => "Interview/classify/jvm/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" (74%) rename "Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" => "Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" (97%) rename "Interview/sad/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" => "Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" (91%) rename "Interview/sad/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" => "Interview/classify/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" (94%) rename "Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" => "Interview/classify/jvm/\351\200\203\351\200\270\345\210\206\346\236\220.md" (99%) rename "Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" => Interview/classify/mysql/ACID.md (94%) rename "Interview/sad/InnoDB\345\222\214MyISAM.md" => "Interview/classify/mysql/Innodb\344\270\216MyISAM.md" (97%) rename "Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" => "Interview/classify/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" (99%) rename Interview/sad/MVCC,redolog,undolog,binlog.md => "Interview/classify/mysql/MySQL\346\227\245\345\277\227\346\226\207\344\273\266.md" (97%) create mode 100644 "Interview/classify/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\347\232\204.md" rename "Interview/sad/MySQL\347\232\204\351\224\201.md" => "Interview/classify/mysql/MySQL\347\232\204\351\224\201.md" (99%) rename "Interview/sad/MySQL\347\264\242\345\274\225.md" => "Interview/classify/mysql/\347\264\242\345\274\225.md" (97%) create mode 100644 Interview/classify/net/DNS.md create mode 100644 "Interview/classify/net/HTTP\345\222\214HTTPS.md" create mode 100644 "Interview/classify/net/TCP\345\222\214UDP.md" create mode 100644 "Interview/classify/net/\347\275\221\347\273\234\346\250\241\345\236\213.md" rename "Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" => "Interview/classify/redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" (91%) rename "Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" => "Interview/classify/redis/Redis\346\214\201\344\271\205\345\214\226.md" (94%) rename "Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" => "Interview/classify/redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" (98%) rename "Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" => "Interview/classify/redis/Redis\346\250\241\345\236\213.md" (99%) rename "Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" => "Interview/classify/redis/\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" (95%) rename "Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" => "Interview/classify/redis/\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\347\274\223\345\255\230\351\233\252\345\264\251.md" (100%) create mode 100644 Interview/classify/spring/Bean.md create mode 100644 "Interview/classify/spring/Ioc\345\222\214AOP.md" create mode 100644 Interview/classify/spring/SpringBoot.md create mode 100644 Interview/classify/spring/SpringMVC.md create mode 100644 "Interview/classify/spring/Spring\344\272\213\345\212\241.md" create mode 100644 "Interview/classify/sys/\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\256\241\347\220\206\346\226\271\345\274\217.md" create mode 100644 "Interview/classify/sys/\346\255\273\351\224\201.md" create mode 100644 "Interview/classify/sys/\347\263\273\347\273\237\350\277\233\347\250\213\350\260\203\345\272\246.md" create mode 100644 "Interview/classify/sys/\350\231\232\346\213\237\345\206\205\345\255\230.md" create mode 100644 "Interview/classify/sys/\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" create mode 100644 "Interview/classify/sys/\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241.md" create mode 100644 "Interview/classify/sys/\351\241\265\351\235\242\347\275\256\346\215\242\347\256\227\346\263\225.md" rename "Interview/sad/\350\260\210\350\260\210AQS.md" => Interview/classify/thread/AQS.md (98%) create mode 100644 "Interview/classify/thread/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" rename "Interview/sad/\350\260\210\350\260\210CAS.md" => Interview/classify/thread/CAS.md (64%) rename "Interview/sad/\350\260\210\350\260\210CountDownLatch.md" => Interview/classify/thread/CountDownLatch.md (97%) create mode 100755 "Interview/classify/thread/JMM\345\206\205\345\255\230\346\250\241\345\236\213.md" create mode 100644 "Interview/classify/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" rename "Interview/sad/\350\260\210\350\260\210ReentrantLock.md" => Interview/classify/thread/ReentrantLock.md (98%) rename "Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" => Interview/classify/thread/ReentrantReadWriteLock.md (99%) rename "Interview/sad/\350\260\210\350\260\210ThreadLocal.md" => Interview/classify/thread/ThreadLocal.md (98%) rename "Interview/sad/\350\260\210\350\260\210synchronized.md" => Interview/classify/thread/synchronized.md (86%) rename "Interview/sad/\350\260\210\350\260\210volatile.md" => Interview/classify/thread/volatile.md (73%) rename "Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" => "Interview/classify/thread/\346\255\273\351\224\201.md" (96%) rename "Interview/sad/\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" => "Interview/classify/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205.md" (99%) rename "Interview/sad/\350\260\210\350\260\210\347\272\277\347\250\213\346\261\240.md" => "Interview/classify/thread/\347\272\277\347\250\213\346\261\240.md" (97%) create mode 100644 "Interview/classify/thread/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" rename "Interview/sad/\347\272\277\347\250\213\344\270\216\350\277\233\347\250\213\347\232\204\345\214\272\345\210\253.md" => "Interview/classify/thread/\350\277\233\347\250\213\345\222\214\347\272\277\347\250\213.md" (63%) delete mode 100644 "Interview/sad/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" delete mode 100644 "Interview/sad/\350\260\210\350\260\210\345\244\232\346\200\201.md" diff --git a/.DS_Store b/.DS_Store index a5169ff3914d5f81ddb3cc38f41f55db0c9f47f6..260490edf2bf23fae17f0a7331ba596189f3efb2 100644 GIT binary patch delta 20 bcmZn(XbIThD8R@$Sx+p3k#qBEfk<%xLYM{H delta 16 XcmZn(XbIThC@@)9tYGs-fnad}H1Gw^ diff --git a/Interview/.DS_Store b/Interview/.DS_Store index 778c0b98f573629955df5ed0af7e29330ddab807..081cf2111744e22a65fdd6245affc1815f8fe023 100644 GIT binary patch delta 373 zcmZn(Xi1P~U|?W$DortDV9)?EIe-{M3-ADifgFX2@&=+}Kmlf;7z0BhLlr}1Qh9Mf zQqE*Srp1#DR9ILUfwC-<_sU34=8+O%sEzGWxI;lw{`T6>t8@D8$FeyZMal zE5^yfjL#+uFp6we5O~DM%g&I?P|1)4G_`~wbMk*lMP%JVKx0u9Fcfe8EGWUYS%FEL zWiz+LYeqp1hGd2upzdNI%w$MosGQ6wC68=|2+#~nMG69@oB8E>vFYC^BRu(+q#Cjr zo3BYdXPmrWB3lM%7%LD1fd)5_a779w?#+Ts@0lm_tN5}2gM(voipoqD4o*&_z&;S4c delta 230 zcmZokXbF&KU|?W$DortDU{C-uIe-{M3vdI4fgFj6@&=-UKmlf;7z0BhLmopGLne?+ zpDf6G3o9c~mSys1!IztVG79lc)MMNHQ0OD$=If%IY@4|iUNcUi10`93<^q8N&?OA6 iNG{^uEXeepc{0C_F9#FE!5WhjbY@N#5o^b0kRJe!9Wyro diff --git a/Interview/classify/.DS_Store b/Interview/classify/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..45a288f12b95dda9fcbc6bd416f840719cd323ce GIT binary patch literal 14340 zcmeHN&2Jk;6n_)Psof8Ja zNfVPQC&UFIZhVNpfdfY_2nh)e)1 zBiP0CN3kZM0eh;xTW4H9$mJfAk;G_n>{u$DPNh$!H%eP{w-g{(7wrM<0qp_p0qp_p zf&cFT_U*-zIw7cjY7b}+Xb*Hez}ANhRz~xZ9tz5r4t%gJJma%1JTKVBa{%wlNSc@Q zP*ARlI$Ra0P$lw+L4-Q?XP7wByrhSM3Uwet9f*80ktYXb(g^ z@Emp}zy%9}%C6t6IJAO2KUSKK-T&-u2HSqY^w!x)yl4hn71>^<)akU%%8G66mW|1I z>ISCmS{^^R+cgVCZog%_HM(1%+iuP0OsNxz#Tw~y9N$}9gHL$@SR!1u)+SMHyOXTZzEd5~^otvEF zCr+MvYTTGOeP&{EVrpvY?76A))6>tCjWZRqdbjSexR|F7^$O0mwO;q^TB&JPZMVJ} zSoeaGy>FF`(;?AYR_&c`*YaKyWhT2Fe7M&<%X0a6OPia%6|9zxalh(O$0<_Z4s7bK zDcrT&K1TRrU^N!qO^R>a!H8aJGBJO}@_eKb{na(o^GvsH74|k9vtl_*Zh-~?Z7@kT zT9vv*&tfC3+Sq2cWUv1%qB5Jm*>1cj8U|!V1NZ3$S$55al}=~#McJ8xi*N(pfEBn4 z`|t^T317iC@B=)8zu;eTj7*c~$whLN%#rKlb+Slqk|KGN+!ER&ZPYy5eubc99jP*z zG00K4DtbPhXnpihpr=Qoi=cRk7!)b`Ata6AHDMFcWaF|`A66{&7^%@T=xFMh&KNxP z@Kr?B9?ahVMIb69)5TOjnHW^{j1j_B0&#^`@WYBIX;? zqI%vF!d34MM*q?t&>naa4?K-RNvME{AOE2~ns!Xo-=jhQ9x3q}`U)hV3KX1S-B(HT zbOaB21j~txMDI{v4<^Lzr;gsik6s?9F(&~z#p)U0pYP`${n|cBb3JwUALYjE`{k$+ zw7d&D&=}seo|~u9GWclW6B&yZ&Qa0wMZtA`ZlgtVyBB>?ZY)~3UI;Bpe(kU#Mln&N zX3$ZiOlAyn6t0S%vmeeL@BI6zlTWUGZ{j6lQ1m2c=RucA3IlR1|L@iQ`Tv7%rvCf2 z2ebzs;{h_dP+BOUKsz5GEX#83GFb0mWisDlWd++@1s^;e4=GQ_!yhai?`19|AIQtI fCelMev4!Ob5&^UMqfV~S=YN)Frc;BCFg55usi8O1 literal 0 HcmV?d00001 diff --git a/Interview/classify/README.md b/Interview/classify/README.md new file mode 100644 index 00000000..223b554e --- /dev/null +++ b/Interview/classify/README.md @@ -0,0 +1,101 @@ +![个人体系v1](https://gitee.com/dreamcater/blog-img/raw/master/uPic/个人体系v1-Nkkq8b.png) + +## Java基础 +- [基本类型](basis/基本类型.md) +- [==、hashcode和equals](basis/==、hashcode和equals.md) +- [String](basis/String.md) +- [final](basis/final.md) +- [对象特性](basis/对象特性.md) +- [Object](basis/Object.md) +- [异常体系](basis/异常体系.md) +- [值传递](basis/值传递.md) +- [深浅拷贝](basis/深浅拷贝.md) +- [反射机制](basis/反射机制.md) +- [static](basis/static.md) +- [IO](basis/IO.md) +- [泛型](basis/泛型.md) +- [序列化](basis/序列化.md) + +## Java集合 +- [谈谈集合](col/谈谈集合.md) + +## Java多线程 +- [进程与线程](thread/进程与线程.md) +- [线程的创建方式](thread/线程的创建方式.md) +- [死锁](thread/死锁.md) +- [JMM内存模型](thread/JMM内存模型) +- [volatile](thread/volatile.md) +- [CAS](thread/CAS.md) +- [ThreadLocal](thread/ThreadLocal.md) +- [synchronized](thread/synchronized.md) +- [AQS](thread/AQS.md) +- [ReentrantLock](thread/ReentrantLock.md) +- [ReentrantReadWriteLock](thread/ReentrantReadWriteLock.md) +- [CountDownLatch](thread/CountDownLatch.md) +- [Java的锁](thread/Java锁的介绍.md) +- [生产者和消费者](thread/生产者和消费者.md) +- [线程池](thread/线程池.md) +- [BlockingQueue的一些问题](thread/BlockingQueue的一些问题.md) + +## Jvm +- [类文件结构](jvm/类文件结构.md) +- [类加载过程](jvm/类加载过程.md) +- [类加载器](jvm/类加载器.md) +- [JVM内存区域](jvm/JVM内存区域.md) +- [对象的创建过程](jvm/对象的创建过程.md) +- [垃圾回收](jvm/垃圾回收.md) +- [逃逸分析](jvm/逃逸分析.md) + +## Spring +- [Ioc和AOP](spring/Ioc和AOP.md) +- [Bean](spring/Bean.md) +- [Spring事务](spring/Spring事务.md) +- [SpringMVC](spring/SpringMVC.md) +- [SpringBoot](spring/SpringBoot.md) + +## 数据库 +- [MySQL是如何执行一条SQL的](mysql/MySQL是如何执行一条SQL的.md) +- [Innodb与MyISAM](mysql/Innodb与MyISAM.md) +- [ACID](mysql/ACID.md) +- [MySQL日志文件](mysql/MySQL日志文件.md) +- [索引](mysql/索引.md) +- [MySQL数据库结构优化](mysql/MySQL数据库结构优化.md) +- [MySQL的锁](mysql/MySQL的锁.md) +- [Redis模型](redis/Redis模型.md) +- [Redis数据结构](redis/Redis数据结构.md) +- [Redis持久化](redis/Redis持久化.md) +- [内存淘汰机制](redis/内存淘汰机制.md) +- [缓存穿透和缓存雪崩](redis/缓存穿透和缓存雪崩.md) +- [双写一致性](https://www.cnblogs.com/rjzheng/p/9041659.html) +- [并发竞争key](https://juejin.cn/post/6844903846750191630) +- [Redis分布式锁](redis/Redis分布式锁.md) + +## 分布式 +- [分布式概念](dis/分布式概念.md) +- [CAP和BASE](dis/CAP和BASE.md) +- [分布式一致性算法](dis/分布式一致性算法.md) +- [限流算法](dis/限流算法.md) +- [分布式事务](dis/分布式事务.md) +- [布隆过滤器](dis/布隆过滤器.md) +- [Dubbo](dis/Dubbo.md) +- [RocketMQ](dis/RocketMQ.md) +- [zookeeper](dis/zookeeper.md) +- [Sentinel](dis/Sentinel.md) + +## 计网 +- [网络模型](net/网络模型.md) +- [DNS](net/DNS.md) +- [HTTP和HTTPS](net/HTTP和HTTPS.md) +- [TCP和UDP](net/TCP和UDP.md) + +## 操作系统 +- [进程与线程](sys/进程与线程.md) +- [进程间通信](sys/进程间通信.md) +- [系统进程调度](sys/系统进程调度.md) +- [虚拟内存](sys/虚拟内存.md) +- [操作系统内存管理方式](sys/操作系统内存管理方式.md) +- [页面置换算法](sys/页面置换算法.md) +- [死锁](sys/死锁.md) + +## 其他 + diff --git "a/Interview/sad/hashcode\343\200\201equals.md" "b/Interview/classify/basis/==\343\200\201hashcode\345\222\214equals.md" similarity index 77% rename from "Interview/sad/hashcode\343\200\201equals.md" rename to "Interview/classify/basis/==\343\200\201hashcode\345\222\214equals.md" index 5db3bd37..9cca85c3 100644 --- "a/Interview/sad/hashcode\343\200\201equals.md" +++ "b/Interview/classify/basis/==\343\200\201hashcode\345\222\214equals.md" @@ -1,4 +1,8 @@ -> 我感觉这问题被问的频率有点高 + +# ==、hashcode和equals +> 我感觉这问题被问的频率有点高, 那是因为考的知识点比较多 + +## == 面试官:出个题: @@ -10,6 +14,8 @@ System.out.println(a == b); // false 我:false,太简单了嘛。 == 比较的是两个对象的地址哦,你看: +### 概念及作用 + 它的作用是**判断两个对象的地址是不是相等**。即,判断两个对象是不是同一个对象: - 基本数据类型==比较的是**值** @@ -25,6 +31,7 @@ int a = 5; Integer b = new Integer(5); System.out.println(a == b); // true ``` +### 装箱与拆箱 我:你想考我装箱拆箱嘛?其实,b看到了对面那个哥们是**基本类型**,我也是基本类型包装的呀,我把衣服一脱,那么就是**基本类型**了,那就是上面的结论了。b拆箱即可。 @@ -63,14 +70,17 @@ public int intValue() { } ``` +## equals + 面试官:聊一下equals +### 概念及原理 我:equals是顶层父类Object的方法之一 ```java // 你看,object默认调用的== , 你对象如果不重写,可能会发上重大事故 public boolean equals(Object obj) { - return (this == obj); + return (this == obj); // 比较对象的地址值 } ``` @@ -78,10 +88,10 @@ public boolean equals(Object obj) { ```java // Returns a hash code value for the object. -// 说白了,返回的就是该对象的地址值 +// 说白了,调用本地方法返回的就是该对象的地址值 public native int hashCode(); ``` - +### 作用 Equals的作用也是判断两个对象是否相等。但它一般有两种使用情况: - 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过`==`比较这两个对象。 @@ -107,6 +117,8 @@ public boolean equals(Object obj) { } ``` +## hashcode + hashcode的例子: ```java @@ -117,5 +129,5 @@ public static void main(String[] args) { System.out.println(set.toString()); } ``` - -在添加1的时候,先判断hashcode是否相同,如果相同,继续判断equals比较值,这样的好处是,如果hashcode相同就直接返回false了,减少了一次equals的判断,因为通常hashcode的值判断是否相等比较快,而equals相对于hashcode来讲慢一些。所以,如果不重写hashcode,我们看到object的hashcode是对象的内存值,那么set添加1判断的时候,hashcode永远不相等,那么就永远返回false,不管添加1,还是2,都是false。 \ No newline at end of file +**解释:** +在添加1的时候,先判断**hashcode是否相同**,如果相同,**继续判断equals比较值**,这样的好处是,**如果hashcode相同就直接返回false了,减少了一次equals的判断,因为通常hashcode的值判断是否相等比较快,而equals相对于hashcode来讲慢一些**。所以,如果不重写hashcode,我们看到object的hashcode是对象的内存值,那么set添加1判断的时候,hashcode永远不相等,那么就永远返回false,不管添加1,还是2,都是false。 \ No newline at end of file diff --git a/Interview/classify/basis/IO.md b/Interview/classify/basis/IO.md new file mode 100644 index 00000000..bcaf5d5d --- /dev/null +++ b/Interview/classify/basis/IO.md @@ -0,0 +1,11 @@ +# IO +## BIO +**BIO (Blocking I/O)**:**同步阻塞I/O模式**,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 + +## NIO +**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 的非阻塞模式来开发。 + +[NIO底层原理](https://blog.csdn.net/u013857458/article/details/82424104) + +## AIO +**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,不过又放弃了。 \ No newline at end of file diff --git a/Interview/classify/basis/Object.md b/Interview/classify/basis/Object.md new file mode 100644 index 00000000..fb363d4a --- /dev/null +++ b/Interview/classify/basis/Object.md @@ -0,0 +1,13 @@ +# Object + +```java +public final native Class getClass(); +public native int hashCode(); // 返回对象的哈希代码值。 +public boolean equals(Object obj) +protected native Object clone() // 创建并返回此对象的副本。 +public String toString() // 返回对象的字符串表示形式。 +public final native void notify(); // 唤醒正在该对象的监视器上等待的单个线程。 +public final native void notifyAll(); // 唤醒正在该对象的监视器上等待的全部线程。 +public final native void wait(); // 使当前线程等待,直到另一个线程调用此对象的方法或方法。 +protected void finalize(); // 当垃圾回收确定不再有对对象的引用时,由对象上的垃圾回收器调用。 +``` \ No newline at end of file diff --git "a/Interview/sad/\350\260\210\350\260\210String.md" b/Interview/classify/basis/String.md similarity index 55% rename from "Interview/sad/\350\260\210\350\260\210String.md" rename to Interview/classify/basis/String.md index 1aec46ae..f6a127db 100644 --- "a/Interview/sad/\350\260\210\350\260\210String.md" +++ b/Interview/classify/basis/String.md @@ -1,6 +1,6 @@ -> 难道面试官想考String的什么? - +# String +> 难道面试官想考String的什么? ## String、StringBuffer和StringBuilder的区别 @@ -8,27 +8,25 @@ 我:其实总结说一下就行,三点不同 -1. 可变性 +### 可变性 -- 简单的来说:`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 `String` 对象是不可变的。 +- 简单的来说:`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 `String` 对象是不可变的。(还不是为了线程安全和JVM缓存速度问题) - `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串char[]value 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。 -2. 线程安全 - -\- `String` 中的对象是不可变的,也就可以理解为常量,线程安全。 +### 线程安全 -\- `AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。 +- `String` 中的对象是不可变的,也就可以理解为常量,线程安全。 -3. 性能 +- `AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。 -\- 每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。 +### 性能 -\- `StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 +- 每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。(人家目的是不可变,你一直让人家干可变的事情,那岂不是很难受?) +- `StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - -总结: +### 总结 - 操作少量的数据: 适用String @@ -36,6 +34,8 @@ - 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer +> 注意:这里可能要问final关键字来解释一波String的char前面所加的final的好处 + ## final关键字 面试官:有木有想过为什么String的char前面加了final,有什么好处? @@ -51,7 +51,7 @@ final关键字主要用在三个地方:变量、方法、类。 - 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 -final修饰有啥好处 +**final修饰有啥好处**:(面试官想听这三点) - final的关键字**提高了性能**,JVM和java应用会**缓存final变量**; @@ -59,7 +59,28 @@ final修饰有啥好处 - 使用final的关键字提高了性能,JVM会对方法变量类进行优化; - +> 这里可能让解释String对象和JVM的常量池 + +## String对象和常量池 + +```java +public class StringTest { + public static void main(String[] args) { + String str1 = "todo"; // 常量池 + String str2 = "todo"; // 从常量池找了str1 + String str3 = "to"; // 常量池 + String str4 = "do"; // 常量池 + String str5 = str3 + str4; // 内部用StringBuilder拼接了一波。 因此, 并非常量池 + String str6 = new String(str1); // 创建对象了, 那还能是常量池的引用? + } +} +``` + +分析一波: +- 成的class文件中会在常量池中**保存“todo”、“to”和“do”三个String常量**。 +- 变量str1和str2均保存的是常量池中“todo”的引用,所以str1==str2成立; +- 在执行 str5 = str3 + str4这句时,**JVM会先创建一个StringBuilder对象,通过StringBuilder.append()方法将str3与str4的值拼接**,然后通过StringBuilder.toString()返回一个堆中的String对象的引用,赋值给str5,因此str1和str5指向的不是同一个String对象,str1 == str5不成立; +- String str6 = new String(str1)一句显式创建了一个新的String对象,因此str1 == str6不成立便是显而易见的事了。 ## 了解String.intern()方法吗? @@ -92,6 +113,7 @@ final修饰有啥好处 ### 1.8 - 静态常量池在Class文件中。 -- **JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池**。**同时永久代被移除,以元空间代替**。元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。其主要用于存放一些元数据(类信息)。 +- **JVM已经将运行时常量池从方法区中移了出来,在Java堆(Heap)中开辟了一块区域存放运行时常量池**。**同时永久代被移除,以元空间代替**。元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。其主要用于存放一些元数据(类信息)。 - **字符串常量池存在于Java堆中**。 + diff --git a/Interview/classify/basis/final.md b/Interview/classify/basis/final.md new file mode 100644 index 00000000..b7dc3085 --- /dev/null +++ b/Interview/classify/basis/final.md @@ -0,0 +1,22 @@ +# final + +面试官:有木有想过为什么String的char前面加了final,有什么好处? + +我:答这个问题,你要先说final是干啥的 + +final关键字主要用在三个地方:变量、方法、类。 + + +- 对于一个final变量,如果是**基本数据类型的变量,则其数值一旦在初始化之后便不能更改**;如果是引用类型的变量,则在对其初始化之后便**不能再让其指向另一个对象**。 + +- 当用final修饰一个类时,表明**这个类不能被继承**。final类中的所有成员方法都会被隐式地指定为final方法。 + +- 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 + +**final修饰有啥好处**:(面试官想听这三点) + +- final的关键字**提高了性能**,JVM和java应用会**缓存final变量**; + +- final变量可以在多线程环境下保持**线程安全**; + +- 使用final的关键字提高了性能,JVM会对方法变量类进行优化; \ No newline at end of file diff --git a/Interview/classify/basis/static.md b/Interview/classify/basis/static.md new file mode 100644 index 00000000..f91e22b2 --- /dev/null +++ b/Interview/classify/basis/static.md @@ -0,0 +1,5 @@ +# static +- **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` +- **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. +- **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 +- **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 \ No newline at end of file diff --git "a/Interview/sad/\350\260\210\350\260\210Java\345\200\274\344\274\240\351\200\222.md" "b/Interview/classify/basis/\345\200\274\344\274\240\351\200\222.md" similarity index 82% rename from "Interview/sad/\350\260\210\350\260\210Java\345\200\274\344\274\240\351\200\222.md" rename to "Interview/classify/basis/\345\200\274\344\274\240\351\200\222.md" index a83cc6a7..1e9630fe 100644 --- "a/Interview/sad/\350\260\210\350\260\210Java\345\200\274\344\274\240\351\200\222.md" +++ "b/Interview/classify/basis/\345\200\274\344\274\240\351\200\222.md" @@ -1,4 +1,7 @@ -> Java值传递,举例子比较合适 + +# 值传递 + +> Java值传递,举例子比较合适,总之,拷贝,拷来烤去的。 面试官:了解Java值传递嘛? @@ -10,6 +13,8 @@ - 一个方法**不能让对象参数引用一个新的对象**。 +## 基本类型 + 所谓第一个,举例子先: ```java @@ -38,9 +43,11 @@ public static void swap(int a, int b) { ``` -![](http://media.dreamcat.ink/uPic/%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E4%BC%A0%E9%80%92%20.png) +![基本类型传递](https://gitee.com/dreamcater/blog-img/raw/master/uPic/基本类型传递-QPPzXh.png) -> 在 swap 方法中,**a、b 的值进行交换,并不会影响到 num1、num2**。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,**a、b 相当于 num1、num2 的副本**,副本的内容无论怎么修改,都不会影响到原件本身。 +> 在 swap 方法中,**a、b 的值进行交换,并不会影响到 num1、num2**。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,**a、b 相当于 num1、num2 的副本**,副本的内容无论怎么修改,都不会影响到原件本身。 说白了,就是深拷贝 + +## 数组类型传递 所谓第二个,举例子先: @@ -61,9 +68,11 @@ public static void swap(int a, int b) { // 0 ``` -![](http://media.dreamcat.ink/uPic/%E6%95%B0%E7%BB%84%E7%B1%BB%E5%9E%8B%E4%BC%A0%E9%80%92.png) +![数组类型传递](https://gitee.com/dreamcater/blog-img/raw/master/uPic/数组类型传递-fxStps.png) + +> array 被初始化 arr 的拷贝也就是**一个对象的引用**,也就是说 array 和 arr 指向的是**同一个数组对象**。 因此,外部对引用对象的改变会反映到所对应的对象上。说白了,浅拷贝 -> array 被初始化 arr 的拷贝也就是**一个对象的引用**,也就是说 array 和 arr 指向的是**同一个数组对象**。 因此,外部对引用对象的改变会反映到所对应的对象上。 +## 对象引用 所谓第三个,举例子先: @@ -93,5 +102,4 @@ public static void swap(int a, int b) { ``` -![](http://media.dreamcat.ink/uPic/%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92.png) - +![对象引用传递](https://gitee.com/dreamcater/blog-img/raw/master/uPic/对象引用传递-m3CrXE.png) diff --git "a/Interview/sad/\350\260\210\350\260\210\345\217\215\345\260\204\346\234\272\345\210\266.md" "b/Interview/classify/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" similarity index 50% rename from "Interview/sad/\350\260\210\350\260\210\345\217\215\345\260\204\346\234\272\345\210\266.md" rename to "Interview/classify/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" index 4b585415..09928184 100644 --- "a/Interview/sad/\350\260\210\350\260\210\345\217\215\345\260\204\346\234\272\345\210\266.md" +++ "b/Interview/classify/basis/\345\217\215\345\260\204\346\234\272\345\210\266.md" @@ -1,3 +1,5 @@ +# 反射机制 + > 面试遇到这个问题,必须好好的想想如何回答这个问题,我就是这么回答的。害 面试官:反射是什么? @@ -8,23 +10,26 @@ 我:怎么说呢,跟多态是的,比如在Java程序中许多对象在运行是都会出现两种类型:**编译时类型和运行时类型**。其中,编译时类型由**声明对象时使用的类型来决定**,运行时的类型由**实际赋值给对象的类型决定** 。比如 -`People = = new Man();`程序在运行的时候,有时候需要注入外部资源,那么这个外部资源在编译时是object,如果想要它的运行时类型中的某个方法,为了解决这些问题,程序在运行时发现对象和类的真实信息,但是编译时根本无法预知该对象和类属于哪些类,程序只能靠运行时信息来发现该对象和类的信息,那就要用到反射了。 +`People = = new Man();`程序在运行的时候,有时候需要注入外部资源,那么这个外部资源在编译时是People,如果想要它的运行时类型中的某个方法,为了解决这些问题,程序在运行时发现对象和类的真实信息,但是**编译时根本无法预知该对象和类属于哪些**类,程序只能靠运**行时信息来发现该对象和类的信息**,那就要用到反射了。 + +## 反射的API 面试官:举几个反射的API 我: 1. Class 类:反射的核心类,可以获取类的属性,方法等信息。 -2. Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。 -3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。 -4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法 +2. Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。 +3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。 +4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法 + +## 获取class的方式 面试官:获取class对象的三种方式? 我: ```java - Student student = new Student(); *// 这一new 产生一个Student对象,一个Class对象。* Class studentClass2 = Student.class; // 调用某个类的 class 属性来获取该类对应的 Class 对象 @@ -37,12 +42,38 @@ Class studentClass3 = Class.forName("com.reflect.Student") // 使用 Class 类 我: -- Class.class 的形式会使 JVM 将使用类装载器将类装入内存(前提是类还没有装入内存),不做类的初始化工作,返回 Class 对象。 -- Class.forName() 的形式会装入类并做类的静态初始化,返回 Class 对象。 -- getClass() 的形式会对类进行静态初始化、非静态初始化,返回引用运行时真正所指的对象(因为子对象的引用可能会赋给父对象的引用变量中)所属的类的 Class 对象。 +- Class.class 的形式会使 JVM 将使用类装载器将类装入内存(前提是类还没有装入内存),**不做类的初始化工作**,返回 Class 对象。 +- Class.forName() 的形式会装入类并做类的**静态初始化**,返回 Class 对象。 +- getClass() 的形式会对类进行**静态初始化**、**非静态初始化**,返回引用运行时真正所指的对象(因为子对象的引用可能会赋给父对象的引用变量中)所属的类的 Class 对象。 > 静态属性初始化是在加载类的时候初始化,而非静态属性初始化是 new 类实例对象的时候初始化。它们三种情况在生成 Class 对象的时候都会先判断内存中是否已经加载此类。 +面试官:ClassLoader呢? + +我:ClassLoader就是遵循**双亲委派模型最终调用启动类加载器的类加载器**,实现的功能是“通过一个**类的全限定名来获取描述此类的二进制字节流**”,获取到二进制流后放到JVM中,加载的类**默认不会进行初始化**。 + +而Class.forName()会静态初始化,那么看源码: + +```java +@CallerSensitive +public static Class forName(String className) + throws ClassNotFoundException { + Class caller = Reflection.getCallerClass(); + // 这里的true,就是初始化 + return forName0(className, true, ClassLoader.getClassLoader(caller), caller); +} + +// initialize:是否初始化 +private static native Class forName0(String name, boolean initialize, + ClassLoader loader, + Class caller) + throws ClassNotFoundException; +``` + +## 如何用反射创建对象 + +面试官: + 面试官:除了通过反射创建对象,还有? 我:new呗,clone一个呗 diff --git "a/Interview/classify/basis/\345\237\272\346\234\254\347\261\273\345\236\213.md" "b/Interview/classify/basis/\345\237\272\346\234\254\347\261\273\345\236\213.md" new file mode 100644 index 00000000..88107c01 --- /dev/null +++ "b/Interview/classify/basis/\345\237\272\346\234\254\347\261\273\345\236\213.md" @@ -0,0 +1,57 @@ +# 基本类型 + +> 简单说一下 + + +## 8大基本类型 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) + + +> 特别注意Boolean 未精确定义字节。Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。 + + +## 装箱和拆箱 + +- 装箱:将基本类型用它们对应的**引用类型**包装起来; +- 拆箱:将包装类型转换为**基本数据类型**; + +## 注意初始化 + +```java +int a; +System.out.println(a); // 这里会报错,因为a没有初始化 + +Integer b = new Integer(); // 构造函数必须传递一个默认值,要不然提示报错,无法初始化 +// 看一下Integer的源码的value, +public Integer(int value) { + this.value = value; +} +// 而value,这么以来,创建对象必须初始化咯 +private final int value; +``` + +## 注意字面量 + +```java +float a = 3.14; // 字面量3.14 是double类型, 所以这里会报错 +float a = 3.14f; // 这样就不会报错了,需要加个f + +// 转型 +short a = 1; // short类型 +a = a + 1; // short = short + int 会报错, int无法像下转型,但是short可以向上,这样右边就是Int,无法向下 +a++; // 这里是a = (short) (a + 1); +``` + +## 注意Integer.valueOf + +```java +// This method will always cache values in the range -128 to 127, +public static Integer valueOf(int i) { +if (i >= IntegerCache.low && i <= IntegerCache.high) // 条件 +return IntegerCache.cache[i + (-IntegerCache.low)];// 取缓存, +// Integeer的源码中: +// static final int low = -128; IntegerCache.low = -128 +return new Integer(i); +} +``` \ No newline at end of file diff --git "a/Interview/classify/basis/\345\257\271\350\261\241\347\211\271\346\200\247.md" "b/Interview/classify/basis/\345\257\271\350\261\241\347\211\271\346\200\247.md" new file mode 100644 index 00000000..660471c4 --- /dev/null +++ "b/Interview/classify/basis/\345\257\271\350\261\241\347\211\271\346\200\247.md" @@ -0,0 +1,255 @@ +# 对象特性 + +## 封装 + +封装把一个对象的**属性私有化**,同时提供一些可以**被外界访问的属性的方法**。 + +```java +class Person{ + String name; // 属性 + int age; + + // 方法 + public Person(String name, int age){ + this.name = name; + this.age = age; + } +} +``` + +## 继承 + +继承是使用**已存在的类**的定义作为基础建立新类的技术,新类的定义可以增加**新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类**。通过使用继承我们能够非常方便地复用以前的代码。 + +```java +class Student extends Person{ + double grade; // 在已经的name,age中, 在学生类中添加了成绩或者班级 + + // 方法 + public Student(String name, int age, double grade) { + this.name = name; + this.age = age; + this.grade = grade; + } +} +``` + +## 多态 + +### 定义 + +三要素:加黑的地方! + +首先我觉得即**一个引用变量到底会指向哪个类的实例对象**,该**引用变量发出的方法调用到底是哪个类中实现的方法**,必须在由程序**运行期间**才能决定。强调在编译的时候,不知道该引用指向的是哪个对象的实例,包括调用哪个实例的方法,只有运行的时候,动态知道。 + +举个例子: + +任何事物的多个姿态,多个形态。比如,你说一个猫在吃东西,同样的,你也能说一个动物在吃东西。 + +```java +public class Test { + public static void main(String[] args){ + Animal animal = new Cat(); + animal.eat() // 猫也会吃饭 + // 你看到了一只猫,同样它也是动物 + // 比如有很多其他种类继承了动物哈, + // 当编译期间的animal引用变量,到底指的哪个实例对象,(重要)(主语是引用变量) + // 或者该引用调用的eat方法,到底是哪个实例对象的eat,编译期间恐怕不知道哦(主语是引用变量) + // 只有运行期间,哦哦, 原来是猫的eat方法哇... + } +} +``` + +### 表现形式 + + +- **Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。调用方法时通过传递的参数类型来决定具体使用哪个方法**,这就是多态性。比如,java lang的包很多工具类如String工具类,那么就有很多相同的名字,但是参数类型、数量和返回值等等不一样。 + + +- **Java的方法重写,是父类与子类之间的多态性,子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改**。这也是多态性。比如JUC的AQS框架,凡事继承了AQS的那几个类,其中几个重要的方法,都被重写了,很多这样的情况。 + +### 底层 + +首先要说:首先当程序运行需要某个类时,类加载器会将相应的class文件载入到JVM中,并在方法区建立该类的类型信息(包括方法代码,类变量、成员变量以及**方法表**。(标黑的这个玩意) + +面试官:方法表有啥? + +我:方法表的结构如同字段表一样,依次包括了**访问标志、名称索引、描述符索引、属性表集合**几项。 + +接着回答:**方法表是实现动态调用的核心。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向记录该类方法的方法表,方法表中的每一个项都是对应方法的指针**。 + +到这里:就要分情况讨论了,一个是方法调用,一个是接口 + +##### 方法调用 +```java +class Person { + // 重写object的toString + public String toString(){ + return "I'm a person."; + } + public void eat(){} + public void speak(){} + +} + +class Boy extends Person{ + // 重写object的toString + public String toString(){ + return "I'm a boy"; + } + // 继承Person的speak + public void speak(){} + // 自己实现的自定义方法 + public void fight(){} +} + +class Girl extends Person{ + // 重写object的toString + public String toString(){ + return "I'm a girl"; + } + // 继承Person的speak + public void speak(){} + // 自己实现的自定义方法 + public void sing(){} +} +``` + +![多态方法调用-I0jtEe](https://gitee.com/dreamcater/blog-img/raw/master/uPic/多态方法调用-I0jtEe.png) + +这张图的指向:你可以根据颜色对应上,注意方法表条目指向的具体的**方法地址**。其次注意蓝色部分其继承自于 Person 的方法 eat() 和 speak() 分别指向 **Person 的方法实现和本身的实现**。如果子类改写了父类的方法,那么子类和父类的那些**同名的方法共享一个方法表项**。因此,**所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值**。Person 或 Object中的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。 + + +调用过程: + +1. 在常量池里找到方法调用的**符号引用**(肯定先看到Person定义引用类型) +2. 查看Person的方法表,得到speak方法在该**方法表的偏移量**(假设为15),这样就得到该方法的直接引用。 +3. 根据this(invoker this字节码)指针得到**具体的对象**(即 girl 所指向的位于堆中的对象)。 +4. 根据对象得到该对象对应的方法表,根据偏移量15查看**有无重写(override)该方法**,如果重写,则可以直接调用(Girl的方法表的speak项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person类)的方法表,同样按照这个偏移量15查看有无该方法。 + + +#### 接口调用 + +一个类可以实现多个接口,那么就像多继承一样,这样的话,在方法表中的索引就会不一样,所以Java 对于接口方法的调用是采用**搜索方法表**的方式。 + +## 初始化顺序 + +有这样的两个类:Person和Student + +Person: + +```java +class Person{ + // 静态代码块 + static { + System.out.println("Person 静态方法"); + } + // 代码块 + { + System.out.println("Person 代码块"); + } + // 构造方法 + Person(){ + System.out.println("Person 构造方法"); + } +} +``` + +Student: + +```java +class Student extends Person{ + // 静态代码块 + static { + System.out.println("Student 静态方法"); + } + // 代码块 + { + System.out.println("Student 代码块"); + } + // 构造方法 + Student(){ + System.out.println("Student 构造方法"); + } +} +``` + +测试 + +```java +public class Main { + public static void main(String[] args) { + Student student = new Student(); + } +} + +// result: + +Person 静态方法 +Student 静态方法 +Person 代码块 +Person 构造方法 +Student 代码块 +Student 构造方法 +``` + +解释:还不是因为类加载器的双亲委派哈, 先走类方法,也就是说static属于类,所以先调用**Person static -> Student static**,接着走对象的初始化,那么对象初始化了,还是先走父类的初始化了,所以**Perosn {} -> Person(){}**,最后走子类的初始化**Student {} -> Student(){}** + + + + +## 接口和抽象类的区别 + +1. 方法是否能实现:所有**方法在接口中不能有实现**(Java 8 开始接口方法可以有默认实现),而**抽象类可以有非抽象的方法**。 +2. 变量:接口中除了**static、final变量**,不能有其他变量,而抽象类中则不一定。 +3. 实现:一个类可以实现**多个接口**,但**只能实现一个抽象类**。接口自己本身可以通过implement关键字扩展多个接口。 +4. 修饰符:接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 +5 + +## 匿名内部类传参数为什么需要final + +```java +public class Main { + public static void main(String[] args) { + String str = "m"; + new Thread(){ + @Override + public void run() { + System.out.println(str); + } + }.start(); + } +} +// 在1.8版本之前是编译不通过的,1.8能编译能过,但是还是需要final保证局部变量的数据一致性 +``` + +反编译 + +```java + +public class Hello$1 extends Thread { + + private String val$str; + + Hello$1(String paramString) { + this.val$str = paramString; + } + + public void run() { + System.out.println(this.val$str); + } + +} +``` + +解释:**局部变量是被当成了参数传递给匿名对象的构造器(那就相当于拷贝一份,那就是浅拷贝了),这样的话,如果不管是基本类型还是引用类型,一旦这个局部变量是消失(局部变量随着方法的出栈而消失),还是数据被改变,那么此时匿名构造类是不知道的,毕竟是你浅拷贝了一份,那么如果加上final,这个数据或者引用永远都不会改变,保证数据一致性。注意:在JDK8中如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不需要用final修饰符修饰。** + +## 四种修饰符的限制范围 + +1. public:可以被所有其他类所访问。 + +2. private:只能被自己访问和修改。 + +3. protected:自身,子类及同一个包中类可以访问。 + +4. default(默认):同一包中的类可以访问 \ No newline at end of file diff --git "a/Interview/classify/basis/\345\272\217\345\210\227\345\214\226.md" "b/Interview/classify/basis/\345\272\217\345\210\227\345\214\226.md" new file mode 100644 index 00000000..49010d9a --- /dev/null +++ "b/Interview/classify/basis/\345\272\217\345\210\227\345\214\226.md" @@ -0,0 +1,14 @@ +> 很多rpc框架都要考虑序列化的问题,但是我没有过于深入 + +# 序列化 +1. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。 +2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。 +3. 如果想让某个变量不被序列化,使用transient修饰。 +4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。 +5. 反序列化时必须有序列化对象的class文件。 +6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。 +7. 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。 +8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。 +9. 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。 + +[参考链接](https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf#heading-9) \ No newline at end of file diff --git "a/Interview/sad/\350\260\210\350\260\210\345\274\202\345\270\270\346\234\272\345\210\266.md" "b/Interview/classify/basis/\345\274\202\345\270\270\344\275\223\347\263\273.md" similarity index 88% rename from "Interview/sad/\350\260\210\350\260\210\345\274\202\345\270\270\346\234\272\345\210\266.md" rename to "Interview/classify/basis/\345\274\202\345\270\270\344\275\223\347\263\273.md" index 7479b389..408d4a6a 100644 --- "a/Interview/sad/\350\260\210\350\260\210\345\274\202\345\270\270\346\234\272\345\210\266.md" +++ "b/Interview/classify/basis/\345\274\202\345\270\270\344\275\223\347\263\273.md" @@ -1,5 +1,8 @@ + > 面着面着,就修仙了,还原俺的真实面试场景。 +# 异常体系 + 面试官:谈谈异常机制? 我:只要涉及到谈谈,我内心都是崩溃的,不知道该如何说起 @@ -52,7 +55,7 @@ 面试官:RuntimeException中有哪些,举一些? -我:好的,比如,NullPointerException,ArithmeticException,ClassCastException,ArrayIndexOutOfBoundsException等 +我:好的,比如,`NullPointerException`,`ArithmeticException`,`ClassCastException`,`ArrayIndexOutOfBoundsException`等 面试官:什么是受检异常和非受检异常? @@ -72,6 +75,31 @@ throw 抛出异常,throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常) +注意:finally return会覆盖try和catch的返回值 + +```java +public class Main { + public static void main(String[] args) { + System.out.println(test()); + } + + public static int test() { + int a = 0; + try { + int b = 1 / 0; // 不管发生不发生异常 + a++; + return a; + } catch (Exception e) { + a++; + return a; + } finally { + return 5; + } + } +} +// 结果都是5 +``` + ## 结束 面试官:可以,我们换一波问题。 @@ -96,3 +124,4 @@ throw 抛出异常,throws是方法可能抛出异常的声明。(用在声明方 8. 异常类型错误(checked异常失效) + diff --git "a/Interview/classify/basis/\346\263\233\345\236\213.md" "b/Interview/classify/basis/\346\263\233\345\236\213.md" new file mode 100644 index 00000000..3ffeb7d7 --- /dev/null +++ "b/Interview/classify/basis/\346\263\233\345\236\213.md" @@ -0,0 +1,13 @@ + +> 我也没有过多的深入这一块 + +## 泛型 +泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 + +**好处**: +- **类型安全**,提供编译期间的类型检测 +- **前后兼容** +- **泛化代码,代码可以更多的重复利用** +- **性能较高**,用GJ(泛型JAVA)编写的代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件 + +[泛型擦除原理](https://www.jianshu.com/p/328efeb01940) \ No newline at end of file diff --git "a/Interview/classify/basis/\346\267\261\346\265\205\346\213\267\350\264\235.md" "b/Interview/classify/basis/\346\267\261\346\265\205\346\213\267\350\264\235.md" new file mode 100644 index 00000000..d9f2541b --- /dev/null +++ "b/Interview/classify/basis/\346\267\261\346\265\205\346\213\267\350\264\235.md" @@ -0,0 +1,9 @@ +# 深浅拷贝 + +- **浅拷贝**:对**基本数据类型进行值传递**,对**引用数据类型进行引用传递般的拷贝**,此为浅拷贝。 +- **深拷贝**:对**基本数据类型进行值传递**,对**引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝** +- 也就二者对引用数据类型有区别 + + +知道结论即可,要想测试,可以[我博客-先占坑]() + diff --git "a/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" "b/Interview/classify/col/\350\260\210\350\260\210\351\233\206\345\220\210.md" similarity index 95% rename from "Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" rename to "Interview/classify/col/\350\260\210\350\260\210\351\233\206\345\220\210.md" index 47a07b6c..0912a09e 100644 --- "a/Interview/sad/\350\260\210\350\260\210\351\233\206\345\220\210.md" +++ "b/Interview/classify/col/\350\260\210\350\260\210\351\233\206\345\220\210.md" @@ -1,3 +1,6 @@ +[toc] + + > 集合这一块,问就是并发问题,但前提先有总体了解。 @@ -38,6 +41,7 @@ > 其实就是**size++** 这一步的问题。 越界就是两个线程临界值去**扩容**都满足,于是一个线程size++导致的,另外一个线程就溢出了,**null就是element[size] = e**,第一个线程还没来得及size++,第二个线程就在原先的索引上把值给覆盖了,并且在下一个索引为null。 越界 + - 列表大小为9,即size=9 - 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 - 线程B此时也进入add方法,它和获取的size的值也为9,也开始调用ensureCapacityInternal方法。 @@ -49,6 +53,7 @@ - 于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 null + - 列表大小为10,即size=0 - 线程A开始添加元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。也就是说,线程挂在了`element[0] = e`上。 - 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一条的操作。此时线程B获取的size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 @@ -76,12 +81,15 @@ null > 负载因子过低,频繁扩容,扩容会重新哈希,性能下降;负载因子过高,容易浪费容量.(经验+概率) - 为什么红黑树的阈值是8? + > 在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。(泊松分布) - 为什么退化链表的阈值6? + > 6是因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。 - hash + ```java static final int hash(Object key) { int h; @@ -98,7 +106,7 @@ hash 函数是先拿到通过 key 的 hashcode,**是 32 位的 int 值**,然 ### put -![put过程](http://media.dreamcat.ink/uPic/hashmap-put-1.8.png) +![hashmap-put-1.8](https://gitee.com/dreamcater/blog-img/raw/master/uPic/hashmap-put-1.8-WLBoNy.png) - 判断数组是否为空,为空进行初始化;(初始化) - 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;(通过hash计算index) @@ -122,6 +130,7 @@ hash 函数是先拿到通过 key 的 hashcode,**是 32 位的 int 值**,然 ### 扩容 > 先说1.7吧 + ```java for (HashMapEntry e : table) { // 如果这个数组位置上有元素且存在哈希冲突的链表结构则继续遍历链表 @@ -138,7 +147,7 @@ for (HashMapEntry e : table) { ``` > 1.8 -HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。也就是说省略了重新计算hash值的时间,而且新增的1位是0还是1机会是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。如果在新表的数组索引位置相同,则链表元素不会倒置。 +> HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。也就是说省略了重新计算hash值的时间,而且新增的1位是0还是1机会是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。如果在新表的数组索引位置相同,则链表元素不会倒置。 @@ -156,13 +165,16 @@ HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以 ## ConcurrentHashMap ### 1.7 + #### segment + - 唯一的区别(和HashMap)就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 - ConcurrentHashMap 采用了分段锁技术(相对hashTable降低锁的粒度),其中 Segment 继承于 `ReentrantLock`(可能还会扯AQS)。 - 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 - **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** #### put + - 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 - 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` **自旋获取锁**。 - 尝试获取自旋锁 @@ -176,42 +188,51 @@ HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以 - 最后会解除在1中所获取当前Segment的锁。 #### get + - 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 - 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 - ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** #### size + 在 JDK1.7 中,第一种方案他会**使用不加锁的模式**去尝试多次计算 ConcurrentHashMap 的 size,最多**三次**,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给**每个 Segment 加上锁**,然后计算 ConcurrentHashMap 的 size 返回 ### 1.8 + 1.7 查询遍历链表效率太低(种种原因)。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性(会扯1.6对synchronized的优化) #### put + - 根据key计算出hashcode - 判断是否需要进行初始化 - 如果f为null,说明table中这个位置第一次插入元素,利用Unsafe.compareAndSwapObject方法插入Node节点。 - - 如果CAS成功,说明Node节点已经插入,随后addCount(1L, binCount)方法会检查当前容量是否需要进行扩容。 - - 如果CAS失败,说明有其它线程提前插入了节点,自旋重新尝试在这个位置插入节点。 + - 如果CAS成功,说明Node节点已经插入,随后addCount(1L, binCount)方法会检查当前容量是否需要进行扩容。 + - 如果CAS失败,说明有其它线程提前插入了节点,自旋重新尝试在这个位置插入节点。 - 如果f的hash值为-1,说明当前f是ForwardingNode节点,意味有其它线程正在扩容,则一起进行扩容操作。 - 如果都不满足,则利用`synchronized`锁写入数据 - 如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。 #### get + - 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 - 如果是红黑树那就按照树的方式获取值。 - 就不满足那就按照链表的方式遍历获取值。 #### size + ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是迭代 counterCells 来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 **addCount()** 方法。 在addCount()方法中: + - 如果 counterCells == null, 则对 baseCount 做 CAS 自增操作。 - 如果并发导致 baseCount CAS 失败了使用 counterCells。 - 如果counterCells CAS 失败了,在 fullAddCount 方法中,会继续死循环操作,直到成功。 - CounterCell使用了 @sun.misc.Contended 标记的类 + > 缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,**如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享**。 实际上: + - JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。 - JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。 @@ -325,4 +346,3 @@ class LRUCache { } ``` - diff --git "a/Interview/sad/CAP\345\222\214BASE.md" "b/Interview/classify/dis/CAP\345\222\214BASE.md" similarity index 95% rename from "Interview/sad/CAP\345\222\214BASE.md" rename to "Interview/classify/dis/CAP\345\222\214BASE.md" index d729d5ee..c6f3b126 100644 --- "a/Interview/sad/CAP\345\222\214BASE.md" +++ "b/Interview/classify/dis/CAP\345\222\214BASE.md" @@ -1,17 +1,14 @@ -面试官:知道分布式中的CAP嘛? -我:知道 +## CAP - C(一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。 - A(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。 - P(分区容错性):当出现网络分区后,系统能够继续工作。打个比方,集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。 + - CP:对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。 - AP:对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。 -面试官:BASE呢? - -我:简单 - +## BASE BASE是BasicallyAvailable(基本可用)、Softstate(软状态)和Eventuallyconsistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展-基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。 - 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。 - 最终一致:**最终一致是指经过一段时间后,所有节点数据都将会达到一致**。BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。 \ No newline at end of file diff --git a/Interview/classify/dis/Dubbo.md b/Interview/classify/dis/Dubbo.md new file mode 100644 index 00000000..64041f3d --- /dev/null +++ b/Interview/classify/dis/Dubbo.md @@ -0,0 +1,133 @@ +[toc] + +## Dubbo是什么? + +Dubbo是一款**高性能**、**轻量级**的开源JavaRPC框架,它提供了三大核心能力:**面向接口的远程方法调用**,**智能容错和负载均衡**,以及**服务自动注册和发现**。简单来说Dubbo是一个**分布式服务框架**,致力于提供**高性能和透明化的RPC远程服务调用方案**,以及**SOA服务治理方案。** + +- **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务。 +- **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo可以为我们解决服务之间互相是如何调用的。 +- **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 +- **服务降级**——某个服务挂掉之后调用备用服务。 + +## Dubbo的图解? +![dubbo架构-qvfIHl](https://gitee.com/dreamcater/blog-img/raw/master/uPic/dubbo架构-qvfIHl.png) + +- **Provider:**暴露服务的服务提供方 +- **Consumer:**调用远程服务的服务消费方 +- **Registry:**服务注册与发现的注册中心 +- **Monitor:**统计服务的调用次数和调用时间的监控中心 +- **Container:**服务运行容器 + +**调用关系说明**: + +- 服务容器负责启动,加载,运行服务提供者。 +- 服务提供者在启动时,向注册中心注册自己提供的服务。 +- 服务消费者在启动时,向注册中心订阅自己所需的服务。 +- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 +- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 +- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 + +**各个组件总结**: + +- **注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小** +- **监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示** +- **注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外** +- **注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者** +- **注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表** +- **注册中心和监控中心都是可选的,服务消费者可以直连服务提供者** +- **服务提供者无状态,任意一台宕掉后,不影响使用** +- **服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复** + +## Dubbo和SpringCloud的区别? +- **底层**:`Dubbo`底层是使用Netty的NIO框架,基于TCP协议传输,使用Hession序列化完成RPC通信;`SpringCloud`是基于HTTP协议+REST接口调用远程过程的通信,HTTP请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,不存在代码级别的强依赖。 +- **集成**:springcloud相关组件多,有自己的注册中心网关等,集成方便,Dubbo需要自己额外去集成。Dubbo是SOA时代的产物,它的关注点主要在于**服务的调用,流量分发、流量监控和熔断**。而SpringCloud诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了Spring、SpringBoot的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、SpirngCloud是一个生态。 + + +## Dubbo的容错机制 +1. Failover Cluster失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数 +2. Failfast Cluster快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 +3. Failsafe Cluster失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 +4. Failback Cluster失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 +5. Forking Cluster并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 +6. 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息 + +## Dubbo的限流是怎么实现的? +Dubbo默认是**令牌桶**算法实现限流。在某段时间内,以某种速度向桶里面只能放n个令牌,然后来一个请求就减少一个令牌,如果桶内的令牌没有了,则不能继续执行请求。 + +限流主要是通过TPSLimitFilter实现。 + +## 常见的限流算法有哪些? +- 计数算法 +- 滑动窗口,解决计数算法同一时刻进入很多请求 +- 令牌桶算法 +- 漏桶算法 + + +## 什么是dubbo服务降级? +- dubbo在服务调用时,可能由于服务器宕机、网络超时、并发数太高等,导致调用失败。**服务降级**就是指在非业务异常导致的服务不可用时,可以返回默认值,避免影响主业务的处理。 +- dubbo可以通过mock配置实现服务降级。 + +## Dubbo的注册中心挂了,还可以继续通信吗? +可以。因为在开始初试化的时候,消费者会将提供者的地址等信息拉取到**本地缓存**中。 + +## 负载均衡 +个人理解: +> 比如我们的系统中的某个服务的**访问量特别大**,我们将这个服务部署在了**多台服务器**上,当客户端发起请求的时候,**多台服务器都可以处理这个请求**。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。 + +1. RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。 +2. RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。 +3. LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。 +4. ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。 + +[http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html](http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html) + +## 为什么使用zk当dubbo的注册中心 +Zookeeper的数据模型很简单,有一系列被称为ZNode的数据节点组成,与传统的磁盘文件系统不同的是,zk将全量数据存储在内存中,可谓是高性能,而且支持集群,可谓高可用,另外支持事件监听。这些特点决定了zk特别适合作为注册中心(数据发布/订阅)。不过要注意网络闪断引发的节点摘除问题。 + +## 几个服务注册与发现的对比 +[https://zhuanlan.zhihu.com/p/145296163](https://zhuanlan.zhihu.com/p/145296163) + +## SPI源码(过程) +先说一下Java的SPI机制 +Java的SPI机制利用ServiceLoader的load方法传递个接口,就会得到该接口的所有的实现类 +要在指定的META-INF的services下 +但是有一说一,只能通过iterator来遍历判断想要的实现类 +而Dubbo和Spring的SPI比Java的灵活一些,可以通过key来获取对应的实例 + +直接说Dubbo的SPI源码过程 +先说一下Dubbo的SPI机制,不仅支持有着Java的SPI,还有着AOP的功能,同时有着DI功能 +1. 通过getExtensionLoader得到该接口的load,不过获取之间会对一些type检查,同时有缓存机制。 +2. 然后通过load调用getExtension,也是一系列检查和缓存,最关键的就是createExtension +3. 其中getExtensionClasses,这个方法返回对应name的接口的实例对象,接着来到injectExtension注入属性 +4. 如果有wrapper包装,就是通过接口的实例类有木有构造器,如果有,最后injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));无限遍历AOP,也就是构造器注入,最后返回带包装的接口的实例对象。 +5. 以上是没有讲依赖注入的过程,官网上有。 + +## 服务导出与暴漏(源码流程) +源码省略,针对于面试就说源码流程即可 +总结一波吧: +1. 当我们看源码知道,导出和暴漏在IoC初始化的最后一步的finishRefresh中的ServiceBean中。 +2. 其中在onApplicationEvent执行export->doExport,在doExport中首先检查provider呀,register呀,monitor呀等,最后来到关键的一步doExportUrls(); +3. 在这一步当中,实际上,就是对注册的url和导出的url拼接,并且将导出的url远程注册到注册中心,最后暴漏一下自己的url,具体的话就第四步。 +4. doExportUrlsFor1Protocol 包括:1. exportLocal,默认本地导出,2. 远程导出:proxyFactory.getInvoker,然后得到wrapperInvoker,最后就是这个关键了protocol.export(wrapperInvoker),然后会有个子流程去构造buildInvokerChain,调用链。这个是服务调用链路 +5. 实际上找Protocol.class接口的实例代理类,默认是dubbo协议,因此调用的dubbo的实例代理类的export方法,继续使用dubbo协议的url,一步一步绑定nettyClient客户端,最后导出自己的调用链。 + + +## 服务引入与目录(源码过程) + +肯定是ReferenceBean + +1. 当我们看源码知道,首先进来的ReferenceBean的get方法->ReferenceConfig的init方法内部 +2. checkDefault检查消费端的全局配置,接着通过SPI获取消费服务的实现类,经过一些列检查又进入了HashMap的缓存当中 +3. init方法中的最后一步createProxy中,这个方法就是将要引入订阅注册中心的服务端的目录,首先是refprotocol.refer方法从注册中心引入和订阅,该方法是核心。 +4. 首先通过RegistryProtocol的refer中,如果是zk协议,那么就启动zk客户端去连接,接着进入doRefer方法中,先在注册中心,注册消费端服务,接着开始通过subscribe订阅注册中心的目录,category、providers、configurators和routers,然后进入notify,调用listener.notify(categoryList),通知categoryList +5. 这时候来到了协议Dubbo的refer中,开始构造路由链,首先buildInvokerChain调用链,Dubbo启动的是netty客户端哦,debug时候看出来的,获取的是netty的client,最后构建成功就返回。 +6. 最后将所有的目录添加到cluster中,并返回invoker,其实该invoker是MockClusterInvoker,ref是它的代理实现类最后初始化完毕。 + +总感觉处处invoker(执行)类似于发送请求一样。 + +## 服务调用、服务降级和集群容错 +先说一下invorker,在服务引入那里最终返回的是MockClusterInvoker的代理实现类,意思就是说,首先进入Java的动态代理,InvokerInvocationHandler,然后调用invork,进入MockClusterInvoker,然后调用invoke进入默认的FailoverClusterInvoker的invoker。每个invoker就是InvokerDelegate委托实现类 +1. 根据我上面说的,其实从服务目录获取所有的提供者Invokers,在经过MockClusterInvoker的时候,如果配置了服务降级,服务降级就是通过mock机制而已,那么如果调用失败,先走Mock的服务降级策略,如果没有配置,然后开始初始化负载均衡策略, +2. 就进入了容错策略的Invoker类,然后通过负载均衡选择一个invoker,开始调用过滤链,最后才会执行我们的Dubbo协议上的客户端,应该是netty吧,去执行invoker +3. 服务那边开始被触发事件之后,也会执行自己的过滤链,然后最后执行自己的InvokerDelegate服务实现委托类,将结果先返回给自己,然后在通过负责处理请求的控制器传给消费端。 +4. 以上是一次调用过程粗略的经过。 diff --git a/Interview/classify/dis/RocketMQ.md b/Interview/classify/dis/RocketMQ.md new file mode 100644 index 00000000..2181e150 --- /dev/null +++ b/Interview/classify/dis/RocketMQ.md @@ -0,0 +1,169 @@ + + +> 注意:先去了解消息队列:https://www.zhihu.com/question/34243607 + + +## 为什么选择RocketMQ作消息队列 +- ActiveMQ 的社区算是比较成熟,但是较目前来说,**ActiveMQ 的性能比较差,而且版本迭代很慢**,不推荐使用。 +- RabbitMQ 在**吞吐量方面虽然稍逊于 Kafka 和 RocketMQ** ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果**业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选**。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 +- RocketMQ 阿里出品,**Java 系开源项目**,源代码我们可以直接阅读,然后可以**定制自己公司的MQ**,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。其次具有分布式事务消息的功能,可以达到消息的最终一致性。 +- kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。**kafka 唯一的一点劣势是有可能消息重复消费**,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 + +## RocketMQ组件 +- Producer:消息发布的角色,主要负责把消息发送到Broker,支持分布式集群方式部署。 +- Consumer:消息消费者的角色,主要负责从Broker订阅消息消费,支持分布式集群方式部署。 +- Broker:消息存储的角色,主要负责消息的存储、投递和查询,以及服务高可用的保证,支持分布式集群方式部署。 +- NameServer:是一个非常简单的Topic路由注册中心,其角色类似于Dubbo中依赖的Zookeeper,支持Broker动态注册和发现。 + - 服务注册:NameServer接收Broker集群注册的信息,保存下来作为路由信息的基本数据,并提供心跳检测机制,检查Broker是否存活。 + - 路由信息管理:NameServer保存了Broker集群的路由信息,用于提供给客户端查询Broker的队列信息。Producer和Consumer通过NameServer可以知道Broker集群的路由信息,从而进行消息的投递和消费。 + +## MQ在高并发情况下,假设队列满了如何防止消息丢失? +- 生产者可以采用重试机制。因为消费者会不停的消费消息,可以重试将消息放入队列。 +- 死信队列,可以理解为备胎(推荐) + - 即在消息过期,队列满了,消息被拒绝的时候,都可以扔给死信队列。 + - 如果出现死信队列和普通队列都满的情况,此时考虑消费者消费能力不足,可以对消费者开多线程进行处理。 + +## RocketMQ事务消息原理 + +![mq事务消息-NaWGxH](https://gitee.com/dreamcater/blog-img/raw/master/uPic/mq事务消息-NaWGxH.png) + +想了想,mq也可以 +1. 先给Brock发送一条消息:我要下单了,注意哈 +2. Brock给本地事务回馈消息:ack,好的,我知道了(半投递状态,消费端看不到) +3. 本地事务开始执行业务逻辑,这里首先(校验场次id的座位是否重复,如果没有,直接执行下单:这两个业务非原子,上个锁,要不然可能会出现同样的座位)。下单成功,则返回commit给Brock。消费者此时就可以看到这条消息了。 +4. 如果下单不成功,则返回rollback,Brock一般三天自动删除该无效的消息,消费者也看不到。 +5. 消费者看到了这条消息,调用绑定座位服务,如果失败了,则重试。(消费端不能失败,要不然不能保持一致,如果还是一直失败,则人工处理。) 注意:幂等性 + +## 谈谈死信队列 +**死信队列用于处理无法被正常消费的消息,即死信消息**。 + +当一条消息初次消费失败,**消息队列 RocketMQ 会自动进行消息重试**;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 不会立刻将消息丢弃,而是将其发送到该**消费者对应的特殊队列中**,该特殊队列称为**死信队列**。 + +**死信消息的特点**: + +- 不会再被消费者正常消费。 +- 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。 + +**死信队列的特点**: + +- 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。 +- 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列。 +- 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。 + +消息队列 RocketMQ 控制台提供对死信消息的查询、导出和重发的功能。 + +## 使用异步消息时如何保证数据的一致性 + +- **借助数据库的事务**:这需要在数据库中创建一个**本地消息表**,这样可以通过**一个事务来控制本地业务逻辑更新**和**本地消息表的写入在同一个事务中**,一旦消息落库失败,则直接全部回滚。如果消息落库成功,后续就可以根据情况基于本地数据库中的消息数据对消息进行重投了。关于本地消息表和消息队列中状态如何保持一致,可以采用 2PC 的方式。在发消息之前落库,然后发消息,在得到同步结果或者消息回调的时候更新本地数据库表中消息状态。然后只需要通过**定时轮询**的方式对状态未已记录但是未发送的消息重新投递就行了。但是这种方案有个前提,就是要求消息的消费者**做好幂等控制**,这个其实异步消息的消费者一般都需要考虑的。 +- 除了使用数据库以外,还可以使用 **Redis** 等缓存。这样就是无法利用关系型数据库自带的事务回滚了。 + +## RockMQ不适用Zookeeper作为注册中心的原因,以及自制的NameServer优缺点? +- ZooKeeper 作为支持**顺序一致性**的中间件,在某些情况下,它为了满足一致性,会丢失一定时间内的**可用性**,RocketMQ 需要注册中心只是为了**发现组件地址**,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,这同时也是 **NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致**。 +- 另外,当有新的服务器加入时,**NameServer 并不会立马通知到 Produer**,而是由 **Produer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息**(这种情况是通过 Producer 发送消息时,负载均衡解决) +- 包括组件通信间使用 Netty 的自定义协议 +- 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略) +- 消息过滤器(Producer 发送消息到 Broker,Broker 存储消息信息,Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤) +- Broker 同步双写和异步双写中 Master 和 Slave 的交互 + +## 实现延迟队列 +rocketmq发送延时消息时先把消息按照延迟时间段发送到指定的队列中(rocketmq把每种延迟时间段的消息都存放到同一个队列中)然后通过一个定时器进行轮训这些队列,查看消息是否到期,如果到期就把这个消息发送到指定topic的队列中,这样的好处是同一队列中的消息延时时间是一致的,还有一个好处是这个队列中的消息时按照消息到期时间进行递增排序的,说的简单直白就是队列中消息越靠前的到期时间越早 + +缺点:定时器采用了timer,timer是单线程运行,如果延迟消息数量很大的情况下,可能单线程处理不过来,造成消息到期后也没有发送出去的情况 + +改进点:可以在每个延迟队列上各采用一个timer,或者使用timer进行扫描,加一个线程池对消息进行处理,这样可以提供效率 + +## 消息队列如何保证顺序消费 +生产者中把 orderId 进行取模,把相同模的数据放到 messagequeue 里面,消费者消费同一个 messagequeue,只要消费者这边有序消费,那么可以保证数据被顺序消费。 + +RocketMQ:顺序消息一般使用集群模式,是指对消息消费者内的线程池中的线程对消息消费队列只能串行消费。并并发消息消费最本质的区别是消息消费时必须成功锁定消息消费队列,在Broker端会存储消息消费队列的锁占用情况。 +[https://blog.csdn.net/AAA821/article/details/86650471](https://blog.csdn.net/AAA821/article/details/86650471) + +## RocketMQ消息失败策略 +[https://developer.aliyun.com/article/717340](https://developer.aliyun.com/article/717340) + +## RocketMQ顺序消息消费 +[https://juejin.im/post/6844903982805024782](https://juejin.im/post/6844903982805024782) + +[https://www.cnblogs.com/hzmark/p/orderly_message.html](https://www.cnblogs.com/hzmark/p/orderly_message.html) + +## 零拷贝 +都知道内核和用户态了,不必多说了 +1. 从磁盘复制数据到内核态内存; +2. 从内核态内存复制到用户态内存; +3. 然后从用户态内存复制到网络驱动的内核态内存; +4. 最后是从网络驱动的内核态内存复制到网卡中进行传输。 + +但,通过使用mmap的方式,可以省去向用户态的内存复制,提高速度。这种机制在Java中是通过MappedByteBuffer实现的(零拷贝) + +[https://zhuanlan.zhihu.com/p/83398714](https://zhuanlan.zhihu.com/p/83398714) + +## 消息存储 +### 存储过程 +1. 消息生产者发送消息 +2. MQ收到消息,将消息进行持久化,在存储中新增一条记录 +3. 返回ACK给生产者 +4. MQ push 消息给对应的消费者,然后等待消费者返回ACK +5. 如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,即执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤 +6. MQ删除消息 + +想说一点,activeMQ的存储介质DB,这就影响了存储效率,其他几位MQ采用的文件系统,并且依照顺序写,极大跟随了SSD的步伐。 + + +### 存储结构 +RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成的,消息真正的物理存储文件是CommitLog,ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。 + +- CommitLog:存储消息的元数据 +- ConsumerQueue:存储消息在CommitLog的索引 +- IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程 + +## 刷盘机制 +**同步机制**:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。 +1. 封装刷盘请求 +2. 提交刷盘请求 +3. 线程阻塞5秒,等待刷盘结束 + +服务那边: +1. 加锁 +2. 遍历requestsRead +3. 刷盘 +4. 唤醒发送消息客户端 +5. 更新刷盘监测点 + +**异步机制**:在返回写成功状态时,消息**可能**只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。 + +在消息追加到内存后,立即返回给消息发送端。如果开启transientStorePoolEnable,RocketMQ会单独申请一个与目标物理文件(commitLog)同样大小的堆外内存,该堆外内存将使用内存锁定,确保不会被置换到虚拟内存中去,消息首先追加到堆外内存,然后提交到物理文件的内存映射中,然后刷写到磁盘。如果未开启transientStorePoolEnable,消息直接追加到物理文件直接映射文件中,然后刷写到磁盘中。 + +开启transientStorePoolEnable后异步刷盘步骤: + +1. 将消息直接追加到ByteBuffer(堆外内存) +2. CommitRealTimeService线程每隔200ms将ByteBuffer新追加内容提交到MappedByteBuffer中 +3. MappedByteBuffer在内存中追加提交的内容,wrotePosition指针向后移动 +4. commit操作成功返回,将committedPosition位置恢复 +5. FlushRealTimeService线程默认每500ms将MappedByteBuffer中新追加的内存刷写到磁盘 + +## 路由管理 +### 心跳机制 +- RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。 +- Broker启动时向集群中所有的NameServer发送心跳信息,每隔30s向集群中所有NameServer发送心跳包,NameServer收到心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdataTimeStamp信息,然后NameServer每隔10s扫描brokerLiveTable,如果连续120S没有收到心跳包,NameServer将移除Broker的路由信息同时关闭Socket连接。 + +### 删除路由 +- `Broker`每隔30s向`NameServer`发送一个心跳包,心跳包包含`BrokerId`,`Broker`地址,`Broker`名称,`Broker`所属集群名称、`Broker`关联的`FilterServer`列表。但是如果`Broker`宕机,`NameServer`无法收到心跳包,此时`NameServer`如何来剔除这些失效的`Broker`呢? +- `NameServer`会每隔10s扫描`brokerLiveTable`状态表,如果`BrokerLive`的**lastUpdateTimestamp**的时间戳距当前时间超过120s,则认为`Broker`失效,移除该`Broker`,关闭与`Broker`连接,同时更新`topicQueueTable`、`brokerAddrTable`、`brokerLiveTable`、`filterServerTable`。 + +### 路由发现 +RocketMQ路由发现是非实时的,当Topic路由出现变化后,NameServer不会主动推送给客户端,而是由客户端定时拉取主题最新的路由。 + +其实就是一张图 +![](https://imgkr.cn-bj.ufileos.com/6ac077ee-bc03-4785-9200-426457563858.png) + + + + +## 长轮训 + +拉取长轮询分析:源码上实际上还是个监听器 +- RocketMQ未真正实现消息推模式,而是消费者主动向消息服务器拉取消息,RocketMQ推模式是循环向消息服务端发起消息拉取请求,如果消息消费者向RocketMQ拉取消息时,消息未到达消费队列时,如果不启用长轮询机制,则会在服务端等待shortPollingTimeMills时间后(挂起)再去判断消息是否已经到达指定消息队列,如果消息仍未到达则提示拉取消息客户端PULL—NOT—FOUND(消息不存在); +- 如果开启长轮询模式,RocketMQ一方面会每隔5s轮询检查一次消息是否可达,同时一有消息达到后立马通知挂起线程再次验证消息是否是自己感兴趣的消息,如果是则从CommitLog文件中提取消息返回给消息拉取客户端,否则直到挂起超时,超时时间由消息拉取方在消息拉取是封装在请求参数中,PUSH模式为15s,PULL模式通过DefaultMQPullConsumer#setBrokerSuspendMaxTimeMillis设置。RocketMQ通过在Broker客户端配置longPollingEnable为true来开启长轮询模式。 + + +说白了, push模式只不过在pull模式下加了个监控......哈哈哈哈 \ No newline at end of file diff --git a/Interview/classify/dis/Sentinel.md b/Interview/classify/dis/Sentinel.md new file mode 100644 index 00000000..26b077e9 --- /dev/null +++ b/Interview/classify/dis/Sentinel.md @@ -0,0 +1,35 @@ + +## 为什么选择Sentinel? +Sentinel是一个面向分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 + +隔离策略:信号量隔离(并发线程数限流) + +熔断策略: +1. 基于响应时间 +2. 异常比率 +3. 异常数 + +限流:基于QPS限流 + +控制台:查看秒级监控、机器发现等。 + +## 服务限流 +当**系统资源不够,不足以应对大量请求**,对系统按照预设的规则进行流量限制或功能限制 + +## 服务熔断 +当**调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务**,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 + +## 服务降级 +**为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级**,有策略的不处理或用简单的方式处理 + +## 为什么熔断降级 +系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 + +## 和Hystrix对比 +![sentinel和hystrix-qb3wFi](https://gitee.com/dreamcater/blog-img/raw/master/uPic/sentinel和hystrix-qb3wFi.png) + +**值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案**虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响**。 + +Sentinel 并发线程数限流不负责创建和管理线程池,而是**简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离**。 + +[官网补充](http://dubbo.apache.org/zh-cn/blog/sentinel-introduction-for-dubbo.html) \ No newline at end of file diff --git a/Interview/classify/dis/zookeeper.md b/Interview/classify/dis/zookeeper.md new file mode 100644 index 00000000..81e87ca1 --- /dev/null +++ b/Interview/classify/dis/zookeeper.md @@ -0,0 +1,40 @@ + +## 什么是Zookeeper? +ZooKeeper 是一个开源的分布式协调服务 + +## Zookeeper使用场景? +1. 数据发布/订阅、 +2. 负载均衡、 +3. 命名服务、 +4. 分布式协调/通知、 +5. 集群管理、 +6. Master 选举、 +7. 分布式锁和分布式队列等功能。 + +## Zookeeper的特点 +- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 +- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 +- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 +- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 + +## Zookeeper的原理? +ZAB 协议&Paxos算法 +ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 + +## 选择算法和流程 + + +先说: +> 每次投票会包含所推举的服务器的 myid 和 ZXID、epoch,使用(myid, ZXID,epoch)来表示 +> zxid,也就是事务 id,为了保证事务的顺序一致性,zookeeper 采用了递增的事务 id 号(zxid)来标识事务。 +> PK 1. 优先检查 ZXID。ZXID 比较大的服务器优先作为Leader 2. 如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器。 + +目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: + +1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 +2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 +3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 +4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 +5. 服务器5启动,后面的逻辑同服务器4成为follower。 + +[https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) \ No newline at end of file diff --git "a/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" new file mode 100644 index 00000000..e13c1969 --- /dev/null +++ "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" @@ -0,0 +1,39 @@ + +## paxos算法 +> 要讲这个算法,还要先扯背景:在常见的分布式系统中,总会发生诸如机器宕机或网络异常(等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。 + +> 其实在整个提议和投票过程当中,主要的角色就是“提议者”和“接受者” + +该算法大致流程:其实分为两个阶段 + +1. 因为存在多个“提议者”Proposer,如果都提意见,那么“接受者”Acceptor不就炸掉了嘛?到底接受谁啊?所以,要先明确哪个“提议者”是领袖,最厉害的那个,先把这个给挑出来。尽早的让意见统一,并且早点形成多数派。 +2. 由上阶段选出的意见领袖提出提议,“接受者”反馈意见。如果多数“接受者”接受了一个提议,那么这个提议就通过了。 + +[例子](https://ocavue.com/paxos.html#%E8%8A%82%E7%82%B9%E6%95%85%E9%9A%9C%E7%9A%84%E4%BE%8B%E5%AD%90) + +## ZAB +- ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。 +- 当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。 +- 当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 + +## zk的leader选举算法和流程 +目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: + +1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 +2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 +3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 +4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 +5. 服务器5启动,后面的逻辑同服务器4成为follower。 + +[https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) + + +## raft + +[https://zhuanlan.zhihu.com/p/66441389](https://zhuanlan.zhihu.com/p/66441389) + + + +## 分布式一致性哈希 + +[https://zhuanlan.zhihu.com/p/24440059](https://zhuanlan.zhihu.com/p/24440059) \ No newline at end of file diff --git "a/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" new file mode 100644 index 00000000..54053faa --- /dev/null +++ "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" @@ -0,0 +1,74 @@ + + +> 无非就是在分布式或者集群的环境下,业务上存在很多上游调用下游链,所以存在是了事务或者数据不一致的情况。 + +## 2PC +![2pc1-DMjtyZ](https://gitee.com/dreamcater/blog-img/raw/master/uPic/2pc1-DMjtyZ.jpg) + +--- +第一阶段: +- 协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。 +- 各个 参与者 节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向 协调者 报告说:“我这边可以处理了/我这边不能处理”。. +- 如果 参与者 成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,如果没有 参与者 成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。 + +图就不放了,很简单 +第二阶段:成功 +- 协调者 向 所有参与者 节点发出Commit请求. +- 参与者 收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。 + +缺点: +- 性能问题:无论是在第一阶段的过程中,还是在第二阶段,所有的参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,事务 协调者 才会通知进行全局提交,参与者 进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。 +- 单节点故障:由于协调者的重要性,一旦 协调者 发生故障。参与者 会一直阻塞下去。尤其在第二阶段,协调者 发生故障,那么所有的 参与者 还都处于锁定事务资源的状态中,而无法继续完成事务操作。(虽然协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) + +2PC出现单点问题的三种情况: +- 协调者正常,参与者宕机:由于 协调者 无法收集到所有 参与者 的反馈,会陷入阻塞情况。解决方案:引入超时机制,如果协调者在超过指定的时间还没有收到参与者的反馈,事务就失败,向所有节点发送终止事务请求。 +- 协调者宕机,参与者正常:无论处于哪个阶段,由于协调者宕机,无法发送提交请求,所有处于执行了操作但是未提交状态的参与者都会陷入阻塞情况.解决方案:引入协调者备份,同时协调者需记录操作日志.当检测到协调者宕机一段时间后,协调者备份取代协调者,并读取操作日志,向所有参与者询问状态。 +- 协调者和参与者都宕机 + + +## 3PC +对2pc的优化 + +- 引入超时机制 +- 在第一阶段和第二阶段中插入一个准备阶段,尝试获取数据库锁。如果可以就yes + +## TCC +Try-Confirm-Cancel +- 先是服务调用链路依次执行 Try 逻辑。 +- 如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务。 +- 如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作。 + +这就是所谓的 TCC 分布式事务。TCC 分布式事务的核心思想,说白了,就是当遇到下面这些情况时: +- 某个服务的数据库宕机了。 +- 某个服务自己挂了。 +- 那个服务的 Redis、Elasticsearch、MQ 等基础设施故障了。 +- 某些资源不足了,比如说库存不够这些。 + + +先来 Try 一下,不要把业务逻辑完成,先试试看,看各个服务能不能基本正常运转,能不能先冻结我需要的资源。 + +如果 Try 都 OK,也就是说,底层的数据库、Redis、Elasticsearch、MQ 都是可以写入数据的,并且你保留好了需要使用的一些资源(比如冻结了一部分库存)。 + +接着,再执行各个服务的 Confirm 逻辑,基本上 Confirm 就可以很大概率保证一个分布式事务的完成了。 + +那如果 Try 阶段某个服务就失败了,比如说底层的数据库挂了,或者 Redis 挂了,等等。 + +此时就自动执行各个服务的 Cancel 逻辑,把之前的 Try 逻辑都回滚,所有服务都不要执行任何设计的业务逻辑。保证大家要么一起成功,要么一起失败。 + +等一等,你有没有想到一个问题?如果有一些意外的情况发生了,比如说订单服务突然挂了,然后再次重启,TCC 分布式事务框架是如何保证之前没执行完的分布式事务继续执行的呢? + +所以,TCC 事务框架都是要记录一些分布式事务的活动日志的,可以在磁盘上的日志文件里记录,也可以在数据库里记录。保存下来分布式事务运行的各个阶段和状态。 + +问题还没完,万一某个服务的 Cancel 或者 Confirm 逻辑执行一直失败怎么办呢? + +那也很简单,TCC 事务框架会通过活动日志记录各个服务的状态。举个例子,比如发现某个服务的 Cancel 或者 Confirm 一直没成功,会不停的重试调用它的 Cancel 或者 Confirm 逻辑,务必要它成功! + +当然了,如果你的代码没有写什么 Bug,有充足的测试,而且 Try 阶段都基本尝试了一下,那么其实一般 Confirm、Cancel 都是可以成功的! + +## 可靠消息最终一致性 +在上面的通用方案设计里,完全依赖可靠消息服务的各种自检机制来确保: + +- 如果上游服务的数据库操作没成功,下游服务是不会收到任何通知。 +- 如果上游服务的数据库操作成功了,**可靠消息服务死活都会确保将一个调用消息投递给下游服务,而且一定会确保下游服务务必成功处理这条消息**。 + +通过这套机制,保证了基于 MQ 的异步调用/通知的服务间的分布式事务保障。其实阿里开源的 RocketMQ,就实现了可靠消息服务的所有功能,核心思想跟上面类似。 \ No newline at end of file diff --git "a/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\346\246\202\345\277\265.md" "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\346\246\202\345\277\265.md" new file mode 100644 index 00000000..492bc810 --- /dev/null +++ "b/Interview/classify/dis/\345\210\206\345\270\203\345\274\217\346\246\202\345\277\265.md" @@ -0,0 +1,9 @@ +## 集群、分布式和微服务的概念 +微服务是一种架构风格,一个大型复杂软件应用由**一个或多个微服务组成**。系统中的**各个微服务可被独立部署,各个微服务之间是松耦合的**。**每个微服务仅关注于完成一件任务并很好地完成该任务**。在所有情况下,每个任务代表着一个小的业务能力。 + +- 分布式将一个大的系统划分为多个业务模块,**业务模块分别部署到不同的机器上**,各个业务模块之间通过接口进行数据交互。区别**分布式的方式是根据不同机器不同业务**。 +- 集群模式是**不同服务器部署同一套服务对外访问,实现服务的负载均衡**。区别集群的方式是根据部署多台服务器业务是否相同。 +- 微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务。微服务与分布式的细微差别是,**微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器**。 + +## 什么是RPC? +RPC(RemoteProcedureCall)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A、B部署在两台不同的机器上,那么服务A如果想要调用服务B中的某个方法该怎么办呢?使用HTTP请求当然可以,但是可能会比较慢而且一些优化做的并不好。RPC的出现就是为了解决这个问题。 \ No newline at end of file diff --git "a/Interview/classify/dis/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md" "b/Interview/classify/dis/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md" new file mode 100644 index 00000000..e228756e --- /dev/null +++ "b/Interview/classify/dis/\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md" @@ -0,0 +1,7 @@ +## 布隆过滤器原理 +- 布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中”。 +- 布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0 +- 为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1” + +总结:当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中。 +[5分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!](https://juejin.im/post/5de1e37c5188256e8e43adfc) \ No newline at end of file diff --git "a/Interview/classify/dis/\351\231\220\346\265\201\347\256\227\346\263\225.md" "b/Interview/classify/dis/\351\231\220\346\265\201\347\256\227\346\263\225.md" new file mode 100644 index 00000000..0accc1cb --- /dev/null +++ "b/Interview/classify/dis/\351\231\220\346\265\201\347\256\227\346\263\225.md" @@ -0,0 +1,34 @@ + +> 在大型电商等系统中,我们回努力提升API的吞吐量和QPS(Query Per Second 每秒查询量),但总归有上限.为了应对巨大流量的瞬间提交,我们会做对应的限流处理. + +常见的限流算法有计数器,漏桶,令牌桶. + +## 计数器 + +计数器限流方式比较粗暴,**一次访问设置一次计数**,在系统内设置每秒的访问量,**超过访问量的访问直接丢弃**,实现访问限流.这种算法的弊端就是,在开始的时间,访问量被使用完后,1S内会有长时间的真空期是处于接口不可用的状态的. + +实现方式和拓展方式很多.比如可以使用redis进行1S的100次访问计数,来一个流量100-1当数量到达0时,拒绝后续的访问.也可以不拒绝而是将请求放入缓存队列,根据实际业务情况选择不同的实现方式. + +## 漏斗 + +在计数器算法中我们看到,当使用了所有的访问量后,接口会完全处于不可用状态.有些系统不喜欢这样的处理方式,可以选择漏斗算法进行限流. 漏斗算法的原理就像名字,是一个漏斗,**访问量从漏斗的大口进入,从漏斗的小口进入系统**.这样不管是多大的访问量进入漏斗,最后进入系统的访问量都是固定的.漏斗的好处就是,大批量访问进入时,漏斗有容量,不超过容量(容量的设计=固定处理的访问量*可接受等待时长)的数据都可以排队等待处理,超过的才会丢弃. + +实现方式可以使用队列,队列设置容量,访问可以大批量塞入队列,满队列后丢弃后续访问量.队列的出口以固定速率拿去访问量处理. + +这种方案由于出口速率是固定的,那么当就无法应对短时间的突发流量. + +## 令牌桶 + +令牌桶算法算是漏斗算法的改进版,为了处理短时间的突发流量而做了优化,令牌桶算法主要由三部分组成令牌流、数据流、令牌桶. + +- 令牌流:流通令牌的管道,用于生成的令牌的流通,放入令牌桶中. +- 数据流:进入系统的数据流量 +- 令牌桶:保存令牌的区域,可以理解为一个缓存区.令牌保存在这里用于使用. + +令牌桶算法会**按照一定的速率生成令牌放入令牌桶,访问要进入系统时,需要从令牌桶获取令牌,有令牌的可以进入,没有的被抛弃**.**由于令牌桶的令牌是源源不断生成的,当访问量小时,可以留存令牌达到令牌桶的上限,这样当短时间的突发访问量来时,积累的令牌数可以处理这个问题.当访问量持续大量流入时,由于生成令牌的速率是固定的,最后也就变成了类似漏斗算法的固定流量处理.** + +实现方式和漏斗也比较类似,可以使用一个队列保存令牌,一个定时任务用等速率生成令牌放入队列,访问量进入系统时,从队列获取令牌再进入系统. + +Google开源的guava包中RateLimiter类实现了令牌桶算法,不过这是单机的.集群可以按照上面的实现方式实现,队列使用中间件MQ实现,配合负载均衡算法,考虑集群各个服务器的承压情况做对应服务器的队列是比较建议的做法. + +[参考](https://zhuanlan.zhihu.com/p/95066428) \ No newline at end of file diff --git "a/Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/Interview/classify/jvm/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" similarity index 80% rename from "Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" rename to "Interview/classify/jvm/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" index 4ca3f599..00aa7025 100644 --- "a/Interview/sad/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" +++ "b/Interview/classify/jvm/JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" @@ -1,22 +1,24 @@ + +# JVM内存区域 > JVM这一块,经常还是经常被问到的 面试官:讲一讲JVM内存区域 我:行,先放两张图 -![1.8之前](http://media.dreamcat.ink/uPic/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B-1.8%E4%B9%8B%E5%89%8D.png) +![JVM内存模型-1.8之前](https://gitee.com/dreamcater/blog-img/raw/master/uPic/JVM内存模型-1.8之前-EKvhGB.png) -![1.8](http://media.dreamcat.ink/uPic/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B-1.8.png) +![JVM内存模型-1.8](https://gitee.com/dreamcater/blog-img/raw/master/uPic/JVM内存模型-1.8-y9XNlT.png) 总体来说,粗略的分为**堆和栈**,那么**栈是线程私有的**,而**堆是线程共享的**。那么**栈**又问分为**程序计数器**,**虚拟机栈**,**本地方法栈**。堆稍后再说,当然还有**方法区**,稍后单独说。 -1. **程序计数器** +## 程序计数器 - **字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制**,如:顺序执行、选择、循环、异常处理。 - 在多线程的情况下,**程序计数器用于记录当前线程执行的位置**,**从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了**。 - **程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域**,它的生命周期随着线程的创建而创建,随着线程的结束而死亡 -2. **虚拟机栈** +## 虚拟机栈 - 说白了,通俗的讲,主要是**对象中的方法产生的各种"材料"**。 - 因此,虚拟机栈存放的是**局部变量表**、**操作数栈**、**动态链接**、**方法出口**。 @@ -25,9 +27,13 @@ - **StackOverFlowError**: 若 J**ava 虚拟机栈的内存大小不允许动态扩展**,**那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候**,就抛出 StackOverFlowError 异常。 - **OutOfMemoryError**:若 **Java 虚拟机栈的内存大小允许动态扩展**,**且当线程请求栈时内存用完了**,**无法再动态扩展了**,此时抛出 OutOfMemoryError 异常。 -3. **本地方法栈**:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而**本地方法栈则为虚拟机使用到的 Native 方法服务**,JDK源码中很多本地方法哦。 +## 本地方法栈 + +虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而**本地方法栈则为虚拟机使用到的 Native 方法服务**,JDK源码中很多本地方法哦。 + +## 方法区 -4. 这里单独说一下**方法区**: +这里单独说一下**方法区**: **方法区与 Java 堆一样,是各个线程共享的内存区域**,它用于存储已被虚拟机加载的**类信息**、**常量**、**静态变量**、即时编译器编译后的代码等数据。不过随着版本的变化,会发生变化。 @@ -37,9 +43,13 @@ 方法区,依然会发生error,因为在之前的版本中,**当一个类启动的时候,也会加载很多class文件**,那么也会充满整个方法区,当满的时候,也会error的,当然,在以前的版本中,字符串常量池在方法区中,而**使用String.intern()方法,依然会占满空间并error**。 -5. **直接内存**并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现(如**DirectByteBuffer**)。本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 +## 直接内存 -6. **堆**:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 +**直接内存**并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现(如**DirectByteBuffer**)。本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 + +## 堆 + +**堆**:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 - 分为四区,分别为eden区、s0("From)区、s1("To")和tentired - 在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。 @@ -48,6 +58,8 @@ - 在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的相同存活的对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象(重点) - 再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。 +## Minor GC和Full GC + **Minor GC和Full GC触发条件**: - Minor GC触发条件:当Eden区满时,触发Minor GC。 diff --git "a/Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/Interview/classify/jvm/\345\236\203\345\234\276\345\233\236\346\224\266.md" similarity index 96% rename from "Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" rename to "Interview/classify/jvm/\345\236\203\345\234\276\345\233\236\346\224\266.md" index 33132ff8..f70f83ca 100644 --- "a/Interview/sad/\345\236\203\345\234\276\345\233\236\346\224\266.md" +++ "b/Interview/classify/jvm/\345\236\203\345\234\276\345\233\236\346\224\266.md" @@ -1,14 +1,20 @@ +# 垃圾回收 + > 这里可以主要讲CMS和G1,简单谈一些其他的 +## JVM垃圾回收 + 面试官:知道JVM垃圾回收机制吗? 我:知道,首先说一下垃圾回收算法,因为其中的一个分代收集,所以让堆又分为年轻代和老年代。 +## 垃圾回收算法 + 垃圾回收算法: 1. **标记-清除** -该算法分为**“标记”和“清除”**阶段:首先**标记出所有需要回收的对象**,在标记完成后**统一回收所有被标记的对象**。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: +该算法分为“标记”和“清除”阶段:首先**标记出所有需要回收的对象**,在标记完成后**统一回收所有被标记的对象**。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: - **效率问题** - **空间问题(标记清除后会产生大量不连续的碎片)** @@ -31,6 +37,8 @@ - **大对象直接进入老年代**,大对象就是需要**大量连续内存空间的对象**(比如:字符串、数组)。频繁复制降低性能。 - 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1. 对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被**晋升到老年代**中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 +## 如何判断死亡 + 面试官:如何判断对象死亡? 我:有两种策略,其一为**引用计数法**,其二为**可达性分析**。 @@ -62,11 +70,13 @@ Safepoint机制保证了程序执行时,在**不太长的时间内就会遇到可进入GC的Safepoint**,但**如果线程处于Sleep或者Blocked状态**,这时候线程**无法响应JVM的中断请求**,JVM也显然不太可能等待线程重新被分配CPU时间,这种情况就需要**安全域**来解决。**安全域是指在一段代码片段中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。这时候安全点就被扩展到了Safe Region**。 +## 垃圾回收器 + 面试官:给我讲讲垃圾收集器吧 我:当然没问题,有一张有趣的图 -![垃圾收集器](http://media.dreamcat.ink/uPic/iShot2020-08-29下午11.17.40.png) +![垃圾回收器-DSVRAO](https://gitee.com/dreamcater/blog-img/raw/master/uPic/垃圾回收器-DSVRAO.png) 小插曲:咱们知道,堆分为新生代和老年代,那么从这张图可以看出,新生代有Serial、ParNew和Parallel Scavenge而老年代自然也有Serial Old和Parallel Old,新生代和老年代都有串并行收集器,能互相搭配,但看CMS就很特殊,它是老年代的收集器,能从图中可以看出来,它不稳定呀,居然用Serial Old当备胎,而且为了能搭配CMS的并行垃圾收集器,就给它造了一个ParNew,哈哈哈(开个玩笑)。G1暂且不说,横跨新生和老年。在它这一块不分家,一把抓。 diff --git "a/Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" "b/Interview/classify/jvm/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" similarity index 74% rename from "Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" rename to "Interview/classify/jvm/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" index cc586a27..524d01cf 100644 --- "a/Interview/sad/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" +++ "b/Interview/classify/jvm/\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272\350\277\207\347\250\213.md" @@ -1,42 +1,46 @@ +# 对象的创建过程 + 面试官:类加载过程,你之前给我讲过,那么创建对象的过程你知道吗? 我:我似乎知道。 -上图![对象创建的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) +![Java创建对象的过程](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Java创建对象的过程-fC0wRb.png) -1. **类加载检查** +## 类加载检查 虚拟机遇到一条 **new** 指令时,首先将去检查这个指令的参数**是否能在常量池中定位到这个类的符号引用**,并且检查这个符号引用代表的**类是否已被加载过**、**解析和初始化过**。如果没有,那必须先执行相应的类加载过程。 -2. **分配内存** +## 分配内存 在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。 - **指针碰撞** - - **堆规整(没有内存碎片)** - - 复制算法 - - GC:Serial、ParNew + - **堆规整(没有内存碎片)** + - 复制算法 + - GC:Serial、ParNew - **空闲列表** - - **堆内存不规整的情况下** - - 虚拟机会维护一个**列表**,该列表会**记录哪些内存块是可用的**,在分配的时候,找一块儿足够大的内存块来划分给对象实例,最后更新列表激励 - - GC:CMS + - **堆内存不规整的情况下** + - 虚拟机会维护一个**列表**,该列表会**记录哪些内存块是可用的**,在分配的时候,找一块儿足够大的内存块来划分给对象实例,最后更新列表激励 + - GC:CMS - **并发问题** - - **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** - - **TLAB:** 为**每一个线程预先在 Eden 区分配一块儿内存**,JVM 在给线程中的对象分配内存时,**首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配** + - **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** + - **TLAB:** 为**每一个线程预先在 Eden 区分配一块儿内存**,JVM 在给线程中的对象分配内存时,**首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配** -3. **初始化零值** +## 初始化零值 内存分配完成后,虚拟机需要将分配到的内存空间都**初始化为零值(不包括对象头)**,这一步操作保证了对象的实例字段在 Java 代码中**可以不赋初始值就直接使用**,程序能访问到这些字段的数据类型所对应的零值。 -4. **设置对象头** +## 设置对象头 初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 -5. **指向init方法** +## 指向init方法 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,**方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行**方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 +## 内存布局 + 面试官:刚才说了内存布局,给我讲一下 我:好的,内存布局分别为**对象头,实例数据,对其填充** @@ -53,19 +57,21 @@ **对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 +## 句柄的访问方式 + 面试官:给我讲讲对象的访问方式 我:明白,两种:使用句柄和使用直接指针 1. **句柄**: -![使用句柄](http://media.dreamcat.ink/uPic/使用句柄.png) +![使用句柄-f4Cw9x](https://gitee.com/dreamcater/blog-img/raw/master/uPic/使用句柄-f4Cw9x.png) 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了**对象实例数据**与**类型数据**各自的具体地址信息; 2. **直接指针**: -![直接指针](http://media.dreamcat.ink/uPic/直接指针.png) +![直接指针-8m3HHz](https://gitee.com/dreamcater/blog-img/raw/master/uPic/直接指针-8m3HHz.png) 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。 diff --git "a/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" similarity index 97% rename from "Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" rename to "Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" index 42aceebc..3ebaa2b5 100644 --- "a/Interview/sad/\347\261\273\345\212\240\350\275\275\345\231\250.md" +++ "b/Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" @@ -1,3 +1,6 @@ + +## 类加载器 + 面试官:谈谈类加载器吧 我:行,那还不得介绍三个类加载器? @@ -6,11 +9,13 @@ - ExtensionClassLoader(扩展类加载器):主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。 - AppClassLoader(应用程序类加载器) +## 双亲委派 + 我可能直接扯双亲委派了 每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,**系统会首先判断当前类是否被加载过**。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求**委派该父类加载器**的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。 -![ClassLoader](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS图片.png) +![类加载器](https://gitee.com/dreamcater/blog-img/raw/master/uPic/类加载器-Z1WdFt.png) 可以按图说话 diff --git "a/Interview/sad/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" "b/Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" similarity index 91% rename from "Interview/sad/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" rename to "Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" index 8bb6da91..e5523bff 100644 --- "a/Interview/sad/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" +++ "b/Interview/classify/jvm/\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" @@ -1,8 +1,12 @@ +# 类加载过程 + 面试官:谈一谈类加载过程 我:加载->验证->准备->解析->初始化 -### 加载 +![类加载过程](https://gitee.com/dreamcater/blog-img/raw/master/uPic/类加载过程-5A2XIO.png) + +## 加载 类加载过程的第一步,主要完成下面3件事情: @@ -10,25 +14,25 @@ - 将字节流所代表的**静态存储结构**转换为方法区的**运行时数据结构** - 在内存中生成一个代表该类的 **Class 对象**,作为**方法区这些数据的访问入口** -### 验证 +## 验证 - 文件格式验证:主要验证Class文件**是否规范**等。 - 元数据验证:对字节码描述的信息**语义分析**等。 - 字节码验证:确保语义是ok的。 - 符号引用验证:确保解析动作能执行。 -### 准备 +## 准备 **准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: - 这时候进行内存分配的仅包括**类变量**(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 - 这里所设置的初始值"通常情况"下是数据类型默认的**零值**(如0、0L、null、false等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 **fianl 关键字**`public static final int value=111` ,那么准备阶段 value 的值就被复制为 111。 -### 解析 +## 解析 解析阶段是虚拟机将常量池内的**符号引用替换为直接引用**的过程,也就是得到**类或者字段、方法在内存中的指针或者偏移量。** -### 初始化 +## 初始化 初始化是类加载的最后一步,也是真正执行类中定义的 **Java 程序代码**(字节码),初始化阶段是执行**类构造器** ` ()`方法的过程。 diff --git "a/Interview/sad/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/Interview/classify/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" similarity index 94% rename from "Interview/sad/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" rename to "Interview/classify/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" index 984563d2..0dc445ff 100644 --- "a/Interview/sad/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" +++ "b/Interview/classify/jvm/\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" @@ -1,6 +1,8 @@ + + > 这里简要放一些重要的 -### 类文件结构 +## 类文件结构 ```java ClassFile { @@ -23,29 +25,29 @@ ClassFile { } ``` -### 静态常量池 +## 静态常量池 - 字面量 - 符号引用 - - 类和接口的全限定名 - - 字段的名称和描述符 - - 方法的名称和描述符 + - 类和接口的全限定名 + - 字段的名称和描述符 + - 方法的名称和描述符 - 好处:**常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享**。 -### 运行时常量池 +## 运行时常量池 当Class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到运行时常量池里,在静态常量池的**符号引用有一部分是会被转变为直接引用**的,比如说类的**静态方法或私有方法,实例构造方法,父类方法**,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的**一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的**。 -### 字符串常量池 +## 字符串常量池 **字符串常量池的存在使JVM提高了性能和减少了内存开销**。 - 每当我们使用字面量(String s=“1”;)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。 - 每当我们使用关键字new(String s=new String(”1”);)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s。 -### 版本变化 +## 版本变化 -#### 1.6 +### 1.6 - 静态常量池在Class文件中。 - 运行时常量池在Perm Gen区(也就是方法区)中。 diff --git "a/Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" "b/Interview/classify/jvm/\351\200\203\351\200\270\345\210\206\346\236\220.md" similarity index 99% rename from "Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" rename to "Interview/classify/jvm/\351\200\203\351\200\270\345\210\206\346\236\220.md" index a8ddcd24..3f313e0d 100644 --- "a/Interview/sad/\351\200\203\351\200\270\345\210\206\346\236\220.md" +++ "b/Interview/classify/jvm/\351\200\203\351\200\270\345\210\206\346\236\220.md" @@ -1,3 +1,5 @@ +# 逃逸分析 + > 这一块知识还是要知道的呀,它是Java虚拟机中比较前沿优化的技术。 面试官:你了解逃逸分析吗? diff --git "a/Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" b/Interview/classify/mysql/ACID.md similarity index 94% rename from "Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" rename to Interview/classify/mysql/ACID.md index 7048090d..b94138eb 100644 --- "a/Interview/sad/MySQL\347\232\204ACID\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" +++ b/Interview/classify/mysql/ACID.md @@ -1,46 +1,53 @@ + +## ACID + > 这个问题经常问,但是问我的比较少,其实MySQL这一块,问的最多还是优化问题 面试官:聊聊ACID是什么 我:分别是:**原子性(Atomicity)**、**一致性(Consistency)**、**隔离性(Isolation)**和**持久性(Durability)**。 -1. 原子性: +### 原子性 根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态!比如:如果不保证原子性,OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~。可能会聊undolog -2. 一致性: +### 一致性 根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!**比如:A给B转100,B不能因为通过这个事务增加了150吧?或者A扣了150吧? -3. 隔离性: +### 隔离性 根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 -4. 持久性: +### 持久性 根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。这里可能会让你聊redolog +## 并发事务带来的问题 + 面试官:并发事务带来的问题都有哪些 我:**脏读、不可重复读和幻读(实际上还有一个丢弃修改)** -1. **脏读** +### 脏读 第一个事务首先读取变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取为100,此时第一个事务做了回滚。最终第二个事务读取的变量和数据库的不一样。 -2. **丢弃修改** +### 丢弃修改 T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,**T2 的修改覆盖了 T1 的修改**。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 -3. **不可重复读** +### 不可重复读 T2 读取一个数据,T1 对该数据做了修改并提交。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 -4. **幻读** +### 幻读 T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和第一次读取的结果不同。(和不可重复读的区别:一个是变量变化,一个是范围变化) -面试官:数据库的隔离界别? +## 数据库的隔离级别 + +面试官:数据库的隔离级别? 我: @@ -50,19 +57,19 @@ T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 -1. 未提交读 +### 未提交读 事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** -2. 提交读 +### 提交读 对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -3. 可重复读 +### 可重复读 就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** -4. 可串行读 +### 可串行读 在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** diff --git "a/Interview/sad/InnoDB\345\222\214MyISAM.md" "b/Interview/classify/mysql/Innodb\344\270\216MyISAM.md" similarity index 97% rename from "Interview/sad/InnoDB\345\222\214MyISAM.md" rename to "Interview/classify/mysql/Innodb\344\270\216MyISAM.md" index c03b50a3..281419a4 100644 --- "a/Interview/sad/InnoDB\345\222\214MyISAM.md" +++ "b/Interview/classify/mysql/Innodb\344\270\216MyISAM.md" @@ -1,10 +1,14 @@ + + > 这个问题简单回答一下即可 +## MySQL的引擎 + 面试官:MySQL的引擎都有哪些? 我:我知道,MySQL内部可以分为服务层和存储引擎层两部分:**服务层包括连接器、查询缓存、分析器、优化器、执行器等;存储引擎层负责数据的存储和提取**。我就说一下自己了解的InnoDB和MyISAM引擎 -**InnoDB**: +## InnoDB - 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 - 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ (Next-Key Locking)**防止幻影读**。 @@ -12,27 +16,31 @@ - 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 - 支持真正的**在线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 -**MyISAM**: +## MyISAM - 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 - 提供了大量的特性,包括**压缩表、空间数据索引**等。 - **不支持事务**。 - **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 -我一般还会回答一个**索引文件**上的区别 +## 索引文件 +我一般还会回答一个**索引文件**上的区别 +### MyISAM -**MyISAM**: 1. MyISAM**索引文件和数据文件是分离**的,**索引文件仅保存数据记录的地址**,同样使用B+Tree作为索引结构,叶节点的**data域存放的是数据记录的地址** 2. 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 3. MyISAM中索引检索的算法为**首先按照B+Tree搜索算法搜索索引**,**如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录** -**InnoDB**: +### InnoDB + 1. **InnoDB的数据文件本身就是索引文件**,这棵树的叶节点**data域保存了完整的数据记录**(聚集索引) 2. InnoDB的**辅助索引data域存储相应记录主键的值而不是地址** 3. **聚集索引这种实现方式使得按主键的搜索十分高效**,**但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录**。 +## 分页查询 + 其实个人还知道一点,分页查询的时候还有一点区别,这点区别也是根据索引文件的区别来的。 咱们知道,使用limit分页查询,offset越大,性能越差,比如: diff --git "a/Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" "b/Interview/classify/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" similarity index 99% rename from "Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" rename to "Interview/classify/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" index cc6c2b96..5354f2a7 100644 --- "a/Interview/sad/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" +++ "b/Interview/classify/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" @@ -1,5 +1,9 @@ +[toc] + > 一个是索引优化,一个是结构优化。。。。 +## 三范式 + 面试官:先来个**三范式** 我:好的 @@ -48,7 +52,7 @@ - char类型是定长的,char类型的最大宽度为255 - 场景:varchar适用于存储很少被更新的字符串列;char适合存储长度近似的值,适合存储短字符串,适合存储经常更新的字符串 -**分库分表**: +## 分库分表 1. **垂直分表** diff --git a/Interview/sad/MVCC,redolog,undolog,binlog.md "b/Interview/classify/mysql/MySQL\346\227\245\345\277\227\346\226\207\344\273\266.md" similarity index 97% rename from Interview/sad/MVCC,redolog,undolog,binlog.md rename to "Interview/classify/mysql/MySQL\346\227\245\345\277\227\346\226\207\344\273\266.md" index 9e485a3b..26ccd5ed 100644 --- a/Interview/sad/MVCC,redolog,undolog,binlog.md +++ "b/Interview/classify/mysql/MySQL\346\227\245\345\277\227\346\226\207\344\273\266.md" @@ -1,9 +1,11 @@ +# MySQL的日志文件 + 面试官:分别讲一下MySQL的几大文件,你懂的 我:我不懂,ok,好的。 - undoLog 也就是我们常说的**回滚日志文件** 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由**引擎层的InnoDB引擎实现**,是**逻辑日志**,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,**undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事务用到该版本的信息时才可以清理相应undolog**。它保存了事务发生之前的数据的一个版本,用于回滚,**同时可以提供多版本并发控制下的读(MVCC)**,也即非锁定读。 -- redoLog 是重做日志文件是**记录数据修改之后的值**,**用于持久化到磁盘中**。redo log包括两部分:**一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的**;二是**磁盘上的重做日志文件(redo log file)**,该部分日志是持久的。由引**擎层的InnoDB引擎实现**,是**物理日志**,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。 +- redoLog 是重做日志文件是**记录数据修改之后的值**,**用于持久化到磁盘中**。redo log包括两部分:**一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的**;二是**磁盘上的重做日志文件(redo log file)**,该部分日志是持久的。由引**擎层的InnoDB引擎实现**,是**物理日志**,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。[参考](https://juejin.im/post/6844903573910716430) - binlog由**Mysql的Server层实现**,是**逻辑日志**,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:**事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复**。 - MVCC多版本并发控制是MySQL中基于**乐观锁理论实现隔离级别**的方式,用于**读已提交和可重复读**取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:**最近修改该行数据的事务ID**,**指向该行(undolog表中)回滚段的指针**。**Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表**。 diff --git "a/Interview/classify/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\347\232\204.md" "b/Interview/classify/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\347\232\204.md" new file mode 100644 index 00000000..3021ee41 --- /dev/null +++ "b/Interview/classify/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\347\232\204.md" @@ -0,0 +1,20 @@ + +## SQL执行顺序 +SQL的执行顺序:from---where--group by---having---select---order by + +## MySQL是如何执行一条SQL的 +![SQL执行的全部过程-MlS1d5](https://gitee.com/dreamcater/blog-img/raw/master/uPic/SQL执行的全部过程-MlS1d5.png) + +**MySQL内部可以分为服务层和存储引擎层两部分:** + +1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 +2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 + +**Server层按顺序执行sql的步骤为**: +客户端请求: +- **连接器**(验证用户身份,给予权限) +- **查询缓存**(存在缓存则直接返回,不存在则执行后续操作) +- **分析器**(对SQL进行词法分析和语法分析操作) +- **优化器**(主要对执行的sql优化选择最优的执行方案方法) +- **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) +- **去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) \ No newline at end of file diff --git "a/Interview/sad/MySQL\347\232\204\351\224\201.md" "b/Interview/classify/mysql/MySQL\347\232\204\351\224\201.md" similarity index 99% rename from "Interview/sad/MySQL\347\232\204\351\224\201.md" rename to "Interview/classify/mysql/MySQL\347\232\204\351\224\201.md" index 35b48f04..406db529 100644 --- "a/Interview/sad/MySQL\347\232\204\351\224\201.md" +++ "b/Interview/classify/mysql/MySQL\347\232\204\351\224\201.md" @@ -1,3 +1,5 @@ +# MySQL的锁 + > MySQL的锁,其实跟Java差不了,一个思想。 面试官:MySQL的锁,介绍一下 @@ -5,18 +7,22 @@ 我: MyISAM:MyISAM只有表锁,其中又分为共享读锁和独占写锁。 + - MyISAM表的读操作,不会阻塞其他用户对同一个表的读请求,但会阻塞对同一个表的写请求。 - MyISAM表的写操作,会阻塞其他用户对同一个表的读和写操作。 - MyISAM表的读、写操作之间、以及写操作之间是串行的。 Innodb行锁:共享锁,排他锁 + - 对于UPDATE、DELETE、INSERT语句,Innodb会自动给涉及的数据集加排他锁(X);对于普通SELECT语句,Innodb不会加任何锁。 + ```sql //显示共享锁(S) : SELECT * FROM table_name WHERE .... LOCK IN SHARE MODE //显示排他锁(X): SELECT * FROM table_name WHERE .... FOR UPDATE. ``` + - 记录锁(Record Locks):记录锁是封锁记录,记录锁也叫行锁,注意:行锁是针对索引的,如果表中没有索引,那么就会锁整张表 - 间隙锁(GAP)对于键值在条件范围内但并不存在的记录,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。 - 临键锁(Next-Key Lock):(Record Locks+GAP),锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 @@ -52,7 +58,9 @@ int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock); WHERE id=#{orderId} AND version=#{oldVersion} ``` + 如 + ```java // orderId:订单id // getVersion:获取数据库版本号,旧版本 diff --git "a/Interview/sad/MySQL\347\264\242\345\274\225.md" "b/Interview/classify/mysql/\347\264\242\345\274\225.md" similarity index 97% rename from "Interview/sad/MySQL\347\264\242\345\274\225.md" rename to "Interview/classify/mysql/\347\264\242\345\274\225.md" index e121d4c6..7f730414 100644 --- "a/Interview/sad/MySQL\347\264\242\345\274\225.md" +++ "b/Interview/classify/mysql/\347\264\242\345\274\225.md" @@ -1,7 +1,11 @@ + + > 这一块,最好能知道怎么优化 面试官:索引介绍一下 +## 索引类型 + 我:ok,先说一下**索引类型**: - FULLTEXT:即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 @@ -9,6 +13,8 @@ - BTREE:BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 - RTREE:RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 +## 索引种类 + 再说一下**索引种类**: - 普通索引:仅加速查询 @@ -20,14 +26,18 @@ - 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 - 聚簇索引:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) +## 索引结构 + 其次说**索引结构**: **MyISAM**: + 1. MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址,同样使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址 2. 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 3. MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录 **InnoDB**: + 1. InnoDB的数据文件本身就是索引文件,这棵树的叶节点data域保存了完整的数据记录(聚集索引) 2. InnoDB的辅助索引data域存储相应记录主键的值而不是地址 3. 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。 @@ -38,7 +48,8 @@ - 二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。 - 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。 - 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。 -- B+树:在B树的基础上,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**;此外将叶节点使用指针连接成链表,范围查询更加高效。B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点, +- B+树:在B树的基础上,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**;此外将叶节点使用指针连接成链表,范围查询更加高效。B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点。 +- [B+树的高度](https://www.jianshu.com/p/544e97672deb) 补充B树: @@ -49,6 +60,8 @@ B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。 - 搜索有可能在非叶子结点结束; - 其搜索性能等价于在关键字全集内做一次二分查找; +## 索引失效 + 查询什么时候不走**索引**: 1. **模糊查询 %like** @@ -60,6 +73,8 @@ B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。 7. 需要回表的查询结果集过大(超过配置的范围) 8. **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** +## 索引最左 + **索引最左原则:** **举例子**: @@ -72,7 +87,7 @@ B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。 **组合索引的底层其实按照第一个索引排序,从排序里面查第二个索引,以此类推。如果第一个索引失效,或者没有经过第一个索引,后面没发在前面的基础上查询。** -**为什么使用索引?** +## 为什么使用索引 - 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 - 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 @@ -88,6 +103,8 @@ B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。 **你想,如果某个场景,发送10条请求,9条写,1条读。 加索引岂不是在浪费效率和空间?** +## explain + 面试官:聊聊**explain** 我:好的,不过这一块内容好多,我只说几个关键的吧 @@ -103,6 +120,8 @@ B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。 9. rows: 表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,理论上行数越少,查询性能越好 10. Extra:该列包含MySQL解决查询的详细信息 +## 慢查询优化 + 面试官:慢查询优化 我:我试试 diff --git a/Interview/classify/net/DNS.md b/Interview/classify/net/DNS.md new file mode 100644 index 00000000..6ced16f3 --- /dev/null +++ b/Interview/classify/net/DNS.md @@ -0,0 +1,42 @@ + +## DNS + +### DNS是什么 + +**官方解释**:DNS(Domain Name System,域名系统),因特网上作为**域名和IP地址相互映射**的一个**分布式数据库**,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 + +**通俗的讲**,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2。 + +### 谈谈DNS解析过程 +![DNS解析过程-eiVd6a](https://gitee.com/dreamcater/blog-img/raw/master/uPic/DNS解析过程-eiVd6a.png) + +- 请求一旦发起,若是chrome浏览器,先在浏览器找之前**有没有缓存过的域名所对应的ip地址**,有的话,直接跳过dns解析了,若是没有,就会**找硬盘的hosts文件**,看看有没有,有的话,直接找到hosts文件里面的ip + +[字节问了修改hosts,浏览器会变吗?](https://blog.csdn.net/woshizhangliang999/article/details/51457864) + +- 如果本地的hosts文件没有能的到对应的ip地址,浏览器会发出一个**dns请求到本地dns服务器**,**本地dns服务器一般都是你的网络接入服务器商提供**,比如中国电信,中国移动等。 +- 查询你输入的网址的DNS请求到达本地DNS服务器之后,**本地DNS服务器会首先查询它的缓存记录**,如果缓存中有此条记录,就可以直接返回结果,此过程是**递归的方式进行查询**。如果没有,本地DNS服务器还要向**DNS根服务器**进行查询。 +- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 +- 最后,本地DNS服务器向**域名的解析服务器**发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。 + +### DNS查询方式 + +#### 递归解析 + +当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。此时有两种方式。**局部DNS服务器自己负责向其他DNS服务器进行查询,一般是先向该域名的根域服务器查询,再由根域名服务器一级级向下查询**。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。 + +#### 迭代解析 + +当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析。局部DNS服务器不是自己向其他DNS服务器进行查询,**而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序**,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。 + +### DNS负载均衡 + +当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。处理办法就是用DNS负载均衡技术,它的原理是在**DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器**,从而达到负载均衡的目的。例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等。 + +### 为什么域名解析用UDP协议? + +因为UDP快啊!UDP的DNS协议只要一个请求、一个应答就好了。而使用基于TCP的DNS协议要三次握手、发送数据以及应答、四次挥手。但是UDP协议传输内容不能超过512字节。不过客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。 + +### 为什么区域传送用TCP协议? + +因为TCP协议可靠性好啊!你要从主DNS上复制内容啊,你用不可靠的UDP? 因为TCP协议传输的内容大啊,你用最大只能传512字节的UDP协议?万一同步的数据大于512字节,你怎么办? \ No newline at end of file diff --git "a/Interview/classify/net/HTTP\345\222\214HTTPS.md" "b/Interview/classify/net/HTTP\345\222\214HTTPS.md" new file mode 100644 index 00000000..9e0aa524 --- /dev/null +++ "b/Interview/classify/net/HTTP\345\222\214HTTPS.md" @@ -0,0 +1,212 @@ + +## HTTP + +### GET和POST的区别? + +1. GET使用URL或Cookie传参,而POST将数据放在BODY中 +2. GET方式提交的数据有长度限制,则POST的数据则可以非常大 +3. POST比GET安全,因为数据在地址栏上不可见,没毛病 +4. **本质区别**:GET请求是幂等性的,POST请求不是。 + +> 这里的幂等性:幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。 + +正因为它们有这样的区别,所以不应该且**不能用get请求做数据的增删改这些有副作用的操作**。因为get请求是幂等的,**在网络不好的隧道中会尝试重试**。如果用get请求增数据,会有**重复操作**的风险,而这种重复操作可能会导致副作用(浏览器和操作系统并不知道你会用get请求去做增操作)。 + +### 响应码 + +#### 1xx 信息 + +**100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 + +#### 2xx 成功 + +- **200 OK** +- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 +- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 + +#### 3xx 重定向 + +- **301 Moved Permanently** :永久性重定向 +- **302 Found** :临时性重定向 +- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 +- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 +- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 + +#### 4xx 客户端错误 + +- **400 Bad Request** :请求报文中存在语法错误。 +- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 +- **403 Forbidden** :请求被拒绝。没有权限 +- **404 Not Found**:路由不存在,或者没找到 + +#### 5xx 服务器错误 + +- **500 Internal Server Error** :服务器正在执行请求时发生错误。 +- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 + + +### HTTP首部 + +> 这块有点多,可参考[http首部](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/计算机网络原理-http那些事儿.md#http首部) + +### Cookies + +HTTP 协议是**无状态**的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 + +Cookie 是**服务器发送到用户浏览器并保存在本地的一小块数据**,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 + +用途 + +- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) +- 个性化设置(如用户自定义设置、主题等) +- 浏览器行为跟踪(如跟踪分析用户行为等) + + +### Session + +除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 + +Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 + +### Cookie和Session的选择 + +- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; +- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; +- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 + +### JWT + +JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 + +cookie+session这种模式通常是保存在**内存**中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,**只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可**。 + +**JWT的构成**: + +第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。详情请见[官网](https://jwt.io/introduction/) + +**JWT总结**: + +1. 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。 +2. payload部分,JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。 +3. 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。它不需要在服务端保存会话信息, 所以它易于应用的扩展。 + +[jwt优缺点](https://snailclimb.gitee.io/javaguide/#/docs/system-design/authority-certification/JWT-advantages-and-disadvantages) + +### 浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开? + +在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。所以虽然标准中没有设定,**某些服务器对 Connection: keep-alive 的 Header 进行了支持**。 + +**持久连接**:既然维持 TCP 连接好处这么多,HTTP/1.1 就把 Connection 头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。 + + +### 一个TCP连接可以对应几个HTTP请求? + +如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。 + +### 一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)? + +HTTP/1.1 存在一个问题,单个 TCP 连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。 + +在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的。在 HTTP2 中由于 Multiplexing 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行。 + +那么在 HTTP/1.1 时代,浏览器是如何提高页面加载效率的呢?主要有下面两点: + +- 维持和服务器已经建立的 TCP 连接,在同一连接上顺序处理多个请求。 +- 和服务器建立多个 TCP 连接。 + +### 为什么有的时候刷新页面不需要重新建立 SSL 连接? + +TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。 + +### 浏览器对同一 Host 建立 TCP 连接到数量有没有限制? + +**有。Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。** + + +### 在浏览器中输入url地址后显示主页的过程? + +> - 根据域名,进行DNS域名解析; +> - 拿到解析的IP地址,建立TCP连接; +> - 向IP地址,发送HTTP请求; +> - 服务器处理请求; +> - 返回响应结果; +> - 关闭TCP连接; +> - 浏览器解析HTML; +> - 浏览器布局渲染; + + +### HTTP1.x的缺点 +1. HTTP/1.0 一次只允许在一个TCP连接上发起一个请求,HTTP/1.1使用的流水线技术也只能部分处理请求分析,仍然会存在队列头阻塞问题,因此客户端在需要发起多次请求时,通常会采用建立多连接来减少延迟 +2. 单向请求,只能由客户端发起 +3. 请求报文与响应报文首部信息冗余量大。 +4. 数据未压缩,导致数据的传输量大。 + +### HTTP2.0有哪些改动 +1. 多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。 +2. 二进制分帧:应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。 +3. 首部压缩(Header Compression) +4. 服务端推送(Server Push) + + +## HTTPS + +### HTTPS是什么 + +HTTPS 并不是新协议,而是让 **HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信**。通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 + +### HTTP的缺点 + +- 使用明文进行通信,内容可能会被窃听; +- 不验证通信方的身份,通信方的身份有可能遭遇伪装; +- 无法证明报文的完整性,报文有可能遭篡改。 + +### 对称密钥加密 + +对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 + +- 优点:运算速度快 +- 缺点:无法安全地将密钥传输给通信方 + +### 非对称密钥加密 + +非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 + +公开密钥所有人都可以获得,**通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密**,**接收方收到通信内容后使用私有密钥解密**。 + +非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 + +- 优点:可以更安全地将公开密钥传输给通信发送方; +- 缺点:运算速度慢。 + +### HTTPS采用的加密方式 + +HTTPS 采用混合的加密机制,使用**非对称密钥加密用于传输对称密钥来保证传输过程的安全性**,之后使用**对称密钥加密进行通信来保证通信过程的效率**。DES+RSA + +![rsa原理-rWg4oK](https://gitee.com/dreamcater/blog-img/raw/master/uPic/rsa原理-rWg4oK.png) + +确保传输安全过程(其实就是rsa原理): + +1. Client给出**协议版本号**、一个客户端生成的**随机数**(Client random),以及客户端支持的**加密方法**。 +2. Server确认双方使用的**加密方法**,并给出**数字证书**、以及一个服务器生成的**随机数**(Server random)。 +3. Client确认**数字证书有效**,然后生成一个新的**随机数**(Premaster secret),并使用**数字证书中的公钥,加密这个随机数**,发给Server。 +4. Server使用自己的**私钥,获取Client发来的随机数**(Premaster secret)。 +5. Client和Server根据约定的加密方法,使用前面的**三个随机数,生成”对话密钥”**(session key),用来加密接下来的整个对话过程。 + +### 认证 + +通过使用 **证书** 来对通信方进行认证。 + +数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。 + +服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 + +进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 + +[加密全套流程](https://www.cnblogs.com/handsomeBoys/p/6556336.html) + +[https://www.cnblogs.com/xdyixia/p/11610102.html](https://www.cnblogs.com/xdyixia/p/11610102.html) + +### HTTPS的缺点 + +- 因为需要进行加密解密等过程,因此速度会更慢; +- 需要支付证书授权的高额费用。 \ No newline at end of file diff --git "a/Interview/classify/net/TCP\345\222\214UDP.md" "b/Interview/classify/net/TCP\345\222\214UDP.md" new file mode 100644 index 00000000..56eb065d --- /dev/null +++ "b/Interview/classify/net/TCP\345\222\214UDP.md" @@ -0,0 +1,152 @@ + +## TCP/UDP + +### TCP + +#### TCP是什么? + +`TCP(Transmission Control Protocol 传输控制协议)`是一种面向连接的、可靠的、基于字节流的传输层通信协议。 + +#### TCP头部报文 + +##### source port 和 destination port + +> 两者分别为「源端口号」和「目的端口号」。源端口号就是指本地端口,目的端口就是远程端口。 + +可以这么理解,我们有很多软件,每个软件都对应一个端口,假如,你想和我数据交互,咱们得互相知道你我的端口号。 + +再来一个很官方的: + +> 扩展:应用程序的端口号和应用程序所在主机的 IP 地址统称为 socket(套接字),IP:端口号, 在互联网上 socket 唯一标识每一个应用程序,源端口+源IP+目的端口+目的IP称为”套接字对“,一对套接字就是一个连接,一个客户端与服务器之间的连接。 + +##### Sequence Number + +> 称为「序列号」。用于 TCP 通信过程中某一传输方向上字节流的每个字节的编号,为了确保数据通信的有序性,避免网络中乱序的问题。接收端根据这个编号进行确认,保证分割的数据段在原始数据包的位置。初始序列号由自己定,而后绪的序列号由对端的 ACK 决定:SN_x = ACK_y (x 的序列号 = y 发给 x 的 ACK)。 + +说白了,类似于身份证一样,而且还得发送此时此刻的所在的位置,就相当于身份证上的地址一样。 + +##### Acknowledge Number + +> 称为「确认序列号」。确认序列号是接收确认端所期望收到的下一序列号。确认序号应当是上次已成功收到数据字节序号加1,只有当标志位中的 ACK 标志为 1 时该确认序列号的字段才有效。主要用来解决不丢包的问题。 + +##### TCP Flag + +`TCP` 首部中有 6 个标志比特,它们中的多个可同时被设置为 `1`,主要是用于操控 `TCP` 的状态机的,依次为`URG,ACK,PSH,RST,SYN,FIN`。 + +当然只介绍三个: + +1. **ACK**:这个标识可以理解为发送端发送数据到接收端,发送的时候 ACK 为 0,标识接收端还未应答,一旦接收端接收数据之后,就将 ACK 置为 1,发送端接收到之后,就知道了接收端已经接收了数据。 +2. **SYN**:表示「同步序列号」,是 TCP 握手的发送的第一个数据包。用来建立 TCP 的连接。SYN 标志位和 ACK 标志位搭配使用,当连接请求的时候,SYN=1,ACK=0连接被响应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有 SYN 的数据包,如果对方主机响应了一个数据包回来 ,就表明这台主机存在这个端口。 +3. **FIN**:表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的 TCP 数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描。发送端只剩最后的一段数据了,同时要告诉接收端后边没有数据可以接受了,所以用FIN标识一下,接收端看到这个FIN之后,哦!这是接受的最后的数据,接受完就关闭了;**TCP四次分手必然问**。 + +##### Window size + +> 称为滑动窗口大小。所说的滑动窗口,用来进行流量控制。 + +### TCP三次握手 + +![TCP三次握手-2ujCx2](https://gitee.com/dreamcater/blog-img/raw/master/uPic/TCP三次握手-2ujCx2.svg) + +- **初始状态**:客户端处于 `closed(关闭)`状态,服务器处于 `listen(监听)` 状态。 +- **第一次握手**:客户端发送请求报文将 `SYN = 1`同步序列号和初始化序列号`seq = x`发送给服务端,发送完之后客户端处于`SYN_Send`状态。 +- **第二次握手**:服务端收到 `SYN` 请求报文之后,如果同意连接,会以自己的同步序列号`SYN(服务端) = 1`、初始化序列号 `seq = y`和确认序列号(期望下次收到的数据包)`ack = x + 1` 以及确认号`ACK = 1`报文作为应答,服务器为`SYN_Receive`状态。(问题来了,两次握手之后,所以老哥,你需要给我三次握手来传个话告诉我一声。你要是不告诉我,万一我认为你跑了,然后我可能出于安全性的考虑继续给你发一次,看看你回不回我。) +- **第三次握手**: 客户端接收到服务端的 `SYN + ACK`之后,知道可以下次可以发送了下一序列的数据包了,然后发送同步序列号 `ack = y + 1`和数据包的序列号 `seq = x + 1`以及确认号`ACK = 1`确认包作为应答,客户端转为`established`状态。(分别站在双方的角度上思考,各自ok) + +1. 你吃饭了嘛?(seq=x),收到请回答(SYN=1) +2. 收到(ACK=1),吃饭了(ack=x+1),你吃饭了吗?(seq=y),收到请回答(SYN=1) +3. 收到(ACK=1),吃饭了(ack=y+1),那么我们聊一下接下里的事情(established) + +### TCP四次分手 + +![TCP四次分手-Nt8NUx](https://gitee.com/dreamcater/blog-img/raw/master/uPic/TCP四次分手-Nt8NUx.png) + +- **初始化状态**:客户端和服务端都在连接状态,接下来开始进行四次分手断开连接操作。 +- **第一次分手**:第一次分手无论是客户端还是服务端都可以发起,因为 TCP 是全双工的。 + +> 假如客户端发送的数据已经发送完毕,发送FIN = 1 **告诉服务端,客户端所有数据已经全发完了**,**服务端你可以关闭接收了**,但是如果你们服务端有数据要发给客户端,客户端照样可以接收的。此时客户端处于FIN = 1等待服务端确认释放连接状态。 + +- **第二次分手**:服务端接收到客户端的释放请求连接之后,**知道客户端没有数据要发给自己了**,**然后服务端发送ACK = 1告诉客户端收到你发给我的信息**,此时服务端处于 CLOSE_WAIT 等待关闭状态。(服务端先回应给客户端一声,我知道了,但服务端的发送数据能力即将等待关闭,于是接下来第三次就来了。) +- **第三次分手**:此时服务端向客户端把所有的数据发送完了,然后发送一个FIN = 1,**用于告诉客户端,服务端的所有数据发送完毕**,**客户端你也可以关闭接收数据连接了**。此时服务端状态处于LAST_ACK状态,来等待确认客户端是否收到了自己的请求。(服务端等客户端回复是否收到呢,不收到的话,服务端不知道客户端是不是挂掉了还是咋回事呢) +- **第四次分手**:此时如果客户端收到了服务端发送完的信息之后,就发送ACK = 1,告诉服务端,客户端已经收到了你的信息。**有一个 2 MSL 的延迟等待**。 + +#### 为什么要有2MSL等待延迟? + +对应这样一种情况,最后客户端发送的ACK = 1给服务端的**过程中丢失**了,服务端没收到,服务端怎么认为的?我已经发送完数据了,怎么客户端没回应我?是不是中途丢失了?然后服务端再次发起断开连接的请求,一个来回就是2MSL。 + +客户端给服务端发送的ACK = 1丢失,**服务端等待 1MSL没收到**,**然后重新发送消息需要1MSL**。如果再次接收到服务端的消息,则**重启2MSL计时器**,**发送确认请求**。客户端只需等待2MSL,如果没有再次收到服务端的消息,就说明服务端已经接收到自己确认消息;此时双方都关闭的连接,TCP 四次分手完毕 + +#### 为什么四次分手? + +任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 + +### TCP粘包 + +**TCP粘包**是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。 + + +个人觉得:应用层的报文在以流的形式传输时,每一个报文的报头紧接着上一个报文的报文尾部,这就是所谓的“粘包”问题。 + +- 由TCP**连接复用**造成的粘包问题。 +- 因为TCP默认会使用**Nagle算法**,此算法会导致粘包问题。 + - 只有上一个分组得到确认,才会发送下一个分组; + - 收集多个小分组,在一个确认到来时一起发送。 +- **数据包过大**造成的粘包问题。 +- 流量控制,**拥塞控制**也可能导致粘包。 +- **接收方不及时接收缓冲区的包,造成多个包接收** + +**解决**: + +1. **Nagle算法**问题导致的,需要结合应用场景适当关闭该算法 +2. 尾部标记序列。通过特殊标识符表示数据包的边界,例如\n\r,\t,或者一些隐藏字符。 +3. 头部标记分步接收。在TCP报文的头部加上表示数据长度。 +4. 应用层发送数据时**定长**发送。 + +[https://blog.csdn.net/xp178171640/article/details/104746379/](https://blog.csdn.net/xp178171640/article/details/104746379/) + +[https://blog.csdn.net/songchuwang1868/article/details/87707127](https://blog.csdn.net/songchuwang1868/article/details/87707127) + +### TCP 协议如何保证可靠传输? + +- **确认和重传**:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就会重传。 +- **数据校验**:TCP报文头有校验和,用于校验报文是否损坏。 +- **数据合理分片和排序**:tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新排序后交给应用层。而UDP:IP数据报大于1500字节,大于MTU。这个时候发送方的IP层就需要分片,把数据报分成若干片,是的每一片都小于MTU。而接收方IP层则需要进行数据报的重组。由于UDP的特性,某一片数据丢失时,接收方便无法重组数据报,导致丢弃整个UDP数据报。 +- **流量控制**:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。 +- **拥塞控制**:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。 + +### TCP 利用滑动窗口实现流量控制的机制? + +> 流量控制是为了控制发送方发送速率,保证接收方来得及接收。TCP 利用滑动窗口实现流量控制。 + +TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着**接收方还有多大的缓冲区可以用于接收数据**。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据。 + +> 例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。 + +### TCP拥塞控制的机制以及算法? + +> 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。 + +TCP 发送方要维持一个 **拥塞窗口(cwnd) 的状态变量**。拥塞控制窗口的大小**取决于网络的拥塞程度**,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 + +### TCP的长连接和短连接 +[https://www.cnblogs.com/chinaops/p/9303041.html](https://www.cnblogs.com/chinaops/p/9303041.html) + +### UDP + +提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。 + +#### UDP的特点 + +- UDP是**无连接的**; +- UDP使用**尽最大努力交付**,即不保证可靠交付,因此主机不需要维持复杂的链接状态(这里面有许多参数); +- UDP是**面向报文**的; +- UDP**没有拥塞控制**,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等); +- UDP**支持一对一、一对多、多对一和多对多**的交互通信; +- UDP的**首部开销小**,只有8个字节,比TCP的20个字节的首部要短。 + +那么,再说一次TCP的特点: + +- **TCP是面向连接的**。(就好像打电话一样,通话前需要先拨号建立连接,通话结束后要挂机释放连接); +- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的(**一对一**); +- TCP**提供可靠交付的服务**。通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达; +- TCP**提供全双工通信**。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双方通信的数据; +- **面向字节流**。TCP中的“流”(stream)指的是流入进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。 \ No newline at end of file diff --git "a/Interview/classify/net/\347\275\221\347\273\234\346\250\241\345\236\213.md" "b/Interview/classify/net/\347\275\221\347\273\234\346\250\241\345\236\213.md" new file mode 100644 index 00000000..da907eb8 --- /dev/null +++ "b/Interview/classify/net/\347\275\221\347\273\234\346\250\241\345\236\213.md" @@ -0,0 +1,49 @@ + +## 网络模型 + +![分层模型-EiHhGW](https://gitee.com/dreamcater/blog-img/raw/master/uPic/分层模型-EiHhGW.png) + +### 简要概括 + +- 物理层:底层数据传输,如网线;网卡标准。 + +- 数据链路层:定义数据的基本格式,如何传输,如何标识;如网卡MAC地址。 + +- 网络层:定义IP编址,定义路由功能;如不同设备的数据转发。 + +- 传输层:端到端传输数据的基本功能;如 TCP、UDP。 + +- 会话层:控制应用程序之间会话能力;如不同软件数据分发给不同软件。 + +- 标识层:数据格式标识,基本压缩加密功能。 + +- 应用层:各种应用软件,包括 Web 应用。 + +### 流程 + +比如,计算机 A 和 计算机 B 要进行信息交互,比如 A 上开发了一个网页,需要 B 去访问。B 发出一个请求给 A,那么请求数据从 B 的 **应用层开始向下传到表示层、再从表示层传到会话层直到物理层,通过物理层传递到 A,A 的物理层接到请求后将请求向上传递到自己的应用层,应用层再将要请求的数据向自己的物理层方向传递然后 B 接到数据传递数据到自己的应用层**。 + +说明: + +- 在四层,既传输层数据被称作**段**(Segments); +- 三层网络层数据被称做**包**(Packages); +- 二层数据链路层时数据被称为**帧**(Frames); +- 一层物理层时数据被称为**比特流**(Bits)。 + +### 常见的端口号和协议号 + +![常见端口号-PemUq1](https://gitee.com/dreamcater/blog-img/raw/master/uPic/常见端口号-PemUq1.png) + +### 总结 + +- 网络七层模型是一个标准,而非实现。 +- 网络四层模型是一个实现的应用模型。 +- 网络四层模型由七层模型简化合并而来。 + +### ping命令基于哪一层协议的原理是什么? + +ping命令基于网络层的命令,是基于ICMP协议工作的。 + +### ARP +ARP是一种解决地址问题的协议。以目标IP地址为线索,用来定位下一个应该接收数据分包的网络设备对应的MAC地址。 +起初要通过广播发送一个ARP请求包,这个包里存放了其MAC地址的主机IP地址,由于广播的包可以被同一个链路上所有的主机或路由器接收,因此ARP的请求包也就会被这同一个链路上所有的主机和路由器进行解析。如果ARP请求包中的目标IP地址与自己的IP地址的一致,那么这个节点就将自己的MAC地址塞入ARP响应包返回给主机A。 \ No newline at end of file diff --git "a/Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/Interview/classify/redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" similarity index 91% rename from "Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" rename to "Interview/classify/redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" index 6603a675..5a4b7400 100644 --- "a/Interview/sad/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" +++ "b/Interview/classify/redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -1,3 +1,5 @@ +# 分布式锁 + > 毕竟判断和绑定座位(或者下单)非原子性,为了降低锁的粒度,可以将判断和绑定座位锁在一个事务里。集群:Redisson - Key为xx_座位号,过期时间为随机1-5s(用setex的命令,该命令是key和过期时间是原子性的) @@ -6,9 +8,10 @@ - 前三步,少了个签证value,如果不设置,那么当锁过期了,业务逻辑才走完,准备删除的时候,B客户端获取到了该锁,但是A把B的key锁删除了,然而B还不知道。 - 因此,要解决这个问题,可以设置value签证,结束的时候判断一次,该value是不是自己的value,这样就不会误删。 -#### RedLock算法流程 +## RedLock算法流程 首先有这样的问题: + 1. 客户端 A 从 Master 上获取锁。 2. 在锁未被复制到某 Slave 节点的时候,Master 节点 Down 掉了。 3. 某 Slave 节点成为新的 Master。 @@ -22,4 +25,8 @@ 4. 如果获取锁的实例大于3个(过半机制),那么就相当于获取到锁了,该锁的真正的有效时间为TTL-(T2-T1) = 5min 5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 -[https://juejin.im/post/5cc165816fb9a03202221dd5](https://juejin.im/post/5cc165816fb9a03202221dd5) \ No newline at end of file +[https://juejin.im/post/5cc165816fb9a03202221dd5](https://juejin.im/post/5cc165816fb9a03202221dd5) + +## zk实现分布式锁 + +[补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) \ No newline at end of file diff --git "a/Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" "b/Interview/classify/redis/Redis\346\214\201\344\271\205\345\214\226.md" similarity index 94% rename from "Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" rename to "Interview/classify/redis/Redis\346\214\201\344\271\205\345\214\226.md" index 3b77e4c8..44ce106a 100644 --- "a/Interview/sad/Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/Interview/classify/redis/Redis\346\214\201\344\271\205\345\214\226.md" @@ -1,37 +1,42 @@ + 面试官:Redis的持久化了解嘛? 我:了解,Redis的持久化分为两种:**RDB和AOF** -**RDB**: +## RDB **RDB**是一种**快照存储持久化**方式,具体就是将`Redis`某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为`dump.rdb`。在`Redis`服务器启动时,会重新加载`dump.rdb`文件的数据到内存当中恢复数据。 优点: + 1. RDB会生成多个数据文件,**每个数据文件都代表了某一个时刻中redis的数据**,这种多个数据文件的方式,非常适合**做冷备**。 2. RDB对redis对外提供读写服务的时候,影像非常小,因为redis 主进程只需要fork一个子进程出来,让子进程对磁盘io来进行rdb持久化 3. **RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快**。 缺点: + 1. **如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好**,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。 2. RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒 -![rdb](http://media.dreamcat.ink/uPic/iShot2020-08-12下午04.56.18.png) +![rdb-uc03rm](https://gitee.com/dreamcater/blog-img/raw/master/uPic/rdb-uc03rm.png) -**AOF**: +## AOF **AOF**:把所有的**对Redis的服务器进行修改的命令都存到一个文件里,命令的集合**。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。 优点: + 1. **AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒**,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。 2. **AOF以appen-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高**。 3. AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。 缺点: + 1. 对于同一份文件**AOF文件比RDB数据快照要大**。 2. AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的 3. **数据恢复比较慢,不适合做冷备**。 -![AOF](http://media.dreamcat.ink/uPic/iShot2020-08-12下午04.57.10.png) +![aof-ElLyYr](https://gitee.com/dreamcater/blog-img/raw/master/uPic/aof-ElLyYr.png) **如何选择:** diff --git "a/Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/Interview/classify/redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" similarity index 98% rename from "Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" rename to "Interview/classify/redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" index 2d338fdf..79d4ea81 100644 --- "a/Interview/sad/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/Interview/classify/redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -1,7 +1,10 @@ -#### String + +## String + String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; **常规计数:微博数,粉丝数**等。 -#### Hash +## Hash + Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来**存储用户信息,商品信息**等等。 简单说一下结构 @@ -12,19 +15,22 @@ Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适 - 哈希表使用链地址法来解决键冲突。 注意:这里和Java的HashMap不同的rehash过程 + 1. Redis的rehash过程是扩展和收缩,而且还是渐进式的rehash 2. Redis的字典有两个哈希表ht[0]和ht[1] 3. 为字典的ht[1]哈希表分配空间,如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used *2的2^n;如果执行的是收缩操作,那么ht[1]的大小第一个大于等于ht[0].used的2^n。(举个例子,ht[0]的长度为10,那么扩展就是2^5的32,如果是压缩的话2^4=16) 4. 如果ht[0]的键值非常多的话,一次性转移过去,是一个非常耗时的操作哦,因此并非一次性,采取渐进式rehash转移。 -#### List +## List + list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如**微博的关注列表,粉丝列表, 消息列表**等功能都可以用Redis的 list 结构来实现。 Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 -#### Set +## Set + set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 @@ -32,7 +38,8 @@ set 对外提供的功能与list类似是一个列表的功能,特殊之处在 比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如**共同关注、共同粉丝、共同喜好**等功能。这个过程也就是求交集的过程,具体命令如下:`sinterstore key1 key2 key3`将交集存在key1内 -#### Zset +## Zset + 和set相比,sorted set增加了一个**权重参数score**,使得集合中的元素能够按score进行**有序**排列。 举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种**礼物排行榜**,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 diff --git "a/Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" "b/Interview/classify/redis/Redis\346\250\241\345\236\213.md" similarity index 99% rename from "Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" rename to "Interview/classify/redis/Redis\346\250\241\345\236\213.md" index efc0452c..ee0616c1 100644 --- "a/Interview/sad/Redis\347\232\204\346\250\241\345\236\213.md" +++ "b/Interview/classify/redis/Redis\346\250\241\345\236\213.md" @@ -1,3 +1,6 @@ + +### Redis模型 + 面试官:Redis为什么快? 我:内心:不知道为什么一直问这个问题。 @@ -22,11 +25,13 @@ I/O多路复用程序是有常见的select、epoll等系统调用所实现的。 **往返车站可以看成系统调用,调用一次一小时** ### 1. 阻塞I/O模型 + 老李去火车站买票,排队三天买到一张退票。 耗费:在车站吃喝拉撒睡 3天,其他事一件没干。 ### 2. 非阻塞I/O模型 + 老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。 耗费:往返车站6次,路上6小时,其他时间做了好多事。 @@ -34,6 +39,7 @@ I/O多路复用程序是有常见的select、epoll等系统调用所实现的。 2比1多了个自己轮训调用 ### 3. I/O复用模型 + 1. select/poll 老李去火车站买票,委托黄牛,然后每隔6小时电话**黄牛**询问,黄牛三天内买到票,然后老李去火车站交钱领票。 @@ -58,6 +64,7 @@ I/O多路复用程序是有常见的select、epoll等系统调用所实现的。 不要黄牛了,省了这个单线程,系统通知你,你收到以后自己去读 ### 5. 异步I/O模型 + 老李去火车站,告诉售票员要买票,售票员买到票之后,打电话通知老李把票放在某某储物箱,老李根据储物箱地址自己去取票。 耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话 diff --git "a/Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" "b/Interview/classify/redis/\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" similarity index 95% rename from "Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" rename to "Interview/classify/redis/\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" index 5c41450c..0be06449 100644 --- "a/Interview/sad/Redis\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" +++ "b/Interview/classify/redis/\345\206\205\345\255\230\346\267\230\346\261\260\346\234\272\345\210\266.md" @@ -1,3 +1,5 @@ +# 内存淘汰机制 + 面试官:先讲一下Redis的过期时间 我: @@ -18,4 +20,4 @@ redis 内存数据集大小上升到一定大小的时候,就会施行数据 - volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 - allkeys-lru:从数据集中挑选最近最少使用的数据淘汰 - allkeys-random:从数据集中任意选择数据淘汰 -- no-enviction(驱逐):禁止驱逐数据 +- no-enviction(驱逐):禁止驱逐数据 \ No newline at end of file diff --git "a/Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" "b/Interview/classify/redis/\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\347\274\223\345\255\230\351\233\252\345\264\251.md" similarity index 100% rename from "Interview/sad/Redis\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\351\233\252\345\264\251.md" rename to "Interview/classify/redis/\347\274\223\345\255\230\347\251\277\351\200\217\345\222\214\347\274\223\345\255\230\351\233\252\345\264\251.md" diff --git a/Interview/classify/spring/Bean.md b/Interview/classify/spring/Bean.md new file mode 100644 index 00000000..08cb05c7 --- /dev/null +++ b/Interview/classify/spring/Bean.md @@ -0,0 +1,49 @@ + +## bean的作用域 +- singleton 作用域:表示在 Spring 容器中只有一个 Bean 实例,以单例的形式存在,是默认的 Bean 作用域。 +- prototype 作用域:原型作用域,每次调用 Bean 时都会创建一个新实例,也就是说每次调用 getBean() 方法时,相当于执行了 new Bean()。 +- request 作用域:每次 Http 请求时都会创建一个新的 Bean,该作用域仅适应于 WebApplicationContext 环境。 +- session 作用域:同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。 +- application 作用域:全局的 Web 作用域,类似于 Servlet 中的 Application。 + +## Spring的单例有线程安全问题吗 +大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 + +常见的有两种解决办法: + +- 在Bean对象中尽量避免定义可变的成员变量(不太现实)。 +- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。 + +## 什么是三级缓存 +1. 第一级缓存:单例缓存池singletonObjects。 +2. 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化) +3. 第三级缓存:singletonFactories单例对象工厂缓存。 + + +## 创建Bean的整个过程 +1. 首先finishBeanFactoryInitialization->preInstantiateSingletons->getBean->doGetBean; +2. 在doGetBean中,transformedBeanName:主要负责判断一下有木有别名;getSingleton:从一级缓存singletonObjects拿bean,在getSingleton方法中,有一个判断条件就是isSingletonCurrentlyInCreation,判断为false,因为他是第一次进来,并且还没有正在创建该bean;dependsOn:依赖,暂时先不说他。 +3. 再来一次getSingleton:再一次的从singketonObjects缓存拿,依然没有的。接着有个重点beforeSingletonCreation:它把bean添加到临时的singletonsCurrentlyInCreation,这就意味着,下次再碰见它,那可就是true了。接着singletonFactory.getObject(),这里getObject调用的是传递的接口createBean方法。 +4. 在createBean方法中:有个doCreateBean->createBeanInstance方法:它就是直接实例化,实际上构造器有反应了(区分JVM创建对象和Spring创建对象),但是没有赋值(初始化);earlySingletonExposure:提前暴漏该bean。但要知道三个变量,为什么他是true:isSingleton(),是否单例,那肯定是哦;(这里解释了这里是单例才能提前曝漏,意味着才能存三级缓存)allowCircularReferences,默认变量为true,写死了;isSingletonCurrentlyInCreation,这里可就为true了,因为步骤3,已经将它设置为true了。那么会进来这个方法:addSingletonFactory +5. addSingletonFactory在这个方法中:将该bean放入到三级缓存singletonFactories中。(解决循环依赖) +6. 接下来,就是它了,populateBean:实际上就是属性赋值。(如果这里要有A依赖B,又发现三级缓存中没有B,那么它就会再次执行一次(递归开始)getBean->doGetBean->createBeanInstance(把B给实例化一下),同样的道理,这里会将B也会放入三级缓存中,B开始populateBean,那么它发现B依赖A,此时三级缓存中有A(精髓,牛逼),然后把A放到二级缓存中,同时从三级缓存中移除,接着得到A之后直接赋值,最后完成了初始化,然后来到addSingleton,将B仍到了一级缓存,同时将B从三级缓存仍出去)返回B,递归结束,得到B之后将B的赋值给A了。 +7. 最后将二级缓存的A删除,仍到一级缓存中。 + + + + +## bean的生命周期 +![bean的生命周期-GbJvfY](https://gitee.com/dreamcater/blog-img/raw/master/uPic/bean的生命周期-GbJvfY.png) + +- Bean 容器找到配置文件中 Spring Bean 的定义。 +- Bean 容器利用 Java Reflection API 创建一个Bean的实例。 +- 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。 +- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。 +- 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。 +- 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。 +- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法 +- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。 +- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。 +- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法 +- 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。 +- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。 \ No newline at end of file diff --git "a/Interview/classify/spring/Ioc\345\222\214AOP.md" "b/Interview/classify/spring/Ioc\345\222\214AOP.md" new file mode 100644 index 00000000..23d016f9 --- /dev/null +++ "b/Interview/classify/spring/Ioc\345\222\214AOP.md" @@ -0,0 +1,59 @@ + +## IoC +IoC(Inverse of Control:控制反转)是一种**设计思想**,就是 **将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。** + +将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 **IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** + +### 初始化流程 + +- Resource资源定位:这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。 +- BeanDefinition的载入和解析:这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。 +- BeanDefinition注册 +- prepareRefresh():预备一下, 标记启动时间,上下文环境,我要的材料(beanDefinition)准备好了嘛? +- obtainFreshBeanFactory(): + - 如果已经有了BeanFactory就销毁它里面的单例Bean并关闭这个BeanFactory。 + - 创建一个新的BeanFactory。 + - 对这个BeanFactory进行定制(customize),如allowBeanDefinitionOverriding等参数 + - 转载BeanDefinitions(读取配置文件,将xml转换成对应得BeanDefinition) + - 检查是否同时启动了两个BeanFactory。 +- prepareBeanFactory(beanFactory):设置beanFactory的类加载器,材料(BeanDefinition)解析器等 +- postProcessBeanFactory(beanFactory): + - 设置beanFactory的后置处理器 + - 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 +- invokeBeanFactoryPostProcessors(beanFactory): + - 调用beanFactory的后置处理器(BeanDefinitionRegisterPostProcessor和BeanFactoryPostProcessor) + - 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 +- registerBeanPostProcessors(beanFactory): + - 注册 BeanPostProcessor 的实现类(bean的后置处理器) + - 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 +- initMessageSource():对上下文中的消息源进行初始化 +- initApplicationEventMulticaster():初始化上下文的事件广播器 +- onRefresh():- 模版方法,具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) +- registerListeners():注册事件监听器 +- finishBeanFactoryInitialization(beanFactory):初始化所有的 singleton beans +- finishRefresh():最后,广播事件,ApplicationContext 初始化完成 + +## AOP + +AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。 + +- **Spring AOP就是基于动态代理的** +- 如果要代理的对象,实现了某个接口,那么Spring AOP会使用**JDK Proxy**, +- 而对于没有实现接口的对象,这时候Spring AOP会使用 **Cglib** 生成一个被代理对象的子类来作为代理。 + + +![](https://user-gold-cdn.xitu.io/2018/9/14/165d631e56799a5c?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) + +### 初始化流程 +registerAspectJAnnotationAutoProxyCreatorIfNecessary + +- 第一句,注册一个AnnotationAwareAspectJAutoProxyCreator(称它为自动代理器),这个Creator是AOP的操作核心,也是扫描Bean,代理Bean的操作所在。 +- 第二句,解析配置元素,决定代理的模式。其中有JDK动态代理,还有CGLIB代理,这部分后续会再细讲。 +- 第三句,作为系统组件,把Creator这个Bean,放到Spring容器中。让Spring实例化,启动这个Creator。 + +总结: +- Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator,当作一个系统组件。 +- 当一个bean加载到Spring中时,会触发自动代理器中的bean后置处理,然后会先扫描bean中所有的Advisor +- 然后用这些Adviosr和其他参数构建ProxyFactory +- ProxyFactory会根据配置和目标对象的类型寻找代理的方式(JDK动态代理或CGLIG代理) +- 然后代理出来的对象放回context中,完成Spring AOP代理 diff --git a/Interview/classify/spring/SpringBoot.md b/Interview/classify/spring/SpringBoot.md new file mode 100644 index 00000000..dd49e06e --- /dev/null +++ b/Interview/classify/spring/SpringBoot.md @@ -0,0 +1,49 @@ + + +## Springboot自动装配原理 + +SpringBootApplication的注解 +[https://www.jianshu.com/p/943650ab7dfd](https://www.jianshu.com/p/943650ab7dfd) + +- @SpringBootConfiguration:允许在上下文中注册额外的bean或导入其他配置类 +- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制 +- @ComponentScan: 扫描常用的注解 + +其中 @EnableAutoConfiguration 是实现自动配置的入口,该注解又通过 @Import 注解导入了AutoConfigurationImportSelector,在该类中加载 META-INF/spring.factories 的配置信息。然后筛选出以 EnableAutoConfiguration 为 key 的数据,加载到 IOC 容器中,实现自动配置功能! + + +## @Resource和@Autowired区别 + +### 共同点 +两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。 + +```java +public class TestServiceImpl { + // 下面两种@Autowired只要使用一种即可 + @Autowired + private UserDao userDao; // 用于字段上 + + @Autowired + public void setUserDao(UserDao userDao) { // 用于属性的方法上 + this.userDao = userDao; + } +} +``` + +### 不同点 + +1. @Autowired + +@Autowired注解是**按照类型**(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用**按照名称**(byName)来装配,可以结合**@Qualifier注解**一起使用。(通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下,会使用对象名作为最后的fallback匹配)如下: + +```java +public class TestServiceImpl { + @Autowired + @Qualifier("userDao") + private UserDao userDao; +} +``` + +2. @Resource + +@Resource默认按照ByName自动注入。@Resource有两个重要的属性:**name和type**,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。**所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略** \ No newline at end of file diff --git a/Interview/classify/spring/SpringMVC.md b/Interview/classify/spring/SpringMVC.md new file mode 100644 index 00000000..8886d18f --- /dev/null +++ b/Interview/classify/spring/SpringMVC.md @@ -0,0 +1,13 @@ + +# SpringMVC + +![springmvc工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) + +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` 返回给请求者(浏览器) \ No newline at end of file diff --git "a/Interview/classify/spring/Spring\344\272\213\345\212\241.md" "b/Interview/classify/spring/Spring\344\272\213\345\212\241.md" new file mode 100644 index 00000000..72925428 --- /dev/null +++ "b/Interview/classify/spring/Spring\344\272\213\345\212\241.md" @@ -0,0 +1,48 @@ + +## Spring事务 +### 隔离级别 +- **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的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 + +### @Transactional(rollbackFor = Exception.class)注解了解吗 +1. @Transactional注解只能应用到public修饰符上,其它修饰符不起作用,但不报错。 +2. 默认情况下此注解会对unchecked异常进行回滚,对checked异常不回滚。 + +> checked异常:表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。unchecked异常:表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。 + +不回滚解决方案: +1. 检查方法是不是public +2. 检查异常是不是unchecked异常 +3. 如果是checked异常也想回滚的话,注解上写明异常类型即可@Transactional(rollbackFor=Exception.class) + +事务失效的8大原因: +1. 数据库引擎不支持事务 +2. 没有被 Spring 管理 +3. 方法不是 public 的 +4. 自身调用问题 +5. 数据源没有配置事务管理器 +6. 不支持事务(传播机制) +7. 异常被吃了(捕获异常) +8. 异常类型错误(checked异常失效) + +### Spring的的事务传播机制 +1. **required**(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 +2. **requires_new**:创建一个新事务,如果当前事务存在,把当前事务挂起。 +3. **supports**:支持使用当前事务,如果当前事务不存在,则不使用事务。 +4. **not_supported**:无事务执行,如果当前事务存在,把当前事务挂起。 +5. **mandatory**:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 +6. **never**:无事务执行,如果当前有事务则抛出Exception。 +7. **nested**:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 + +### 事务源码 +- 开启@EnableTransactionManagement +- 利用TransactionManagementConfigurationSelector给容器中会导入组件 + - AutoProxyRegistrar + - 给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件 + - 利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用; + - ProxyTransactionManagementConfiguration(给容器中注册事务增强器) + - 事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解 + - 事务拦截器 \ No newline at end of file diff --git "a/Interview/classify/sys/\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\256\241\347\220\206\346\226\271\345\274\217.md" "b/Interview/classify/sys/\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\256\241\347\220\206\346\226\271\345\274\217.md" new file mode 100644 index 00000000..26548dc4 --- /dev/null +++ "b/Interview/classify/sys/\346\223\215\344\275\234\347\263\273\347\273\237\345\206\205\345\255\230\347\256\241\347\220\206\346\226\271\345\274\217.md" @@ -0,0 +1,22 @@ + +## 分页管理 + +分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页加以编号,从0开始,如第0页、第1页等。相应地,也把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或页框(frame),也同样为它们加以编号,如0#块、1#块等等。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“页内碎片”。 + +**优缺点**:**没有外部碎片,内存利用率高。但各页中内容没有关联,不利于编程和共享**。 + +## 分段管理 + +程序通过分段(segmentation)划分为多个模块,如代码段、数据段、共享段。内存每段的大小都匹配程序段,不会产生内部碎片。 +**优缺点**: 可以针对不同类型的段采取不同的保护。 可以按段为单位来进行共享,包括通过动态链接进行代码共享。 **不会产生内部碎片,但会产生外部碎片,内存利用率比分页低**。 + +## 段页式管理 + +一个进程中所包含的具有独立逻辑功能的程序或数据仍被划分为段,并有各自的段号s。这反映相继承了段式管理的特征。其次,对于段s中的程序或数据,则按照一定的大小将其划分为不同的页。和页式系统一样,最后不足一页的部分仍占一页。这反映了段页式管理中的页式特征。从而,段页式管理时的进程的虚拟地址空间中的虚拟地址由三部分组成:即段号s,页号P和页内相对地址d。虚拟空间的最小单位是页而不是段,从而内存可用区也就被划分成为若干个大小相等的页面,且每段所拥有的程序和数据在内存中可以分开存放。分段的大小也不再受内存可用区的限制。 +**优缺点:**既有具有独立逻辑功能的段,又以大小相同的页为内存分配单位进而不会产生外部碎片。但仍会有内部碎片。 + +[操作系统如管理内存](https://blog.csdn.net/hguisu/article/details/5713164) + +## 内存分配过程 + +[https://blog.csdn.net/edonlii/article/details/22601043](https://blog.csdn.net/edonlii/article/details/22601043) \ No newline at end of file diff --git "a/Interview/classify/sys/\346\255\273\351\224\201.md" "b/Interview/classify/sys/\346\255\273\351\224\201.md" new file mode 100644 index 00000000..30fbc5e5 --- /dev/null +++ "b/Interview/classify/sys/\346\255\273\351\224\201.md" @@ -0,0 +1,20 @@ +## 死锁的条件 + +所以:死锁的条件? + +- **互斥条件**:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) +- **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) +- **不剥夺条件**:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) +- **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗)比喻成棒棒糖也阔以。 + +面试官:所以解决死锁的办法是? + +我:好的,没毛病 + +- 预防死锁: + - **资源一次性分配**:破坏请求和保持条件。 + - **可剥夺资源**:当进程新申请的资源不满足时,释放已经分配的资源。破坏不可剥夺条件 + - **资源有序分配**:系统给进程编号,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 +- 避免死锁:银行家算法:分配资源前先评估风险,会不会在分配后导致死锁。 即分配给一个进程资源的时候,该进程能否全部返还占用的资源。 +- 检测死锁:建立资源分配表和进程等待表。 +- 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 \ No newline at end of file diff --git "a/Interview/classify/sys/\347\263\273\347\273\237\350\277\233\347\250\213\350\260\203\345\272\246.md" "b/Interview/classify/sys/\347\263\273\347\273\237\350\277\233\347\250\213\350\260\203\345\272\246.md" new file mode 100644 index 00000000..88d08eee --- /dev/null +++ "b/Interview/classify/sys/\347\263\273\347\273\237\350\277\233\347\250\213\350\260\203\345\272\246.md" @@ -0,0 +1,8 @@ + + +- FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU +- SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度 +- 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化 +- 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 +- 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。 +- 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。 \ No newline at end of file diff --git "a/Interview/classify/sys/\350\231\232\346\213\237\345\206\205\345\255\230.md" "b/Interview/classify/sys/\350\231\232\346\213\237\345\206\205\345\255\230.md" new file mode 100644 index 00000000..042f78da --- /dev/null +++ "b/Interview/classify/sys/\350\231\232\346\213\237\345\206\205\345\255\230.md" @@ -0,0 +1,13 @@ + +> 为什么操作系统引进虚拟内存?说白了,直接让一个进程全部放在主内存当中,太占用内存啦,以前的计算机的内存可没有那么大,并且程序也很多。所以在进程和主内存之间存在虚拟内存。可以[参考](https://draveness.me/whys-the-design-os-virtual-memory/) + + +虚拟内存允许执行进程不必完全在内存中。虚拟内存的基本思想是: +- 每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。 +- 这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。 +- 当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上。 + +举个小例子: +吃饭。 + +比如你和同学去聚餐,根据菜谱点了一堆的菜,有汤,有肉,有素材等。 大可不必一下子全做完一起送过来(如果桌子不给力,比较小,并且时间成本,饭菜都凉了等),比如,同学想吃肉哈?不想先喝汤,那就把肉类端上来,对吧?等同学又想喝汤了,就把汤做一下端过来。 diff --git "a/Interview/classify/sys/\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" "b/Interview/classify/sys/\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" new file mode 100644 index 00000000..cb636b53 --- /dev/null +++ "b/Interview/classify/sys/\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" @@ -0,0 +1,54 @@ +# 线程与进程的区别 +面试官:说一下线程与进程的区别 + +我:好的,如下: + +- 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位 + +- 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。 + +- 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的 + +- 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 + +- 与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程 + +举例子:比如,main函数启动了JVM进程,同时main就是其中的线程,并且启动了JVM进程,那么还有垃圾回收等线程。 + +或者这样的例子:做个简单的比喻:进程=火车,线程=车厢 + +- 线程在进程下行进(单纯的车厢无法运行) +- 一个进程可以包含多个线程(一辆火车可以有多个车厢) +- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) +- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) +- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) +- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) +- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) +- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁" +- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量” + + + +--- + +## 协程 + +协程: + +协程是一种用户态的轻量级线程, 我们的server的发展如下:IO密集型应用:多进程 -> 多线程 ->事件驱动 ->协程 + +协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,然后去做其他工作,当你的IO解除之后切回原来的状态,恢复先前保存的寄存器上下文和栈。 + + +优点: + +- 跨平台 +- 无需线程上下文切换的开销 +- 无需原子操作锁定及同步的开销 +- 方便切换控制流,简化编程模型 +- 高并发+高扩展行+低成本: 一个CPU支持上万的协程都不是问题,所以很适合用于高并发处理 + +缺点: + +- 无法利用多核资源:协程的本质是一个单线程,它不能同时将单个CPU的多个核作用上,协程需要和进程配合才能运行在多CPU上. +- 进行阻塞(Blocking)操作会阻塞到整个程序; 这一点和事件驱动一样,可以使用异步IO操作来解决. \ No newline at end of file diff --git "a/Interview/classify/sys/\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241.md" "b/Interview/classify/sys/\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241.md" new file mode 100644 index 00000000..9ea43cef --- /dev/null +++ "b/Interview/classify/sys/\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241.md" @@ -0,0 +1,57 @@ + +# 进程间通信 + +> 首先要知道进程之间为什么要通信。 进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。 但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程之间的通信。 + +## 进程通信的目的 + +- 数据传输:一个进程需要将它的数据发送给另一个进程。 +- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 +- 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。 +- 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 + + +## 通信方式 + +### 管道 + +- 普通管道:通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用. +- 流管道:去除了第一种限制,为半双工,只能在父子或兄弟进程间使用,可以双向传输. +- 命名管道:去除了第二种限制,可以在许多并不相关的进程之间进行通讯. + +### 信号量 + +- 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 +- 它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。 +- 因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 + +### 消息队列 + +消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 + +### 信号 + +- 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 + +### 共享内存 + +共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。 + +#### 共享内存的实现(mmap) +mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。 + +#### 系统调用mmap共享内存的两种方式 +- 使用普通文件提供的内存映射:适用于任何进程之间; +- 使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间; 由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。 + +### socket + +这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。 + +## 线程之间的通信 + +1. 锁机制:包括互斥锁、条件变量、读写锁 +2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量 +3. 信号机制(Signal):类似进程间的信号处理 + +> 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。 \ No newline at end of file diff --git "a/Interview/classify/sys/\351\241\265\351\235\242\347\275\256\346\215\242\347\256\227\346\263\225.md" "b/Interview/classify/sys/\351\241\265\351\235\242\347\275\256\346\215\242\347\256\227\346\263\225.md" new file mode 100644 index 00000000..d5650418 --- /dev/null +++ "b/Interview/classify/sys/\351\241\265\351\235\242\347\275\256\346\215\242\347\256\227\346\263\225.md" @@ -0,0 +1,7 @@ +> 地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断。当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。常见的置换算法有: + +- 最佳置换算法(OPT):所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。 +- 先进先出置换算法(FIFO):选择换出的页面是最先进入的页面。该算法会将那些经常被访问的页面换出,导致缺页率升高。 +- 第二次机会算法:当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。 +- 最近最久未使用(LRU)算法:为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。 + diff --git "a/Interview/sad/\350\260\210\350\260\210AQS.md" b/Interview/classify/thread/AQS.md similarity index 98% rename from "Interview/sad/\350\260\210\350\260\210AQS.md" rename to Interview/classify/thread/AQS.md index f29d306d..497f219d 100644 --- "a/Interview/sad/\350\260\210\350\260\210AQS.md" +++ b/Interview/classify/thread/AQS.md @@ -1,7 +1,10 @@ +# AQS + > 这个先留着,这个内容有点多,待我这周结束之后来写。 面试官:AQS是什么? +## 原理 我:AbstractQueuedSynchronizer抽象同步队列。说白了,就是个FIFO双向队列。其内部通过节点head和tail记录对首和队尾元素。 **state**:在AQS中维持了一个单一的状态信息state,可以通过**getState**、**setState**、**compareAndSetState**函数修改其值。 @@ -14,6 +17,8 @@ 对于AQS来讲,线程同步的关键是对状态值**state**进行操作。 +## 方法 + 在独占方式下获取和释放资源使用的方法: ```java @@ -62,4 +67,3 @@ public final boolean release(int arg) { **acquireShared和releaseShared**和上面的方法的思想差不多 - diff --git "a/Interview/classify/thread/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/classify/thread/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" new file mode 100644 index 00000000..d8970013 --- /dev/null +++ "b/Interview/classify/thread/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" @@ -0,0 +1,158 @@ +# BlockingQueue +看一下官方1.8的官方文档 +![已知实现类](https://imgkr.cn-bj.ufileos.com/1c08311d-77a7-4f60-a4dd-2e293f43c8d0.png) + +官方文档告诉我们它们有这些特点: +- BlockingQueue实现被设计为主要用于生产者 - 消费者队列 +- BlockingQueue实现是线程安全的。 所有排队方法使用内部锁或其他形式的并发控制在原子上实现其效果。 +- BlockingQueue方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足: + - 一个抛出异常 + - 返回一个特殊值( null或false ,具体取决于操作) + - 第三个程序将无限期地阻止当前线程,直到操作成功为止 + - 在放弃之前只有给定的最大时限。 + +## 看源码之旅 + +### ArrayBlockingQueue +> 看一下官方文档的解释->一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞。此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。 + +#### 常见变量 +```java +int count; // 队列元素数量 +final ReentrantLock lock; // 内部线程安全性用了ReentrantLock +private final Condition notEmpty; // takes方法的等待条件 +private final Condition notFull; // puts方法的等待条件 +``` +#### 构造方法 +```java +public ArrayBlockingQueue(int capacity, boolean fair) { + if (capacity <= 0) + throw new IllegalArgumentException(); // 这里就不说了 + this.items = new Object[capacity]; // 初始容量 + lock = new ReentrantLock(fair); // fair参数决定是否公平锁 + notEmpty = lock.newCondition(); // 上面已提到 + notFull = lock.newCondition(); // 上面已提到 +} +``` +#### add +```java +public boolean add(E e) { + return super.add(e); // 该方法添加失败抛出异常 +} +// AbstractQueue +public boolean add(E e) { + if (offer(e)) + return true; + else + throw new IllegalStateException("Queue full"); // 在这 +} +``` +**remove就不分析了** + +#### offer +```java +public boolean offer(E e) { + checkNotNull(e); + final ReentrantLock lock = this.lock; // 上面的add其实内部也调用了offer,当时我还觉得奇怪,add没上锁?。 原来offer上了锁的 + lock.lock(); + try { + if (count == items.length) + return false; // 满了,就false + else { + enqueue(e); // 否则,添加即可 + return true; // 返回true,并不会抛出异常 + } + } finally { + lock.unlock(); + } +} +``` +**poll不分析了** + +#### put +```java +public void put(E e) throws InterruptedException { + checkNotNull(e); // 检查是否为空,为空就抛出空异常 + final ReentrantLock lock = this.lock; // 上锁哦 + lock.lockInterruptibly(); // 锁中断 + try { + while (count == items.length) // 满了,挂起阻塞 + notFull.await(); + enqueue(e); // 否则添加 + } finally { + lock.unlock(); + } +} +``` + +#### take +```java +public E take() throws InterruptedException { + final ReentrantLock lock = this.lock; // 上锁 + lock.lockInterruptibly(); // 锁中断 + try { + while (count == 0) + notEmpty.await(); // 没有元素,挂起 + return dequeue(); + } finally { + lock.unlock(); + } +} +``` + +#### 带时间的offer和poll +> 其实就是用了`long nanos = unit.toNanos(timeout);` + + +### LinkedBlockingQueue +> 四个方法和ArrayBlockingQueue的差不多,就不多分析了。它的特点之一在于,如果不指定容量,那么默认是等于Integer.MAX_VALUE。可以看一下源码 + +#### 常见的参数 +```java +/** The capacity bound, or Integer.MAX_VALUE if none */ +private final int capacity; // 一看注释,就晓得了 +// 还有一些常见的和上面的差不多 +``` + +#### 构造方法 +```java +public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); // 在这里, 嘿嘿嘿。 +} +``` + +### LinkedTransferQueue +> 基于链接节点的无界TransferQueue 。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。 + +**四个方法就暂时不提了,大部分操作用了cas并且要关注transfer方法** + +先介绍几个标志参数: +```java +private static final int NOW = 0; // for untimed poll, tryTransfer +private static final int ASYNC = 1; // for offer, put, add +private static final int SYNC = 2; // for transfer, take +private static final int TIMED = 3; // for timed poll, tryTransfer +``` + +```java +// 如果可能,立即将元素转移到等待的消费者。 +public boolean tryTransfer(E e) { + return xfer(e, true, NOW, 0) == null; // xfer的now参数 +} +// 还有一个重载参数的,带时间的。 +``` + +```java +// 将元素传输到消费者,必要时等待。 +public void transfer(E e) throws InterruptedException { + if (xfer(e, true, SYNC, 0) != null) { // SYNC + Thread.interrupted(); // failure possible only due to interrupt + throw new InterruptedException(); + } +} +``` +### PriorityBlockingQueue +> 一看名字就是优先级阻塞队列,它的优先级是由堆实现的,所以该类中有很多堆的方法。源码暂时就不看了,很多都是差不多的。 + +### SynchronousQueue +> 官方文档:每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 个人感觉是生产者生产一个元素,消费者必须消费,生产者才能继续生产。内部也维护了一个TransferQueue,其中部分操作是利用cas。源码就不贴了。有兴趣的可以进去看看哦。 diff --git "a/Interview/sad/\350\260\210\350\260\210CAS.md" b/Interview/classify/thread/CAS.md similarity index 64% rename from "Interview/sad/\350\260\210\350\260\210CAS.md" rename to Interview/classify/thread/CAS.md index fe1661e9..9b10034f 100644 --- "a/Interview/sad/\350\260\210\350\260\210CAS.md" +++ b/Interview/classify/thread/CAS.md @@ -1,17 +1,45 @@ +# CAS + 面试官:了解CAS嘛? 我:了解,**我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了`CAS`,并且在调用getAndAddInt方法中,会有compareAndSwapInt方法。其实都是调用unsafe.compareAndSwap()方法。 -细说compareAndSwapInt方法 +## compareAndSwapInt + +细说compareAndSwapInt方法,那岂不是要看源码: + +```cpp +UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) + UnsafeWrapper("Unsafe_CompareAndSwapInt"); + oop p = JNIHandles::resolve(obj); // oop 安全点 引用 + jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); // 计算偏移地址 + return (jint)(Atomic::cmpxchg(x, addr, e)) == e; // 原子操作,比较和更新 +UNSAFE_END +``` -`compareAndSwapInt(obj, offset, expect, update)`比较清楚,**意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作**,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。 +Java中的`compareAndSwapInt(obj, offset, expect, update)`比较清楚,**意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作**,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。 原子操作:可理解为要么这些行为都执行,不被打扰,要不都不执行。实在不行,就理解为串行操作, -再细说这个cpu指令: +再细说这个cpu指令,那就要看该源码了: + +```hpp +inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { + int mp = os::is_MP(); // 返回处理器(判断是多核还是单核,单核省略lock前缀) + __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" // 比较 + : "=a" (exchange_value) + : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) + : "cc", "memory"); + return exchange_value; +} +``` + + 调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数**x是即将更新的值**,参数**e是原内存的值**。代码中能看到cmpxchg有基于各个平台的实现。 +## ABA + 面试官:知道ABA问题嘛? 我:知道,描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程**也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A** (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时**变量 x 的值还是 A**,所以 compareAndSet 操作是成功。 @@ -63,3 +91,8 @@ public class ABADemo { } ``` +## CAS设计思想 + +面试官:了解CAS的设计思想吗? + +我:思想?我理解的是乐观思想,你听我娓娓道来,何为乐观,乐观就是在我角度上认为"哈士奇"不会去偷吃我的菜,那么我就不用同步加个房间锁住门防止"哈士奇"吃。那设计的时候,cpu咋知道"哈士奇"吃过没,那就看这道菜是不是少了点,或者量少了一半等。那么主人回来吃的时候肯定不会开吃了,是吧?所以主人吃就跟CAS的更新值一样,这道菜没被"哈士奇"吃过是CAS的期望值。 那么cpu的这个cmpxchg就是干这个事的。 \ No newline at end of file diff --git "a/Interview/sad/\350\260\210\350\260\210CountDownLatch.md" b/Interview/classify/thread/CountDownLatch.md similarity index 97% rename from "Interview/sad/\350\260\210\350\260\210CountDownLatch.md" rename to Interview/classify/thread/CountDownLatch.md index d1e758aa..2f49a51a 100644 --- "a/Interview/sad/\350\260\210\350\260\210CountDownLatch.md" +++ b/Interview/classify/thread/CountDownLatch.md @@ -1,11 +1,17 @@ +# CountDownLatch + > 我们经常在主线程中开启多个线程去并行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。知道你们要说join,但是join不够灵活。 面试官:讲讲CountDownLatch原理 我:我试试 +## 原理 + 首先状态变量state:**state用来表示计数器当前的值**,当线程调用CountDownLatch对象的await方法后, 当前线程会被阻塞,直到下面的情况之一发生才返回:当所有线程都调用了CountDownLatch对象的countDown方法后,也就是**计数器的值为0**时:其他线程调用了**当前线程的interrupt()方法中断了当前线程**,当前线程就会抛出InterruptedException异常。 +## 方法 + 所以,我们看一下await方法: ```java diff --git "a/Interview/classify/thread/JMM\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/Interview/classify/thread/JMM\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100755 index 00000000..eeda039d --- /dev/null +++ "b/Interview/classify/thread/JMM\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,3 @@ +![volatile保证内存可见性和避免重排-WDKMZd](https://gitee.com/dreamcater/blog-img/raw/master/uPic/volatile保证内存可见性和避免重排-WDKMZd.png) + + diff --git "a/Interview/classify/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" "b/Interview/classify/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" new file mode 100644 index 00000000..e8c3f0a3 --- /dev/null +++ "b/Interview/classify/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" @@ -0,0 +1,98 @@ +# 各种锁 +## 公平锁/非公平锁 +**公平锁指多个线程按照申请锁的顺序来获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...),ReentrantLock,了解一下。** + +## 可重入锁 +**可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,典型的synchronized,了解一下** +```java +synchronized void setA() throws Exception { + Thread.sleep(1000); + setB(); // 因为获取了setA()的锁,此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行 +} +synchronized void setB() throws Exception { + Thread.sleep(1000); +} +``` + +## 独享锁/共享锁 +- 独享锁:是指该锁一次只能被一个线程所持有。 +- 共享锁:是该锁可被多个线程所持有。 + +## 互斥锁/读写锁 +**上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是其具体的实现** + +## 乐观锁/悲观锁 + +1. **乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待兵法同步的角度。** +2. **悲观锁认为对于同一个人数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。** +3. **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作时没有事情的。** +4. **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** +5. **悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁** + +## 分段锁 + +1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** +2. **以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** +3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** +4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** + +## 偏向锁/轻量级锁/重量级锁 + +1. **这三种锁是锁的状态,并且是针对Synchronized。在Java5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。** +2. **偏向锁的适用场景:始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。** +3. **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** +4. **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** + +## 自旋锁 + +1. **在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。** +2. **自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。** +3. **自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。** +4. **但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适用使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。** + +### 手写自旋锁的例子 +```java +public class SpinLock { + + // 原子引用线程 + AtomicReference atomicReference = new AtomicReference<>(); + + public void mylock() { + Thread thread = Thread.currentThread(); + System.out.println(Thread.currentThread().getName() + " como in..."); + while (!atomicReference.compareAndSet(null, thread)) { +// System.out.println("不爽,重新获取一次值瞧瞧..."); + } + } + + public void myUnlock() { + Thread thread = Thread.currentThread(); + atomicReference.compareAndSet(thread, null); + System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); + } + + public static void main(String[] args) { + SpinLock spinLock = new SpinLock(); + new Thread(() -> { + spinLock.mylock(); + try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } + spinLock.myUnlock(); + }, "t1").start(); + try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } + + new Thread(() -> { + spinLock.mylock(); + spinLock.myUnlock(); + }, "t2").start(); + } + +} +``` + +## Java锁总结 + +**Java锁机制可归为Sychornized锁和Lock锁两类。Synchronized是基于JVM来保证数据同步的,而Lock则是硬件层面,依赖特殊的CPU指令来实现数据同步的。** + +- Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 +- ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 +- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 \ No newline at end of file diff --git "a/Interview/sad/\350\260\210\350\260\210ReentrantLock.md" b/Interview/classify/thread/ReentrantLock.md similarity index 98% rename from "Interview/sad/\350\260\210\350\260\210ReentrantLock.md" rename to Interview/classify/thread/ReentrantLock.md index 188259e3..31191b6e 100644 --- "a/Interview/sad/\350\260\210\350\260\210ReentrantLock.md" +++ b/Interview/classify/thread/ReentrantLock.md @@ -1,9 +1,16 @@ +# ReentrantLock + > 经常拿synchronized和ReentrantLock做比较,那就来看看这个锁的一些重点吧 面试官:了解ReentrantLock嘛? +## 原理 + 我:当然,它实现了Lock接口,同时调用内部类sync继承的AQS,先说一下**state**:它代表获取该锁的**可重入次数**,在默认下,state的值为0表示**当前锁没有被任何线程持有**。当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,并且记录该锁的持有者为当前线程。若该线程第二次获取该锁后,状态值被设置2。 + +## 方法 + 我们来看一下lock方法: ```java diff --git "a/Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" b/Interview/classify/thread/ReentrantReadWriteLock.md similarity index 99% rename from "Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" rename to Interview/classify/thread/ReentrantReadWriteLock.md index a6390b18..4ee834a9 100644 --- "a/Interview/sad/\350\260\210\350\260\210ReentrantReadWriteLock.md" +++ b/Interview/classify/thread/ReentrantReadWriteLock.md @@ -1,9 +1,13 @@ +# ReentrantReadWriteLock + > 聊一聊读写锁 面试官:了解AQS的读写锁嘛,知道其原理嘛? 我:刚好了解,我们知道,在一些读多写少的场景中,若是用ReentrantLock效率显然不高,于是ReentrantReadWriteLock问世。 +## 原理 + 老规矩: ```java @@ -29,6 +33,8 @@ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } ``` +## 方法 + 直奔主题:写锁的获取与释放 **lock:** diff --git "a/Interview/sad/\350\260\210\350\260\210ThreadLocal.md" b/Interview/classify/thread/ThreadLocal.md similarity index 98% rename from "Interview/sad/\350\260\210\350\260\210ThreadLocal.md" rename to Interview/classify/thread/ThreadLocal.md index bd00dee9..a088c863 100644 --- "a/Interview/sad/\350\260\210\350\260\210ThreadLocal.md" +++ b/Interview/classify/thread/ThreadLocal.md @@ -1,7 +1,11 @@ +# ThreadLocal + 面试官:了解ThreadLocal嘛?用过ThreadLocal嘛? 我:了解过:它是这样的,假如想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 +## 底层原理 + 面试官:你知道底层原理嘛? 我:知道 @@ -63,6 +67,8 @@ public T get() { **每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 +## 内存泄漏 + 面试官:知道内存泄露嘛? 我:知道,`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。 diff --git "a/Interview/sad/\350\260\210\350\260\210synchronized.md" b/Interview/classify/thread/synchronized.md similarity index 86% rename from "Interview/sad/\350\260\210\350\260\210synchronized.md" rename to Interview/classify/thread/synchronized.md index 5e7eb2b3..91875ae6 100644 --- "a/Interview/sad/\350\260\210\350\260\210synchronized.md" +++ b/Interview/classify/thread/synchronized.md @@ -1,3 +1,5 @@ +# synchronized和lock的区别 + > 很多面试官爱问synchronized和lock的区别 面试官:区别?哈哈哈 @@ -7,10 +9,12 @@ - **两者都是可重入锁**:两者都是可重入锁。“可重入锁”概念是:**自己可以再次获取自己的内部锁**。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 - **synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**:synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 - **ReenTrantLock 比 synchronized 增加了一些高级功能** - 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 2. **可实现公平锁** - 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” - 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** + 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 + 2. **可实现公平锁** + 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” + 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** + +## synchronized修饰范围 面试官:先扯synchronized吧,修饰范围?可否了解? @@ -22,6 +26,8 @@ 这里可能让谈对象头里面都有什么哦,简单的说:**Hotspot 虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC 分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 +## 底层原理 + 面试官:聊聊底层原理 我:好的,代码块和方法有些区别。 diff --git "a/Interview/sad/\350\260\210\350\260\210volatile.md" b/Interview/classify/thread/volatile.md similarity index 73% rename from "Interview/sad/\350\260\210\350\260\210volatile.md" rename to Interview/classify/thread/volatile.md index 4412106c..709ecf5b 100644 --- "a/Interview/sad/\350\260\210\350\260\210volatile.md" +++ b/Interview/classify/thread/volatile.md @@ -1,8 +1,10 @@ +# volatile的特性 + 面试官:了解volatile嘛?有啥子特性 -我:了解,两个特性:内存可见性、禁止重排序 +我:了解,两个特性:**内存可见性、禁止重排序** -禁止重排序:不管是编译器还是JVM还是CPU,都会对一些指令进行重排序,目的是为了提高程序运行的速度,提高程序的性能,毫无疑问,在单线程下没毛病,多线程就似乎生病了。 +禁止重排序:**不管是编译器还是JVM还是CPU,都会对一些指令进行重排序,目的是为了提高程序运行的速度,提高程序的性能,毫无疑问,在单线程下没毛病,多线程就似乎生病了。** 给你稍微举举例子:禁止重排->单例模式 @@ -46,7 +48,7 @@ ctorInstance(memory); //2. 初始化对象 这里多说多说一点:为什么在synchronized上面多加了一次判断 -还不是因为synchronized比较笨重,锁了代码块嘛,多线程不能每次都要进来块中,岂不是都要发生阻塞等这class的锁呀,直接给他上面判断一下不为空就直接跳出去了。提高了性能哇。 +**还不是因为synchronized比较笨重,锁了代码块嘛,多线程不能每次都要进来块中,岂不是都要发生阻塞等这class的锁呀,直接给他上面判断一下不为空就直接跳出去了。提高了性能哇。** 其实这里也能体现出volatile的内存可见性,让其他线程对这个实例可见。 @@ -54,7 +56,7 @@ ctorInstance(memory); //2. 初始化对象 扯一波JMM内存模型 -![volatile保证内存可见性和避免重排](http://media.dreamcat.ink/uPic/volatile保证内存可见性和避免重排.png) +![volatile保证内存可见性和避免重排-ouQfjW](https://gitee.com/dreamcater/blog-img/raw/master/uPic/volatile保证内存可见性和避免重排-ouQfjW.png) 根据这个图如何回答总结JMM内存模型,看各位的造化了,理解讲出来即可。结合例子讲也可以 @@ -63,10 +65,16 @@ ctorInstance(memory); //2. 初始化对象 3. 然后扯流程 4. 撒花结束 +## 底层结构 + 面试官:知道底层结构嘛? 我:禁止重排是利用内存屏障来解决的,其实最根本的还是cpu的一个**lock**指令:**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - 锁住内存 - 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 \ No newline at end of file +- 使其它线程这个值的栈缓存失效 + +## 不能保证原子性 + +![volatile不能保证数据一致性](https://gitee.com/dreamcater/blog-img/raw/master/uPic/volatile不能保证数据一致性-iGuPEi.png) diff --git "a/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" "b/Interview/classify/thread/\346\255\273\351\224\201.md" similarity index 96% rename from "Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" rename to "Interview/classify/thread/\346\255\273\351\224\201.md" index d7e2a4ba..6452f806 100644 --- "a/Interview/sad/\350\260\210\350\260\210\346\255\273\351\224\201.md" +++ "b/Interview/classify/thread/\346\255\273\351\224\201.md" @@ -1,3 +1,5 @@ +# 死锁 + 面试官:知道死锁嘛? 我:知道,那我就谈一下Java的死锁吧,其实原理都一样。 @@ -34,6 +36,8 @@ public class Test { } ``` +## 死锁的条件 + 所以:死锁的条件? - **互斥条件**:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) @@ -53,10 +57,13 @@ public class Test { - 检测死锁:建立资源分配表和进程等待表。 - 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 +## 找死锁的步骤 + 所以:找死锁的步骤: 1. 我们通过jps确定当前执行任务的进程号 2. 然后执行jstack命令查看当前进程堆栈信息 -3. 然后将会看到`Found a total of 1 deadlock` \ No newline at end of file +3. 然后将会看到`Found a total of 1 deadlock` + diff --git "a/Interview/sad/\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" "b/Interview/classify/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205.md" similarity index 99% rename from "Interview/sad/\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" rename to "Interview/classify/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205.md" index 92517845..5afc3457 100644 --- "a/Interview/sad/\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" +++ "b/Interview/classify/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205.md" @@ -1,4 +1,3 @@ -> 一定要会手撸 ### synchronized diff --git "a/Interview/sad/\350\260\210\350\260\210\347\272\277\347\250\213\346\261\240.md" "b/Interview/classify/thread/\347\272\277\347\250\213\346\261\240.md" similarity index 97% rename from "Interview/sad/\350\260\210\350\260\210\347\272\277\347\250\213\346\261\240.md" rename to "Interview/classify/thread/\347\272\277\347\250\213\346\261\240.md" index 290e6927..508f0e80 100644 --- "a/Interview/sad/\350\260\210\350\260\210\347\272\277\347\250\213\346\261\240.md" +++ "b/Interview/classify/thread/\347\272\277\347\250\213\346\261\240.md" @@ -1,3 +1,5 @@ +# 线程池 + > 面试必问高频 面试官:了解线程池嘛? @@ -58,6 +60,8 @@ public static ExecutorService newCachedThreadPool() { } ``` +## 线程池参数 + 面试官:讲一下线程池的参数? 我:没问题,ThreadPoolExecutor源码走起: @@ -80,7 +84,7 @@ public ThreadPoolExecutor(int corePoolSize, - ThreadFactory:线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler:拒绝策略 -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) +![线程池参数关系](https://gitee.com/dreamcater/blog-img/raw/master/uPic/线程池参数关系-JgjlWU.png) 面试官:讲一下都有哪些拒绝策略 @@ -99,6 +103,8 @@ public ThreadPoolExecutor(int corePoolSize, 2. 一般来说,如果是IO密集型应用,则线程池大小设置为2N+1。 3. 在IO优化中,线程等待时间所占比例越高,需要越多线程,线程CPU时间所占比例越高,需要越少线程。这样的估算公式可能更适合:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 +## shutdown和shutdownNow + 面试官:shutdown和shutdownNow的区别 我:上源码: @@ -141,6 +147,7 @@ public List shutdownNow() { } ``` +## execute和submit的区别 面试官:execute和submit的区别 diff --git "a/Interview/classify/thread/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" "b/Interview/classify/thread/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" new file mode 100644 index 00000000..d57d40fc --- /dev/null +++ "b/Interview/classify/thread/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" @@ -0,0 +1,173 @@ + +# 线程的创建方式 +面试官:线程的创建方式有哪些? + +我:我目前知道4种:分别如下: + +- Thread + + +```java +public class Test extents Thread { + public void run() { + // 重写Thread的run方法 + System.out.println("dream"); + } + + public static void main(String[] args) { + new Test().start(); + } +} +``` + +- Runnable + +```java +public class Test { + public static void main(String[] args) { + new Thread(() -> { + System.out.println("dream"); + }).start(); + } +} +``` + +- Callable + +```java +public class Test { + public static void main(String[] args) { + // FutureTask 构造方法包装了Callable和Runnable。 + FutureTask task = new FutureTask<>(() -> { + System.out.println("dream"); + return 0; + }); + new Thread(task).start(); + } +} + +``` + +- 线程池 + +```java + +public class Test { + public static void main(String[] args) { + ExecutorService threadPool = Executors.newFixedThreadPool(1); + threadPool.submit(() -> { + System.out.println("dream"); + }); + threadPool.shutdown(); + } +} +``` + +面试官:Runnable和Callable有啥区别? + +我:那得先看源码咯 + +请看 + +```java +@FunctionalInterface +public interface Runnable { + /** + * 被线程执行,没有返回值也无法抛出异常 + */ + public abstract void run(); +} + +@FunctionalInterface +public interface Callable { + /** + * 计算结果,或在无法这样做时抛出异常。 + * @return 计算得出的结果 + * @throws 如果无法计算结果,则抛出异常 + */ + V call() throws Exception; +} +``` + +1. Runnable没有返回值并且无法抛出异常 +2. 不巧,我Callable可以做到你不能做到的 + +线程池的源码后边讲,讲东西,要结合源码和例子讲!!! + + + +> 补充一下主线程等待子线程的两种方式 + +# 主线程等待子线程的两种方式 + +## sleep +> 这个不常用,但是简单一些 +```java +public class Test { + void m() { + System.out.println(Thread.currentThread().getName()); + } + + public static void main(String[] args) { + Test t1 = new Test(); + for (int i = 0; i < 5; i++) { + new Thread(t1::m, "Thread " + i).start(); + } + try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } + System.out.println("main thread"); + } +} +``` + +## join + +```java +public class Test { + void m() { + System.out.println(Thread.currentThread().getName()); + } + + public static void main(String[] args) { + Test t1 = new Test(); + ArrayList threads = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + threads.add(new Thread(t1::m, "Thread " + i)); + } + threads.forEach(o -> o.start()); + threads.forEach(o -> { + try { + o.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + System.out.println("main thread"); + } +} +``` + +## CountDownLatch +```java +public class Test { + private CountDownLatch latch; + + public Test(CountDownLatch latch) { + this.latch = latch; + } + + void m() { + System.out.println(Thread.currentThread().getName()); + latch.countDown(); + } + + public static void main(String[] args) throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(5); + Test t1 = new Test(countDownLatch); + for (int i = 0; i < 5; i++) { + new Thread(t1::m, "Thread " + i).start(); + } + countDownLatch.await(); + System.out.println("main thread"); + } +} +``` diff --git "a/Interview/sad/\347\272\277\347\250\213\344\270\216\350\277\233\347\250\213\347\232\204\345\214\272\345\210\253.md" "b/Interview/classify/thread/\350\277\233\347\250\213\345\222\214\347\272\277\347\250\213.md" similarity index 63% rename from "Interview/sad/\347\272\277\347\250\213\344\270\216\350\277\233\347\250\213\347\232\204\345\214\272\345\210\253.md" rename to "Interview/classify/thread/\350\277\233\347\250\213\345\222\214\347\272\277\347\250\213.md" index 8696b98f..132a3272 100644 --- "a/Interview/sad/\347\272\277\347\250\213\344\270\216\350\277\233\347\250\213\347\232\204\345\214\272\345\210\253.md" +++ "b/Interview/classify/thread/\350\277\233\347\250\213\345\222\214\347\272\277\347\250\213.md" @@ -1,3 +1,26 @@ +# 进程和线程相关问题 + +## 什么是线程安全? +> 我的理解是:多个线程交替执行,本身是没有问题的,但是如果访问共享资源,结果可能会出现问题,于是就出现了线程不安全的问题。 + +1. 访问共享变量或资源 +2. 依赖时序的操作 +3. 不同数据之间存在绑定关系 +4. 对方没有声明自己是线程安全的 + +## 上下文切换 + +多线程编程中一般**线程的个数都大于 CPU 核心的个数**,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配**时间片并轮转**的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 + +实际上就是**任务从保存到再加载的过程就是一次上下文切换**。 + +## 并行与并发 + +- 并行:**单位时间内**,多个任务同时执行。 +- 并发:**同一时间段**,多个任务都在执行 (单位时间内不一定同时执行); + +## 进程与线程 + > 面试官挺喜欢问这个问题的,他能引出来他想要问你的知识点。 面试官:说一下线程与进程的区别 @@ -14,10 +37,24 @@ 举例子:比如,main函数启动了JVM进程,同时main就是其中的线程,并且启动了JVM进程,那么还有垃圾回收等线程。 +或者这样的例子:做个简单的比喻:进程=火车,线程=车厢 + +- 线程在进程下行进(单纯的车厢无法运行) +- 一个进程可以包含多个线程(一辆火车可以有多个车厢) +- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) +- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) +- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) +- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) +- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) +- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁" +- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量” + 面试官:直接调用Thread的run方法不行吗? 我:肯定不行的,通过run方法启动线程其实就是调用一个类中的方法,**当作普通的方法的方式调用**。并没有创建一个线程,**程序中依旧只有一个主线程**,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。如果是start方法,效果就不一样了。 +## start源码 + 首先看一下start源码: ```java @@ -66,9 +103,11 @@ public abstract void run();//抽象方法 - 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 `BLOCKED`(阻塞)状态。 - 线程在执行 Runnable 的` run() `方法结束之后将会进入到 `TERMINATED`(终止) 状态。 +## wait/notify和sleep + 面试官:wait/notify 和 sleep 方法的异同? -:美 +我:ok 相同点: @@ -76,7 +115,8 @@ public abstract void run();//抽象方法 2. 它们都可以响应 **interrupt** 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。 不同点: + 1. wait 方法必须在 **synchronized** 保护的代码中使用,而 sleep 方法并没有这个要求。 2. 在同步代码中**执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁**。 3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。 -4. **wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法**。 \ No newline at end of file +4. **wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法**。 diff --git "a/Interview/sad/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" "b/Interview/sad/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" deleted file mode 100644 index 883e0a95..00000000 --- "a/Interview/sad/\347\272\277\347\250\213\347\232\204\345\210\233\345\273\272\346\226\271\345\274\217.md" +++ /dev/null @@ -1,97 +0,0 @@ -面试官:线程的创建方式有哪些? - -我:我目前知道4种:分别如下: - -Thread - -```java -​```java -public class Test extents Thread { - public void run() { - // 重写Thread的run方法 - System.out.println("dream"); - } - - public static void main(String[] args) { - new Test().start(); - } -} -​``` -``` - -Runnable - -```java -​```java -public class Test { - public static void main(String[] args) { - new Thread(() -> { - System.out.println("dream"); - }).start(); - } -} -​``` -``` - -Callable - -```java -public class Test { - public static void main(String[] args) { - // FutureTask 构造方法包装了Callable和Runnable。 - FutureTask task = new FutureTask<>(() -> { - System.out.println("dream"); - return 0; - }); - new Thread(task).start(); - } -} -​``` -``` - -线程池 - -```java -​```java -public class Test { - public static void main(String[] args) { - ExecutorService threadPool = Executors.newFixedThreadPool(1); - threadPool.submit(() -> { - System.out.println("dream"); - }); - threadPool.shutdown(); - } -} -``` - -面试官:Runnable和Callable有啥区别? - -我:那得先看源码咯 - -请看 - -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} - -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` - -1. Runnable没有返回值并且无法抛出异常 -2. 不巧,我Callable可以做到你不能做到的 - -线程池的源码后边讲 - diff --git "a/Interview/sad/\350\260\210\350\260\210\345\244\232\346\200\201.md" "b/Interview/sad/\350\260\210\350\260\210\345\244\232\346\200\201.md" deleted file mode 100644 index 4ba622a4..00000000 --- "a/Interview/sad/\350\260\210\350\260\210\345\244\232\346\200\201.md" +++ /dev/null @@ -1,114 +0,0 @@ -> 要说,封装和继承呀,问的比较少,还是多态问的多一点,能体现出来一定的钻研程度。 - -面试官:咱们聊聊多态?你是怎么理解多态的呀? - -我:那我可就要按照我的意思去讲了。 - -## 概念 - -三要素:加黑的地方! - -首先我觉得即**一个引用变量到底会指向哪个类的实例对象**,该**引用变量发出的方法调用到底是哪个类中实现的方法**,必须在由程序**运行期间**才能决定。 - - - -## 例子 - -举个例子: - -任何事物的多个姿态,多个形态。比如,你说一个猫在吃东西,同样的,你也能说一个动物在吃东西。 - -```java -public class Test { - public static void main(String[] args){ - Animal animal = new Cat(); - animal.eat() // 猫也会吃饭 - // 你看到了一只猫,同样它也是动物 - // 比如有很多其他种类继承了动物哈, - // 当编译期间的animal引用变量,到底指的哪个实例对象,(重要)(主语是引用变量) - // 或者该引用调用的eat方法,到底是哪个实例对象的eat,编译期间恐怕不知道哦(主语是引用变量) - // 只有运行期间,哦哦, 原来是猫的eat方法哇... - } -} -``` - -## 表现形式 - -所以多态的表现形式: - -- **Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。调用方法时通过传递的参数类型来决定具体使用哪个方法**,这就是多态性。 -- **Java的方法重写,是父类与子类之间的多态性,子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改**。这也是多态性。 - -## 底层 - -首先要说:首先当程序运行需要某个类时,类加载器会将相应的class文件载入到JVM中,并在方法区建立该类的类型信息(包括方法代码,类变量、成员变量以及**方法表**。(标黑的这个玩意) - - - -面试官:方法表有啥? - -我:方法表的结构如同字段表一样,依次包括了**访问标志、名称索引、描述符索引、属性表集合**几项。 - -接着回答:**方法表是实现动态调用的核心。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向记录该类方法的方法表,方法表中的每一个项都是对应方法的指针**。 - -到这里:就要分情况讨论了,一个是方法调用,一个是接口 - -### 方法调用 - -先说方法调用:举个例子 - -```java -class Person { - // 重写object的toString - public String toString(){ - return "I'm a person."; - } - public void eat(){} - public void speak(){} - -} - -class Boy extends Person{ - // 重写object的toString - public String toString(){ - return "I'm a boy"; - } - // 继承Person的speak - public void speak(){} - // 自己实现的自定义方法 - public void fight(){} -} - -class Girl extends Person{ - // 重写object的toString - public String toString(){ - return "I'm a girl"; - } - // 继承Person的speak - public void speak(){} - // 自己实现的自定义方法 - public void sing(){} -} -``` - -![参考](https://imgkr.cn-bj.ufileos.com/aba89335-fb8c-44b8-84ea-ff6a0bfa9548.png) - -这张图的指向:你可以根据颜色对应上,注意方法表条目指向的具体的**方法地址**。其次注意蓝色部分其继承自于 Person 的方法 eat() 和 speak() 分别指向 **Person 的方法实现和本身的实现**。如果子类改写了父类的方法,那么子类和父类的那些**同名的方法共享一个方法表项**。因此,**所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值**。Person 或 Object中的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。 - - - -调用过程: - -1. 在常量池里找到方法调用的**符号引用**(肯定先看到Person定义引用类型) -2. 查看Person的方法表,得到speak方法在该**方法表的偏移量**(假设为15),这样就得到该方法的直接引用。 -3. 根据this(invoker this字节码)指针得到**具体的对象**(即 girl 所指向的位于堆中的对象)。 -4. 根据对象得到该对象对应的方法表,根据偏移量15查看**有无重写(override)该方法**,如果重写,则可以直接调用(Girl的方法表的speak项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person类)的方法表,同样按照这个偏移量15查看有无该方法。 - - - -### 接口调用 - -一个类可以实现多个接口,那么就像多继承一样,这样的话,在方法表中的索引就会不一样,所以Java 对于接口方法的调用是采用**搜索方法表**的方式。[]() - - -[参考](https://blog.csdn.net/huangrunqing/article/details/51996424) \ No newline at end of file From c4c6bbbf46115a61955d00ec881ed84c0c8363a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Thu, 31 Dec 2020 16:34:37 +0800 Subject: [PATCH 061/366] update --- .DS_Store | Bin 10244 -> 10244 bytes Interview/.DS_Store | Bin 12292 -> 14340 bytes Interview/bus/README.md | 11 + ...07\346\234\237\344\272\213\344\273\266.md" | 68 ++++ .../Redis\347\273\221\345\256\232Token.md" | 201 ++++++++++ ...10\344\270\200\350\207\264\346\200\247.md" | 363 ++++++++++++++++++ ...351\201\207\345\210\260\347\232\204bug.md" | 200 ++++++++++ ...350\276\221SQL\350\257\255\345\217\245.md" | 211 ++++++++++ ...57\344\273\230\346\234\215\345\212\241.md" | 141 +++++++ ...55\345\273\272\346\226\207\346\241\243.md" | 64 +++ ...55\350\275\246\346\234\215\345\212\241.md" | 357 +++++++++++++++++ ...50\346\210\267\346\234\215\345\212\241.md" | 74 ++++ ...42\345\215\225\346\234\215\345\212\241.md" | 282 ++++++++++++++ Interview/codes/.DS_Store | Bin 14340 -> 14340 bytes Interview/crazy/.DS_Store | Bin 0 -> 6148 bytes .../crazy/Dubbo.md | 0 .../crazy/JVM.md | 21 +- .../crazy/Java\345\237\272\347\241\200.md" | 0 ...va\345\244\232\347\272\277\347\250\213.md" | 7 +- .../crazy/Java\351\233\206\345\220\210.md" | 0 .../crazy/MySQL.md | 0 .../crazy/Mybatis.md | 0 Interview/crazy/README.md | 11 + .../crazy/Redis.md | 0 .../crazy/RocketMQ.md | 0 .../crazy/Spring.md | 21 +- ...27\346\234\272\347\275\221\347\273\234.md" | 0 Interview/mind/README.md | 13 + Interview/mode/README.md | 6 + ...43\347\220\206\346\250\241\345\274\217.md" | 86 +++++ ...25\344\276\213\346\250\241\345\274\217.md" | 87 +++++ ...45\345\216\202\346\250\241\345\274\217.md" | 81 ++++ ...71\346\263\225\346\250\241\345\274\217.md" | 63 +++ ...60\345\231\250\346\250\241\345\274\217.md" | 136 +++++++ ...37\350\200\205\346\250\241\345\274\217.md" | 113 ++++++ README.md | 56 +-- 36 files changed, 2596 insertions(+), 77 deletions(-) create mode 100644 Interview/bus/README.md create mode 100644 "Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" create mode 100644 "Interview/bus/Redis\347\273\221\345\256\232Token.md" create mode 100644 "Interview/bus/RocketMQ\346\234\200\347\273\210\344\270\200\350\207\264\346\200\247.md" create mode 100644 "Interview/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" create mode 100644 "Interview/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" create mode 100644 "Interview/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" create mode 100644 "Interview/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" create mode 100644 "Interview/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" create mode 100644 "Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" create mode 100644 "Interview/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" create mode 100644 Interview/crazy/.DS_Store rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Dubbo.md" => Interview/crazy/Dubbo.md (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223JVM.md" => Interview/crazy/JVM.md (96%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\237\272\347\241\200.md" => "Interview/crazy/Java\345\237\272\347\241\200.md" (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\244\232\347\272\277\347\250\213.md" => "Interview/crazy/Java\345\244\232\347\272\277\347\250\213.md" (99%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\351\233\206\345\220\210.md" => "Interview/crazy/Java\351\233\206\345\220\210.md" (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223MySQL.md" => Interview/crazy/MySQL.md (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Mybatis.md" => Interview/crazy/Mybatis.md (100%) create mode 100644 Interview/crazy/README.md rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Redis.md" => Interview/crazy/Redis.md (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223RocketMQ.md" => Interview/crazy/RocketMQ.md (100%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Spring.md" => Interview/crazy/Spring.md (96%) rename "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" => "Interview/crazy/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" (100%) create mode 100644 Interview/mind/README.md create mode 100644 Interview/mode/README.md create mode 100644 "Interview/mode/\344\273\243\347\220\206\346\250\241\345\274\217.md" create mode 100644 "Interview/mode/\345\215\225\344\276\213\346\250\241\345\274\217.md" create mode 100644 "Interview/mode/\345\267\245\345\216\202\346\250\241\345\274\217.md" create mode 100644 "Interview/mode/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" create mode 100644 "Interview/mode/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" create mode 100644 "Interview/mode/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" diff --git a/.DS_Store b/.DS_Store index 260490edf2bf23fae17f0a7331ba596189f3efb2..2023ea2acc66a7c539b2ffbb9da0f27c8cf0b037 100644 GIT binary patch literal 10244 zcmeHMYiu0V6+UMik{LY2<6x5*cE~DDsA&>#VyBK$BG#`2RW@5S|sT2p4 z;(+nY7-uLbW+%C{UhTMn9g1;AWrvt(+Sqd8Lg(I&UFnYfor}BD7rMIIccnW!KCrl` zshfA~IXG~7{@nQsA6fk9gHk(;cL->x()OX+syg{%tSWx1tm-ceqoVAU_4-vC8;r(H zS2s2_Ha9n4v$=UoOUrfB#??71e`Yq|uifjre$e9<*ooP|u}2rIyyMMgBX%|#bx5GAfBd`1~J^>fH6;uH#9R7vM4)kG=%xUcimw>bRx(1CRX)MOog-n z0}-1a@Me6Pu}B6VTHtKqaTbI`*T`oltRS$wS=Mvzq-*7vJLL8FQRL5aPCi=R_l5(; zN6I=>=DM`r`bTNj*34kJ&?XB8w8{d7lm?D@);!a+*359+sKQR@hNEyC&cJ!N4<3Q1 z;W@YnufreUP27MjxD9t=CmzIG@irX5K^(?Aa70QgQfcP*6KW-_uaz07fu_EdCScgTq|PTuFI`JZPjbBYPGAnvXh}7 zM6{+f*tB63LN>x^ET_o&Xha&S##*&z+NFZpQyVvL-QKo)Z`UoE{(%pTEDI#+&H5zy zh!;6C4htr%FwZ=jAa|_f*wNg0(FvWL%j6O?;x28uVN1)k*QHETGOP%cS+j+lb7#Sc z&Ka$$D&|O=+w>`$Uht@I^I2@wc$UT?KiFYzGf(iI-hk25js;4gL<=>&HMB7q++Pawr;c6JjJIy zVujJr0`vGQU8Fuz?a(&UoZx-kwk=-h+AS>&=Ae00HC|CC!#OKpLnYdEmgDw2R8_1u zwe!*XZ71?8oyT~m;dn)dHaGGcxyPvl_$9F>t(h3`6}U@78$5)kX2~)TocjL|2j(hL`9wzMHPS_vA(`aE1 z&)@>yi6KU~h|CAbuIYj!)o|_!NE{zmGq_7w|`T34eyK8`m|I^9N$|>9Bm;e70Iat;v delta 46 zcmV+}0MY-1P=rvBPXP(BP`eKS36t*_7PBG{zyh-c83G5ffFlO82O#AVv+th_8Gm1-X7G(Oyx>G6?_x diff --git a/Interview/bus/README.md b/Interview/bus/README.md new file mode 100644 index 00000000..c9d95da6 --- /dev/null +++ b/Interview/bus/README.md @@ -0,0 +1,11 @@ +- 项目地址:[微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) +- [环境搭建文档](Java/bus/环境搭建文档.md) +- [Redis绑定Token分析文档](Java/bus/Redis绑定Token.md) +- [用户服务所有接口分析文档](Java/bus/用户服务.md) +- [班车服务所有接口分析文档](Java/bus/班车服务.md) +- [订单服务所有接口分析文档](Java/bus/订单服务.md) +- [支付服务所有接口分析文档](Java/bus/支付服务.md) +- [添加订单、支付和退款的业务结合消息队列](Java/bus/RocketMQ最终一致性.md) +- [Redis的key过期事件结合自动取消订单业务](Java/bus/Redis的key过期事件.md) +- [SQL语句调优](Java/bus/业务逻辑SQL语句.md) +- [Zookeeper的bug之一](Java/bus/上线遇到的bug.md) \ No newline at end of file diff --git "a/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" "b/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" new file mode 100644 index 00000000..4e2167ac --- /dev/null +++ "b/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" @@ -0,0 +1,68 @@ +# 订单自动取消 + +> 如果系统的并发量不是特别高的,或者日访问量不是特别高的话,可以使用Redis的key过期事件,如果访问量过高,还是采用延迟队列吧。 + +## Redis的配置 +``` +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "Ex" +``` + +## RedisListenerConfig配置 +```java +@Configuration +public class RedisListenerConfig { + @Bean + RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } +} +``` + +## RedisKeyExpirationListener监听事件 + +```java +@Component +@Slf4j +public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { + public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { + super(listenerContainer); + } + + @Autowired + private IOrderService orderService; + + /** + * 监听key过期事件 + * @param message + * @param pattern + */ + @Override + public void onMessage(Message message, byte[] pattern) { + super.onMessage(message, pattern); + String expiredKey = message.toString(); + log.info("redis key过期:{}",expiredKey); + String orderCancleKey = RedisConstants.ORDER_CANCLE_EXPIRE.getKey(); + if (expiredKey.startsWith(orderCancleKey)) { + // 获取订单id + String[] strings = expiredKey.split(orderCancleKey); + String orderId = strings[1]; + log.warn("过期订单ID:" + orderId); + // 得到过期订单。 + // 1。 更改订单状态就完事了 + OrderRequest request = new OrderRequest(); + request.setOrderStatus("2"); // 关闭订单 + request.setUuid(Convert.toLong(orderId)); + orderService.updateOrderStatus(request); + log.warn("过期订单已处理:" + orderId); + } + } +} +``` \ No newline at end of file diff --git "a/Interview/bus/Redis\347\273\221\345\256\232Token.md" "b/Interview/bus/Redis\347\273\221\345\256\232Token.md" new file mode 100644 index 00000000..550fbd30 --- /dev/null +++ "b/Interview/bus/Redis\347\273\221\345\256\232Token.md" @@ -0,0 +1,201 @@ +# Redis绑定Token +> 先介绍一下概念 + +## cookie +HTTP 协议是**无状态的**,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 + +Cookie 是服务器发送到用户浏览器并保存在**本地的一小块数据**,它会在浏览器之后向同一服务器**再次发起请求时被携带上**,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 + +### 用途 +- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) +- 个性化设置(如用户自定义设置、主题等) +- 浏览器行为跟踪(如跟踪分析用户行为等) + +## session +除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 + +Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 + +使用 Session 维护用户登录状态的过程如下: +1. 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; +2. 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; +3. 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; +4. 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 + +> 注意:Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 + +### session和cookie选择 +- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; +- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; +- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 + +## Token +实际上,Token是在服务端将用户信息经过Base64Url编码过后传给在客户端。每次用户请求的时候都会带上这一段信息,因此服务端拿到此信息进行解密后就知道此用户是谁了,这个方法叫做JWT(Json Web Token)。 + +## JWT +> JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 + +cookie+session这种模式通常是保存在内存中,而且服务从**单服务到多服务会面临的session共享问题**,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。 + +### 构成 +第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。详情请见[官网](https://jwt.io/introduction/) + +简单说一下 + +#### header +是一个Json对象,描述JWT的元数据,通常是下面这样子的: +```json +{ + "alg": "HS256", + "typ": "JWT" +} +``` +上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。 + + +#### payload +Payload部分也是一个Json对象,用来存放实际需要传输的数据,JWT官方规定了下面几个官方的字段供选用。 + +- iss (issuer):签发人 +- exp (expiration time):过期时间 +- sub (subject):主题 +- aud (audience):受众 +- nbf (Not Before):生效时间 +- iat (Issued At):签发时间 +- jti (JWT ID):编号 + +也可以定义私有属性 + +#### signature +Signature部分是对前面的两部分的数据进行签名,防止数据篡改。 + +首先需要定义一个秘钥,这个秘钥只有服务器才知道,不能泄露给用户,然后使用Header中指定的签名算法(默认情况是HMAC SHA256),算出签名以后将Header、Payload、Signature三部分拼成一个字符串,每个部分用.分割开来,就可以返给用户了。 + +## 在项目中如何写 + +### JwtTokenUtil +这里是使用guns搭建的项目,自带的工具类,我就不详细赘述了,主要是一下我们的业务逻辑,以及如何使用。 + + +### AuthController +> sb-gateway-auth-AuthController + +能请求到AuthContoroller,说明两种情况: +1. 你没有携带token +2. 你token过期了 + +而这个业务处理,是为了生成token + +```java + @ApiOperation(value = "获取token接口", notes = "每调用一次,就会随机生成一串token", response = ResponseData.class) + @RequestMapping(value = "${jwt.auth-path}") + public ResponseData createAuthenticationToken(@Validated AuthRequest authRequest, BindingResult bindingResult) { + //Validated 的作用是抵消了使用很多if判断 + if (bindingResult.hasErrors()) { + for (FieldError fieldError : bindingResult.getFieldErrors()) { + log.error("参数:{}校验失败,原因:{}", fieldError.getField(), fieldError.getDefaultMessage()); + } + return new ResponseUtil<>().setErrorMsg("用户参数设置错误:" + CommonBindingResult.getErrors(bindingResult)); + } + // 获取用户的账号和密码 + UserLoginRequst req = new UserLoginRequst(); + req.setUsername(authRequest.getUserName()); + req.setPassword(authRequest.getPassword()); + // 调用userAPI.login业务获取用户id + UserLoginResponse res = userAPI.login(req); + String userId = "" + res.getUserId(); + if (res.getUserId() != 0) { + // 如果id不等于0,说明账户存 + // 从redis看一下userId在不在 + if (redisUtils.hasKey(userId)) { + // 如果存在,两种情况:1.你在其他登陆,2.别人登陆 + // 删除redis中的键为userId的 + redisUtils.del(userId); + } + // 以下两步,针对于userId生成token + res.setRandomKey(jwtTokenUtil.getRandomKey()); + String token = jwtTokenUtil.generateToken(userId, res.getRandomKey()); + res.setToken(token); + // 写进redis + redisUtils.set(userId, token, RedisConstants.TOKEN_EXPIRE.getTime()); + // 返回给用户 + return new ResponseUtil<>().setData(res); + } else { + // 如果id等于0,那么说明两种情况:1.账号密码错误,2.账户不存在 + return new ResponseUtil<>().setErrorMsg("账号密码错误"); + } + } +``` + +### AuthFilter +> sb-gateway->auth->filter->AuthFileter + +#### 忽略列表 +```java +// 配置忽略列表 +String ignoreUrl = jwtProperties.getIgnoreUrl(); // 获取忽略的url +String[] ignoreUrls = ignoreUrl.split(","); +for(int i=0;i().setData(response1)); + return; + } else { + // 如果存在,去取token + String token = (String) redisUtils.get(userId); + // 判断redis的token和当前的浏览器的token是否一致 + if(!token.equals(authToken)) { + // 如果不相等,说明别人已经登录了,你当前的token无效,需要重新登录,将它替换掉。 + // 比如,两个手机不能同时登录同一个账户的QQ + CommonResponse response1 = new CommonResponse(); + response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); + response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); + RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); + return; + } + } + } catch (Exception e) { + // 如果发生异常,所携带的token有问题,防止爬虫造token + CommonResponse response1 = new CommonResponse(); + response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); + response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); + RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); + return; + } + } else { + //header没有带Bearer字段 + CommonResponse response1 = new CommonResponse(); + response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); + response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); + RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); + return; + } + chain.doFilter(request, response); + } +``` + +**以上流程图,日后补充** \ No newline at end of file diff --git "a/Interview/bus/RocketMQ\346\234\200\347\273\210\344\270\200\350\207\264\346\200\247.md" "b/Interview/bus/RocketMQ\346\234\200\347\273\210\344\270\200\350\207\264\346\200\247.md" new file mode 100644 index 00000000..60dd5547 --- /dev/null +++ "b/Interview/bus/RocketMQ\346\234\200\347\273\210\344\270\200\350\207\264\346\200\247.md" @@ -0,0 +1,363 @@ +# RocketMQ + +> 该项目主要在添加订单,支付和回退的时候采用了RocketMQ消息中间件。 目的是系统发生异常时,保持系统的最终一致性。 + +## MQTags +> 异常种类 +```java +public enum MqTags { + ORDER_CANCEL("order_cancel", "订单取消异常"), + ORDER_SEATS_CANCEL("order_seats_cancel", "判断座位异常"), + ORDER_ADD_SEATS_CANCLE("order_add_seats_cancle", "更新座位异常"), + ORDER_CALC_MONEY_CANCLE("order_calc_money_cancle", "计算总金额异常"), + ORDER_ADD_CANCLE("order_add_cancle", "添加订单异常"), + PAY_CANCLE("pay_cancle", "支付异常"), + PAY_CHECK_CANCLE("pay_check_cancle", "校验支付密码和余额"), + PAY_MONEY_CANCLE("pay_money_cancle", "支付余额写入异常"), + ; + private String tag; + private String message; + + + MqTags(String tag, String message) { + this.tag = tag; + this.message = message; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} +``` + +## 添加订单 +### addOrder +```java + /** + * 添加订单,这里比较重要 + * @param request + * @return + */ + @Override + public AddOrderResponse addOrder(AddOrderRequest request) { + // 判断座位,如果重复,直接退出,否则更新场次的座位信息 + AddOrderResponse response = new AddOrderResponse(); + // 全局orderId + Long orderId = UUIDUtils.flakesUUID(); + // 1。 判断座位,如果重复,直接退出,否则下一步 + // 2。 更新座位,如果没有异常,这是写操作 + // 3。 计算总金额,如果没有异常 + // 4。 添加订单,如果异常,这是写操作 + try { + // 1。 判断座位,如果重复,直接退出,否则下一步 + tag = MqTags.ORDER_SEATS_CANCEL.getTag(); + boolean repeatSeats = busService.repeatSeats(request.getSeatsIds(), request.getCountId()); + if (repeatSeats) { + // b:true 说明重复 + response.setCode(SbCode.SELECTED_SEATS.getCode()); + response.setMsg(SbCode.SELECTED_SEATS.getMessage()); + return response; + } +// CastException.cast(SbCode.SYSTEM_ERROR); + // 2。 更新座位,如果没有异常,这是写操作 + // 用tags来过滤消息 + tag = MqTags.ORDER_ADD_SEATS_CANCLE.getTag(); + boolean addSeats = busService.addSeats(request.getSeatsIds(), request.getCountId()); + if (!addSeats) { + response.setCode(SbCode.DB_EXCEPTION.getCode()); + response.setMsg(SbCode.DB_EXCEPTION.getMessage()); + return response; + } + // 模拟系统异常 +// CastException.cast(SbCode.SYSTEM_ERROR); + // 3。 计算总金额,如果没有异常 + tag = MqTags.ORDER_CALC_MONEY_CANCLE.getTag(); + String seatIds = request.getSeatsIds(); + Integer seatNumber = seatIds.split(",").length; + Double countPrice = request.getCountPrice(); + Double totalPrice = getTotalPrice(seatNumber, countPrice); + +// CastException.cast(SbCode.SYSTEM_ERROR); + // 4。 添加订单,如果异常,这是写操作 + Order order = orderConvertver.res2Order(request); + order.setOrderPrice(totalPrice); + order.setEvaluateStatus("0"); // 未评价 + order.setOrderStatus("0"); // 未支付 + order.setUuid(orderId); // 唯一id + tag = MqTags.ORDER_ADD_CANCLE.getTag(); + int insert = orderMapper.insert(order);// 插入 不判断了 +// CastException.cast(SbCode.SYSTEM_ERROR); + // 这里就不读了,耗时 +// QueryWrapper wrapper = new QueryWrapper<>(); +// wrapper.eq("so.uuid", order.getUuid()); +// OrderDto orderDto = orderMapper.selectOrderById(wrapper); + response.setCode(SbCode.SUCCESS.getCode()); + response.setMsg(SbCode.SUCCESS.getMessage()); + response.setOrderId(orderId); +// response.setOrderDto(orderDto); + // 这里放redis 未支付缓存,时间前端给定 + redisUtils.set(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + orderId, orderId, request.getExpireTime()); + return response; + } catch (Exception e) { + // 以上操作如果程序都不发生异常的话, 是不会执行这里的代码的 + // 也就是说不会发送回退消息的。 + // 目的是在高并发的情况下,程序内部发生异常,依然高可用 +// e.printStackTrace(); + log.error("订单业务发生异常"); + // 发消息,将座位退回,将订单退回 + MQDto mqDto = new MQDto(); + mqDto.setOrderId(orderId); + mqDto.setCountId(request.getCountId()); + mqDto.setSeatsIds(request.getSeatsIds()); + try { + String key = RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(orderId); + sendCancelOrder(topic,tag, key, JSON.toJSONString(mqDto)); + log.warn("订单回退消息发送成功..." + mqDto); + } catch (Exception ex) { + ex.printStackTrace(); + } + response.setCode(SbCode.SYSTEM_ERROR.getCode()); + response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); + return response; + } + } +``` + +### sendCancelOrder +```java + /** + * 发送订单回退消息 + * @param topic + * @param tag + * @param keys + * @param body + * @throws Exception + */ + private void sendCancelOrder(String topic, String tag, String keys, String body) throws Exception{ + // 封装消息 + Message message = new Message(topic,tag,keys,body.getBytes()); + // 消息生产者发送消息,默认用自带的消息生产者 + rocketMQTemplate.getProducer().send(message); + } +``` + +### OrderSeatsCancleListener(座位异常) +```java + /** + * 回退座位 + * @param messageExt + */ + @Override + public void onMessage(MessageExt messageExt) { + try { + // 1. 解析消息 + String tags = messageExt.getTags(); + String orderTag = MqTags.ORDER_SEATS_CANCEL.getTag(); + // 过滤标签 + if (tags.equals(orderTag)) { + return; + } + String key = messageExt.getKeys(); + System.out.println("取消订单消息:" + key); + // Redis保持消费幂等性 + if (!redisUtils.hasKey(key)) { + String body = new String(messageExt.getBody(), "UTF-8"); + log.warn("收到订单服务异常:" + body); + MQDto mqDto = JSON.parseObject(body, MQDto.class); + // 判断需要的值在不在 + if (mqDto.getCountId() != null && mqDto.getSeatsIds() != null) { + // 2. 调用业务,回退座位 + boolean b = busService.filterRepeatSeats(mqDto.getSeatsIds(), mqDto.getCountId()); + if (b) { + log.warn("回退座位成功"); + redisUtils.set(key, mqDto.getOrderId(), RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getTime()); + } + } + } + } catch (UnsupportedEncodingException e) { + log.error("座位回退程序崩了...好好检查程序吧", e); + } + } +``` + +### OrderAddCancleListener(订单异常) +```java + /** + * 取消订单 + * @param messageExt + */ + @Override + public void onMessage(MessageExt messageExt) { + try { + // 1. 解析消息 + String tags = messageExt.getTags(); + String orderAddTag = MqTags.ORDER_ADD_CANCLE.getTag(); + // 过滤标签 + if (!tags.equals(orderAddTag)) { + return; + } + String key = messageExt.getKeys(); + // Redis保持消费幂等性 + if (!redisUtils.hasKey(key)) { + String body = new String(messageExt.getBody(), "UTF-8"); + log.warn("收到订单服务异常:" + body); + MQDto mqDto = JSON.parseObject(body, MQDto.class); + if (mqDto.getOrderId() != null) { + // 2. 程序异常或者系统内部异常导致的订单,因此我认为删除该订单。 + // 该订单有可能没有插入成功程序就异常了。 + orderService.deleteOrderById(mqDto.getOrderId()); + log.warn("异常订单已删除"); + redisUtils.set(key, mqDto.getOrderId(), RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getTime()); + } + } + } catch (UnsupportedEncodingException e) { + log.error("订单消费信息程序崩...", e); + } + } +``` + +## 支付 +### pay +```java + /** + * 支付业务逻辑 + * @param requset + * @return + */ + @Override + public PayResponse pay(PayRequset requset) { + PayResponse payResponse = new PayResponse(); + Long userId = requset.getUserId(); + Double userMoney = null; + try { + // 1. 先核对支付密码是否正确 + tag = MqTags.PAY_CHECK_CANCLE.getTag(); + String key = RedisConstants.USER_INFO_EXPIRE.getKey() + userId; + UserResponse userResponse = new UserResponse(); + if (redisUtils.hasKey(key)) { + userResponse = (UserResponse) redisUtils.get(key); + } else { + UserRequest request = new UserRequest(); + request.setId(userId); + // 获取用户信息 + userResponse = userService.getUserById(request); + } + + // 支付密码不对 + if (!userResponse.getUserDto().getPayPassword().equals(requset.getPayPassword())) { + payResponse.setCode(SbCode.PAY_PASSWORD_ERROR.getCode()); + payResponse.setMsg(SbCode.PAY_PASSWORD_ERROR.getMessage()); + return payResponse; + } + // 2。 核对余额是否够 + userMoney = userResponse.getUserDto().getMoney(); + Double subMoney = NumberUtil.sub(userMoney, requset.getTotalMoney()); + BigDecimal round = NumberUtil.round(subMoney, 2); + if (round.doubleValue() < 0) { + payResponse.setCode(SbCode.MONEY_ERROR.getCode()); + payResponse.setMsg(SbCode.MONEY_ERROR.getMessage()); + return payResponse; + } + // 3。 够,就写入 + UserUpdateInfoRequest request = new UserUpdateInfoRequest(); + request.setId(userId); + request.setMoney(round.doubleValue()); + tag = MqTags.PAY_MONEY_CANCLE.getTag(); + userService.updateUserInfo(request); // 暂时先不接受返回信息 + // 模拟异常 +// CastException.cast(SbCode.SYSTEM_ERROR); + payResponse.setCode(SbCode.SUCCESS.getCode()); + payResponse.setMsg(SbCode.SUCCESS.getMessage()); + // 4. 按道理讲,这边更改订单状态...... + return payResponse; + } catch (Exception e) { + log.error("支付业务发生异常"); + MQDto mqDto = new MQDto(); + mqDto.setUserId(userId); + mqDto.setUserMoney(userMoney); + // 发送消息 + try { + String key = RedisConstants.PAY_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(userId); + sendCancelPay(topic,tag,key, JSON.toJSONString(mqDto)); + log.warn("支付回退消息已发送"); + } catch (Exception ex) { + ex.printStackTrace(); + log.error("支付消息都崩的话..."); + } + payResponse.setCode(SbCode.SYSTEM_ERROR.getCode()); + payResponse.setMsg(SbCode.SYSTEM_ERROR.getMessage()); + return payResponse; + } + + } +``` +### sendCancelPay +```java + /** + * 发送支付消息 + * @param topic + * @param tag + * @param keys + * @param body + * @throws Exception + */ + private void sendCancelPay(String topic, String tag, String keys, String body) throws Exception { + Message message = new Message(topic,tag,keys,body.getBytes()); + rocketMQTemplate.getProducer().send(message); + } +``` + +### PayMoneyCancleListener +```java + /** + * 支付金额异常 + * @param messageExt + */ + @Override + public void onMessage(MessageExt messageExt) { + try { + // 1. 解析消息 + String tags = messageExt.getTags(); + String payCancleTag = MqTags.PAY_MONEY_CANCLE.getTag(); + if (!tags.equals(payCancleTag)) { + return; + } + // 2. 拿到key + String key = messageExt.getKeys(); + if (!redisUtils.hasKey(key)) { + String body = new String(messageExt.getBody(), "UTF-8"); + log.warn("收到订单服务异常:" + body); + MQDto mqDto = JSON.parseObject(body, MQDto.class); + if (mqDto.getUserId() != null && mqDto.getUserMoney() != null) { + UserUpdateInfoRequest request = new UserUpdateInfoRequest(); + request.setId(mqDto.getUserId()); + request.setMoney(mqDto.getUserMoney()); + userService.updateUserInfo(request); + log.warn("余额已恢复"); + redisUtils.set(key, mqDto.getUserId(), RedisConstants.PAY_EXCEPTION_CANCLE_EXPIRE.getTime()); + } + } + } catch (Exception e) { + log.error("支付消费信息程序崩...\n", e); + } + } +``` + +## 回退 +### payBack +> 这里可以写的,我省了... + +## 再谈 +关于项目中只是采用了RocketMQ维持了系统的最终一致性,其他的优点,限流等都没有用上,也可以用上的。以上流程图我也没时间画,来不及。 \ No newline at end of file diff --git "a/Interview/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" "b/Interview/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" new file mode 100644 index 00000000..dd379ca9 --- /dev/null +++ "b/Interview/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" @@ -0,0 +1,200 @@ +## 上线bug + +> 项目上线之后,大概将近一个月,我点击车次列表页面,突然什么都没有了,于是就开始找哪个不顺眼的家伙搞的鬼。 + +### 起因 + +访问主页[http://47.104.22.225:8080/home](http://47.104.22.225:8080/home) + +![](https://imgkr.cn-bj.ufileos.com/8c563c2e-49f8-4a96-8d02-0109bc956449.png) + +如图: + +一直加载中,于是我看一下响应信息。 + +```json +code: 200 +message: "success" +result: {code: "003099", msg: "系统错误"} +success: true +timestamp: "1589715142518" +``` + +### 查看一下gateway的日志 +- 终端显示不长,于是我将日志用vscode打开看 + +```log +org.apache.dubbo.rpc.RpcException: No provider available from registry 39.108.93.119:2181 for service com.stylefeng.guns.rest.bus.IBusService on consumer 192.168.31.221 use dubbo version 2.7.4.1, please check status of providers(disabled, not registered or in blacklist). +``` + +类似于这样的信息,说我们的bus服务没有注册。 + +> 注意:出事之前,几个服务都在的呀,怎么今天bus突然不在了。于是,我不相信,我就去dubbo后台看了一下... + +- 进入文件夹`dubbo-admin -> dubbo-admin-ui` +- 执行`npm run dev` +- 在我目前的mac上chrome浏览器输入`http://dubbo.dreamcat.ink:2020/` + +**此时,还真没有bus服务** +![](https://imgkr.cn-bj.ufileos.com/3eb38910-64b5-439c-8437-c155d50857d2.png) + +**哭晕在厕所**。 + +![](https://imgkr.cn-bj.ufileos.com/e391627b-dce7-4146-b6d7-78cbd1fee35e.jpg) + +**于是乎,俺又不想重新启动服务,毕竟你看** + +- 终端输入`ps -ef | grep guns-bus` +- 我们看到了惊人的一幕 +```shell +pch 2003942 1 0 4月16 ? 06:10:54 java -jar guns-bus-0.0.1.jar +``` + +**又哭晕在厕所...** + +### 猜测 + +#### DubboAdmin展示? +好像没问题吧?这样的话,其他三个也应该不存在的啊 + +#### 注册中心,bus节点丢了 + +- 查看日志,当天出现zk出现了大量的超时,原因是当天的zk**主节点**宕机了。 + +#### 找原因 + +**问题是否出现在了dubbo对zk重连恢复数据这块,开始查源码。注册中心源码ZookeeperRegistry。** + +1. 连接注册zk:通过zkclient添加zk状态监听。并且继承了FailbackRegistry各种失败重试。 + +```java +public class ZookeeperRegistry extends FailbackRegistry { + public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { + ... + // 1. 连接zk + zkClient = zookeeperTransporter.connect(url); + // 2. 添加zk状态监听 + zkClient.addStateListener(new StateListener() { + @Override + public void stateChanged(int state) { + // 3. 重新连接后恢复动作,将当前的注册服务于订阅任务添加至重试列表中等待重试 + if (state == RECONNECTED) { + try { + recover(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + }); + } +} + +``` + +2. zk客户端:默认使用CuratorZookeeperClient实现 + +```java +public class CuratorZookeeperClient extends AbstractZookeeperClient { + public CuratorZookeeperClient(URL url) { + ... + client = builder.build(); + // dubbo对接zk连接状态监听器 + client.getConnectionStateListenable().addListener(new ConnectionStateListener() { + @Override + public void stateChanged(CuratorFramework client, ConnectionState state) { + if (state == ConnectionState.LOST) { + CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); + } else if (state == ConnectionState.CONNECTED) { + CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); + } else if (state == ConnectionState.RECONNECTED) { + CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); + } + } + }); + client.start(); + ... + } +} +``` + +3. 重试任务:注册重新失败重连任务FailbackRegistry中的DubboRegistryFailedRetryTimer,默认5秒检查一次是否需要失败恢复 + +```java +public FailbackRegistry(URL url) { + super(url); + this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD); + this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + // Check and connect to the registry + try { + // failedRegistered失败注册重试,failedUnregistered失败注销重试,failedSubscribed失败订阅重试,failedUnsubscribed失败取消订阅重试,failedNotified失败通知重试 + retry(); + } catch (Throwable t) { // Defensive fault tolerance + logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t); + } + } + }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS); +} + +``` + +#### 总结一波 +通过三部分的代码我们可以推断,如果zk状态监听与恢复部分出现问题可能会导致数据丢失问题。于是查看相关的api并且尝试查看dubbo社区的问题与bug,果然发现了类似问题的修改与原因分析:[https://github.com/apache/dubbo/pull/5135](https://github.com/apache/dubbo/pull/5135) + +原因: +> 如果ZNode数据已经存在,在会话超时期间,此时我们将重建一个数据节点,这个重复的异常原因可能是由于zk server中老的超时会话依然持有节点导致该节点的delete删除事件延迟,并且zk server还没有来得及去执行删除,可能由这种场景引起。在这个情景下,我们可以本地删除节点后再创建恢复节点数据。 + +其实说白了: +> 如果会话断开连接又重新连接成功。断开连接发出的删除节点事件,**因为延迟原因走在了重新连接恢复节点事件的后面**。**导致重新连接后没能成功恢复节点**。也就是我么见到的,provider有三个节点服务正常,但是zk注册中心中的提供者节点数据丢失,导致出现该节点对其他订阅者不可见的现象 + +```java + protected void createEphemeral(String path, String data) { + byte[] dataBytes = data.getBytes(CHARSET); + + try { + ((ACLBackgroundPathAndBytesable)this.client.create().withMode(CreateMode.EPHEMERAL)).forPath(path, dataBytes); + } catch (NodeExistsException var5) { + logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration, this duplication might be caused by a delete delay from the zk server, which means the old expired session may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, we can just try to delete and create again.", var5); + this.deletePath(path); + this.createEphemeral(path, data); + } catch (Exception var6) { + throw new IllegalStateException(var6.getMessage(), var6); + } + + } +``` + +**人家源码的warn写的是真清楚**... + +### 定位cpu过高的线程或者位置 + +> 这里要用到几个命令了,比如top,jstack等。 + +#### 查看一波 +- 终端输入`top -H` + +![](https://imgkr.cn-bj.ufileos.com/e69d6335-c260-4d69-9f4d-840b7427cce5.png) +- 类似于长这样,但是有些含义就不解释了,请上互联网 +- 我就挑它吧`1227027` +- 终端输入`top -Hp 1227027` +![](https://imgkr.cn-bj.ufileos.com/d703c8ae-a3a8-4eed-ad57-6c3440dd4ea7.png) +- 这里我也就不介绍了,找一个`1227029` +- `printf "%x\n" 1227029`结果是:`12b915` +- `jstack -l 1227027| grep 0x12b915 -A 10` + +```shell +"DestroyJavaVM" #84 prio=5 os_prio=0 tid=0x00007f69f800b7c0 nid=0x12b915 waiting on condition [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + + Locked ownable synchronizers: + - None + +"BrokerFastFailureScheduledThread1" #83 prio=5 os_prio=0 tid=0x00007f69f8b29050 nid=0x12ba0a runnable [0x00007f68b6bec000] + java.lang.Thread.State: TIMED_WAITING (parking) + at sun.misc.Unsafe.park(Native Method) + - parking to wait for <0x0000000080594a60> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) +``` +**以上就是举个例子** ... \ No newline at end of file diff --git "a/Interview/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" "b/Interview/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" new file mode 100644 index 00000000..6ef6d800 --- /dev/null +++ "b/Interview/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" @@ -0,0 +1,211 @@ +## Explain + +> 分别对应: +> +> id select_type table type possible_keys key key_len ref rows Extra +> +> 参考:[https://juejin.im/post/5ec4e4a5e51d45786973b357](https://juejin.im/post/5ec4e4a5e51d45786973b357) + +## 用户服务 + +### checkUsername + +```sql +SELECT + * +FROM + sb_user su +WHERE + user_name = 'mai'; +``` + +- 唯一索引 + +``` +1 "SIMPLE" "su" "const" "user_name" "user_name" "152" "const" 1 "" +``` + +- 普通索引 + +```sql +1 "SIMPLE" "su" "ref" "user_name" "user_name" "152" "const" 1 "Using index condition" +``` + +- 不加索引 + +```sql +1 "SIMPLE" "su" "ALL" NULL NULL NULL NULL 11 "Using where" +``` + +### register省略 + +### login省略 + +### getUserById省略 + +### updateUserInfo省略 + +## 班车服务 + +### getBus省略 + +### getCount + +```sql +SELECT + sc.uuid, + sc.begin_date, + sc.begin_time, + sc.bus_id, + sc.bus_status, + sc.seat_status +FROM + sb_count sc +WHERE + sc.begin_date = '2020-06-03' + AND sc.begin_time >= '14:00' + AND sc.bus_status = '1'; +``` + +- 不加索引 + +``` +1 "SIMPLE" "sc" "ALL" NULL NULL NULL NULL 581 "Using where" + +53ms +``` + +- 给begin_date加普通索引 + +``` +1 "SIMPLE" "sc" "ref" "begin_date" "begin_date" "402" "const" 17 "Using index condition; Using where" + +53ms // 数据太少,体现不出来... +``` + +- 给begin_time加普通索引 + +``` +1 "SIMPLE" "sc" "ref" "begin_date,begin_time" "begin_date" "402" "const" 17 "Using index condition; Using where" + +53ms +``` + +- 给bus_status加普通索引 + +``` +1 "SIMPLE" "sc" "index_merge" "begin_date,begin_time,bus_status" "begin_date,bus_status" "402,152" NULL 7 "Using intersect(begin_date,bus_status); Using where" + +53ms +``` + +### getCountDetailById省略 + +## 订单服务 + +### getNoTakeOrdersById + +```sql +SELECT + so.uuid, + so.bus_status, + so.seats_ids, + so.order_user, + so.order_status, + so.order_time, + sc.bus_id, + CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time +FROM + sb_order so + LEFT JOIN sb_count sc ON so.count_id = sc.uuid +WHERE + so.user_id = '4' + AND so.order_status = '1' + AND sc.begin_date >= '2020-06-03' + AND sc.begin_time >= '15:00'; +``` + +- 不加普通索引 + +``` +1 "SIMPLE" "so" "ALL" NULL NULL NULL NULL 45 "Using where" +1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" +``` + +- user_id+order_status加普通索引 + +``` +1 "SIMPLE" "so" "ref" "order_status,user_id" "order_status" "152" "const" 10 "Using index condition; Using where" +1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" +``` + +### getEvaluateOrdersById + +```sql +SELECT + so.uuid, + so.bus_status, + so.seats_ids, + so.order_user, + so.order_time, + sc.bus_id, + so.evaluate_status, + so.comment, + CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time +FROM + sb_order so + LEFT JOIN sb_count sc ON so.count_id = sc.uuid +WHERE + so.user_id = 4 + AND so.evaluate_status = "1" + AND so.order_status = "1" + AND (sc.begin_date = "2020-05-30" AND sc.begin_time < "21:00" OR sc.begin_date < "2020-05-30") + +``` + +- 带了普通索引 + +``` +1 "SIMPLE" "so" "ref" "user_id_order_eval_status" "user_id_order_eval_status" "312" "const,const,const" 1 "Using index condition" +1 "SIMPLE" "sc" "eq_ref" "PRIMARY" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" +``` + +### getNoPayOrdersById + +```sql +SELECT + so.uuid, + so.bus_status, + so.seats_ids, + so.order_user, + so.order_status, + so.order_time, + sc.bus_id, + CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time +FROM + sb_order so + LEFT JOIN sb_count sc ON so.count_id = sc.uuid +WHERE + so.user_id = '4' + AND so.order_status = '0' + AND sc.begin_date >= '2020-06-03' + AND sc.begin_time >= '20:00'; +``` + +- 带了普通索引 + +``` +1 "SIMPLE" "so" "ref" "order_status,user_id" "order_status" "152" "const" 8 "Using index condition; Using where" +1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" +``` + +### addOrder省略 + +### selectOrderById省略 + +### updateOrderStatus省略 + +### deleteOrderById省略 + +## 支付服务省略 + diff --git "a/Interview/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" "b/Interview/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" new file mode 100644 index 00000000..4f1bfb8b --- /dev/null +++ "b/Interview/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" @@ -0,0 +1,141 @@ +# 支付服务 + +## PayController + +### pay +> 省略 + +### payBack +> 退款 +```java +// 删除个人详细信息缓存 +if (redisUtils.hasKey(userInfoKey)) { + redisUtils.del(userInfoKey); +} +// 删除未支付订单列表缓存 +if (redisUtils.hasKey(noTakeKey)) { + redisUtils.del(noTakeKey); +} +// 删除订单详情缓存 +if (redisUtils.hasKey(selectOrderKey)) { + redisUtils.del(selectOrderKey); +} +// 删除场次详情缓存 +if (redisUtils.hasKey(countDetailKey)) { + redisUtils.del(countDetailKey); +} +``` + +## PayServiceImpl + +### pay +```java +public PayResponse pay(PayRequset requset) { + PayResponse payResponse = new PayResponse(); + Long userId = requset.getUserId(); + Double userMoney = null; + try { + // 1. 先核对支付密码是否正确 + tag = MqTags.PAY_CHECK_CANCLE.getTag(); + String key = RedisConstants.USER_INFO_EXPIRE.getKey() + userId; + UserResponse userResponse = new UserResponse(); + if (redisUtils.hasKey(key)) { + userResponse = (UserResponse) redisUtils.get(key); + } else { + UserRequest request = new UserRequest(); + request.setId(userId); + // 获取用户信息 + userResponse = userService.getUserById(request); + } + + // 支付密码不对 + if (!userResponse.getUserDto().getPayPassword().equals(requset.getPayPassword())) { + payResponse.setCode(SbCode.PAY_PASSWORD_ERROR.getCode()); + payResponse.setMsg(SbCode.PAY_PASSWORD_ERROR.getMessage()); + return payResponse; + } + // 2。 核对余额是否够 + userMoney = userResponse.getUserDto().getMoney(); + Double subMoney = NumberUtil.sub(userMoney, requset.getTotalMoney()); + BigDecimal round = NumberUtil.round(subMoney, 2); + if (round.doubleValue() < 0) { + payResponse.setCode(SbCode.MONEY_ERROR.getCode()); + payResponse.setMsg(SbCode.MONEY_ERROR.getMessage()); + return payResponse; + } + // 3。 够,就写入 + UserUpdateInfoRequest request = new UserUpdateInfoRequest(); + request.setId(userId); + request.setMoney(round.doubleValue()); + tag = MqTags.PAY_MONEY_CANCLE.getTag(); + userService.updateUserInfo(request); // 暂时先不接受返回信息 + // 模拟异常 +// CastException.cast(SbCode.SYSTEM_ERROR); + payResponse.setCode(SbCode.SUCCESS.getCode()); + payResponse.setMsg(SbCode.SUCCESS.getMessage()); + // 4. 按道理讲,这边更改订单状态...... + return payResponse; + } catch (Exception e) { + log.error("支付业务发生异常"); + MQDto mqDto = new MQDto(); + mqDto.setUserId(userId); + mqDto.setUserMoney(userMoney); + // 发送消息 + try { + String key = RedisConstants.PAY_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(userId); + sendCancelPay(topic,tag,key, JSON.toJSONString(mqDto)); + log.warn("支付回退消息已发送"); + } catch (Exception ex) { + ex.printStackTrace(); + log.error("支付消息都崩的话..."); + } + payResponse.setCode(SbCode.SYSTEM_ERROR.getCode()); + payResponse.setMsg(SbCode.SYSTEM_ERROR.getMessage()); + return payResponse; + } + +} +``` + +### payBack + +```java +public PayResponse payBack(PayBackRequest request) { + PayResponse response = new PayResponse(); + try { + // 1. 退回金额 + // 读用户金额 + UserRequest userRequest = new UserRequest(); + userRequest.setId(request.getUserId()); + UserResponse userResponse = userService.getUserById(userRequest); + UserDto userDto = userResponse.getUserDto(); + // 计算金额 + BigDecimal add = NumberUtil.add(userDto.getMoney() + request.getTotalMoney()); + BigDecimal round = NumberUtil.round(add, 2); + // 写回 + UserUpdateInfoRequest userUpdateInfoRequest = new UserUpdateInfoRequest(); + userUpdateInfoRequest.setId(request.getUserId()); + userUpdateInfoRequest.setMoney(round.doubleValue()); + userService.updateUserInfo(userUpdateInfoRequest); + // 2. 退回座位 + busService.filterRepeatSeats(request.getSeatsIds(), request.getCoundId()); + // 3. 更改订单状态:关闭 + OrderRequest orderRequest = new OrderRequest(); + orderRequest.setUuid(request.getOrderId()); + orderRequest.setOrderStatus("3"); + orderService.updateOrderStatus(orderRequest); + response.setCode(SbCode.SUCCESS.getCode()); + response.setMsg(SbCode.SUCCESS.getMessage()); + return response; + } catch (Exception e) { +// e.printStackTrace(); + log.error("退款业务异常"); + // 这里可以发消息, 此处先省略 + response.setCode(SbCode.SYSTEM_ERROR.getCode()); + response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); + return response; + } +} +``` + +这两个业务逻辑异常都采用了消息队列,日后再写。 \ No newline at end of file diff --git "a/Interview/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" "b/Interview/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" new file mode 100644 index 00000000..e6839718 --- /dev/null +++ "b/Interview/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" @@ -0,0 +1,64 @@ +# 项目环境搭建 +> 主要搭建的技术:Java、MySQL、Redis、zookeeper、Dubbo、RocketMQ + +## 引言 +首先说明,MySQL和Redis放到了我的阿里云服务器,负载并不大,其他全部服务搭建在实验室的服务器主机上,并采用内网穿透映射端口,让大家访问,大家注意一点哈。请谅解。 + +还有,本项目是对本校的班车预约服务的架构进行了重构,采用的比较热门的技术,如README所示。 + +## 技术选型 + + +![选型介绍图](https://imgkr.cn-bj.ufileos.com/56250623-c12e-4440-abe9-bc0277c4e3be.png) + +从选型图上可以清晰的看到: +- **Web层**:sb-gateway网关,控制整个项目的请求分发与转接等。 +- **接口层**:sb-api定义各个服务的接口,比如用户、班车、订单等。 +- **服务层**:有着四大服务,分别如上图所示,用户、班车、订单和支付,并且其中均采用第三方中间件Zookeeper、Dubbo和RocketMQ等。 +- **存储层**,实际上,MySQL作为存储,而Redis做为MySQL上一层的缓存层 + +## 架构路线 + + +![架构路线图](https://imgkr.cn-bj.ufileos.com/18606af1-be51-44ce-9c06-f2abff84a294.png) + +首先,一条请求从用户移动端或者PC端发出,经过前端的Nginx的代理转发到sb-gateway的网关,网关进一步的将请求进行路由分发到对应的Controller,从而执行相应的业务逻辑,其中对应的Controller中分为两步,第一步:找缓存,若存在,直接返回,若不存在。第二步:业务逻辑方法向Dubbo请求远程调用(RPC),得到结果,另外一边业务执行相应的底层如MySQL等,将结果返回给网关,并写入缓存等。 + +## 环境端口 + + +![端口定义](https://imgkr.cn-bj.ufileos.com/9a949053-2417-468a-b7cb-0490c5c78b51.png) + + +![环境启动](https://imgkr.cn-bj.ufileos.com/53607d10-75a7-47bc-815c-adbeef4193f5.png) + + + +## Java +> 我在博客上写好了,可以参考。[http://dreamcat.ink/2019/07/08/windows-mac-he-linux-an-zhuang-java/](http://dreamcat.ink/2019/07/08/windows-mac-he-linux-an-zhuang-java/) + +## MySQL +> 我也在博客上写好了,可以参考,项目采用的MySQL8版本。[http://dreamcat.ink/2019/05/27/windows-mac-he-linux-an-zhuang-mysql/](http://dreamcat.ink/2019/05/27/windows-mac-he-linux-an-zhuang-mysql/) + +## Redis +> 我还在博客上写好了,可以参考。[http://dreamcat.ink/2019/05/28/windows-mac-he-linux-an-zhuang-redis/](http://dreamcat.ink/2019/05/28/windows-mac-he-linux-an-zhuang-redis/) + +## zookeeper +- [下载地址](https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/) +- 选择3.4.14版本下载即可,放进和刚才的myapps下。 +- 解压:tar -zxf zookeeper-3.4.14.tar.gz +- 将配置文件cp -r zoo_sample.cfg zoo.cfg +- 启动zookeeper./zkServer.sh start + +## Dubbo +- [采用的是最新版本](https://github.com/apache/dubbo-admin) +- 下载解压后修配置文件 `dubbo-admin-server/src/main/resources/application.properties` +- 配置文件修改`zookeeper`地址,dubbo控制台端口默认8080,(参考端口图) +- 可以修改为其他端口例如 server.port=9898,以免与其他服务端口冲突。(参考端口图) +- 在主目录dubbo-admin-server目录下,执行 `mvn clean package -Dmaven.test.skip=true` +- `cd dubbo-admin-server/target java -jar dubbo-admin-0.1.jar` 启动即可 +- 最新版本采用前后端分离,那么前端可以到`dubbo-admin-ui`目录下,可在config目录下找到index.js中修改端口,修改过后可以终端输入`npm install` +- 安装依赖过后,可以`npm run dev`启动 + +## RocketMQ +> 我早有记录:[http://dreamcat.ink/2020/02/21/centos7-an-zhuang-rocketmq-ji-pei-zhi/](http://dreamcat.ink/2020/02/21/centos7-an-zhuang-rocketmq-ji-pei-zhi/) \ No newline at end of file diff --git "a/Interview/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" "b/Interview/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" new file mode 100644 index 00000000..0ee0849d --- /dev/null +++ "b/Interview/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" @@ -0,0 +1,357 @@ +# 班车服务 + +## BusController + +### getCount +> 这部分缓存,优化的时候采用了Redis的list,发现我用Jmeter测试并发的时候,发现了Redis中出现了非常多的异常数据,我当时还没有找到问题,暂时先采取以下方案,以下方案在并发时候,并没有出现数据异常。 +```java +if (redisUtils.hasKey(key)) { + // 如果缓存存在 + Object obj = redisUtils.get(key); + log.warn("getCount->redis\n"); + // 返回数据 + return new ResponseUtil().setData(obj); +} + +// 写 +if (!redisUtils.hasKey(key)) { + // 如果缓存不存在,就写,注意与数据库数据一致性 + redisUtils.set(key, response, RedisConstants.COUNTS_EXPIRE.getTime()); +} +``` + +### getCountDetailById +```java +// 和上面一样 +if (redisUtils.hasKey(key)) { + Object obj = redisUtils.get(key); + log.warn("getCountDetailById->redis\n"); + return new ResponseUtil().setData(obj); +} +// 这里不判断了,上面已经判断过了 +redisUtils.set(key, response, RedisConstants.COUNT_DETAIL_EXPIRE.getTime()); +``` + +## BusServiceImpl +### getBus +> 这里基本没调用过,它是获取班车人物信息的。所以分页查询即可 + +```java +// MyBatis plus的分页查询 +IPage busIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); +busIPage = busMapper.selectPage(busIPage, null); +``` + +### getCount +> 获取场次列表 + +```java +// 分页插件,这里自定义分页查询 +IPage countIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 获取时间 +String currHours = DateUtil.getHours(); +String day = DateUtil.getDay(); +System.out.println("当前时间:"+currHours); +System.out.println("当前日期:"+day); +// 判断条件 +// 1. 找出符合当前天(比如,5.30) +// 2. 找出大于等于当前时间的场次(比如,数据库8点有一场,目前时间为7点,它就符合) +// 3. 找出状态为getBusStatus的场次,一般是还未发车的场次,(比如0,1) +queryWrapper + .eq("begin_date", day) + .ge("begin_time", currHours) + .eq("bus_status", request.getBusStatus()) + .orderByAsc("begin_time");// 时间 +countIPage = countMapper.selectCounts(countIPage, queryWrapper); +``` +**自定义分页CountMapper接口** +```java +public interface CountMapper extends BaseMapper { + /** + * + * @param page + * @param wrapper + * @return + */ + IPage selectCounts(IPage page, @Param(Constants.WRAPPER) Wrapper wrapper); + + /** + * + * @param wrapper + * @return + */ + CountDetailDto selectCountDetailById(@Param(Constants.WRAPPER) Wrapper wrapper); +} +``` +**xml** +```xml + +``` + +### getCountDetailById +> 查询场次详情信息 +```java +// 自定义查询,涉及到联表查询 +QueryWrapper wrapper = new QueryWrapper<>(); +wrapper.eq("sc.uuid", request.getCountId()); +``` +**xml** +```xml + +``` + +### repeatSeats +> 判断座位是否已重复 +```java +public boolean repeatSeats(String seats, Long coundId) { + // 查查数据库, 找到座位字段 + boolean b = false; // false:不重复,true:重复 + try { + Count count = countMapper.selectById(coundId); + // 比如,selectedSeats 是1,2 + // dbSeats:"", + // dbSeats:"1,2,3", + // dbSeats: "4,5" + // 前端传来的selectedSeats, 前端判断是否为空,要不然后端也判断一下得了 + if (seats.equals("")) { + return true; + } + if (count.getSelectedSeats().equals("")) { + return false; + } + String[] ss = seats.split(","); + String[] cs = count.getSelectedSeats().split(","); + // 这里考虑并发问题 + HashSet hashSet = new HashSet<>(Arrays.asList(cs)); + for (String s : ss) { + if (hashSet.contains(s)) return true; + } + } catch (Exception e) { + e.printStackTrace(); + log.error("selectedSeats", e); + return true; // 异常就算是重复 + } + return b; +} +``` +### addSeats +```java +if (!StrUtil.isEmpty(selectedSeats)) { + // 这里可以优化,字符串拼接,这样的方式爆内存 + // StringBuffer + newSelectedSeats = selectedSeats + "," + newSelectedSeats; +} +``` + +### filterRepeatSeats +> 回退座位 +```java +Count count = countMapper.selectById(coundId); +String[] ss = seats.split(","); +String[] cs = count.getSelectedSeats().split(","); +// 并发问题,注意 +HashSet hashSet = new HashSet<>(Arrays.asList(cs)); +for (String s : ss) { + if (hashSet.contains(s)) { + hashSet.remove(s); + } +} +if (hashSet.isEmpty()) { + count.setSelectedSeats(""); +} +// 考虑了并发 +StringBuffer sb = new StringBuffer(); +for (String s : hashSet) { + sb.append(s); + sb.append(","); +} +// 上面的方案可以用String的replace的方法替换,遍历要回退的座位,依次替换即可 +count.setSelectedSeats(sb.toString()); +countMapper.updateById(count); +``` +看一下String的replace的源码(1.8),感觉遍历还挺多,但的确不用自己去写了 +```java +public String replace(char oldChar, char newChar) { + if (oldChar != newChar) { + int len = value.length; + int i = -1; + char[] val = value; /* avoid getfield opcode */ + + while (++i < len) { + if (val[i] == oldChar) { + break; + } + } + if (i < len) { + char buf[] = new char[len]; + for (int j = 0; j < i; j++) { + buf[j] = val[j]; + } + while (i < len) { + char c = val[i]; + buf[i] = (c == oldChar) ? newChar : c; + i++; + } + return new String(buf, true); + } + } + return this; +} +``` + +## BusSchedule定时器 +> 这里可以使用redis的延时队列或者RocketMQ的消息队列 + +### schedulChangeBusStatus + +```java +/** + * 每天上午7点到晚上21点,每隔30分钟执行一次 + */ +@Scheduled(cron = "0 0/30 7-21 * * ?") +private void schedulChangeBusStatus() { + log.warn("schedulChangeBusStatus执行"); + busService.schedulChangeBusStatus(); +} +``` +看一下业务逻辑 +```java +public void schedulChangeBusStatus() { + // 获取时间 + String currTime = DateUtil.getHours(); + // 获取日期 + String day = DateUtil.getDay(); + log.warn("schedulChangeBusStatus->目前时间:" + currTime); + log.warn("schedulChangeBusStatus->目前时间:" + day); + System.out.println("目前时间:"+ currTime); + System.out.println("目前时间:"+ day); + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 先取出beingtime和now相等的表或者end_time和now相等到表 + // 1. 取出当天 + // 2. 取出开始时间或者结束时间符合当前时间 + queryWrapper + .eq("begin_date", day) // 取出当天 + .and(o -> o.eq("begin_time", currTime) // 当前时间 + .or() + .eq("end_time", currTime)); + List counts = countMapper.selectList(queryWrapper); + log.warn("schedulChangeBusStatus->查询到的:" + counts.toString()); + // 开始作妖 + for (Count count : counts) { + String busStatus = count.getBusStatus(); + String beginTime = count.getBeginTime(); + String endTime = count.getEndTime(); + if (currTime.equals(beginTime)) { + if (busStatus.equals("0")) { // 沙河空闲 + count.setBusStatus("2"); // 沙河->清水河 + } + if (busStatus.equals("1")) { // 清水河空闲 + count.setBusStatus("3"); // 清水河->沙河 + } + count.setSelectedSeats(""); // 清空座位 + } + if (currTime.equals(endTime)) { + if (busStatus.equals("2")) { // 沙河->清水河 + count.setBusStatus("1"); // 清水河空闲 + } + if (busStatus.equals("3")) { // 清水河->沙河 + count.setBusStatus("0"); // 沙河空闲 + } + } + System.out.println("修改的:" + count); + log.warn("schedulChangeBusStatus->修改的:" + count); + // 写入数据库 + countMapper.updateById(count); + } + // 删缓存,这里非常重要...不删后果很严重 + String key1 = RedisConstants.COUNTS_EXPIRE + "0"; + String key2 = RedisConstants.COUNTS_EXPIRE + "1"; + if (redisUtils.hasKey(key1)) { + redisUtils.del(key1); + } + if (redisUtils.hasKey(key2)) { + redisUtils.del(key2); + } +} +``` +> 这里每隔30分钟扫库进行一次io,这里可以有优化... 假如我使用Redis的延迟队列 +1. 在用定时器添加场次的时候,可以将这些场次存入Redis的zset的队列中,元素为场次ID,元素对应的score是出发时间,这样就有17个(定时器每天凌晨添加17个) +- 还是用定时器轮询,采用每隔半小时轮询一次,我们取出队列中当前时间大于队列中权重的时间的场次ID,开始进行业务逻辑判断(更改场次状态) +- 更改过后,如果是发车时间,则删除队列中的场次id和score,重新添加队列中场次的id和结束时间score,或者直接修改score +- 如果是结束时间,则删除队列中的场次id和score + +### addCounts +> 这个项目,没有后台,因此场次需要定时器添加即可 +```java +/** + * 每天凌晨0点2分执行 + */ +@Scheduled(cron = "0 2 0 * * ? ") +private void addCounts(){ + log.warn("addCounts执行"); + busService.addCounts(); +} +``` +具体的业务逻辑: +```java +public void addCounts() { + // 获取日期 + String day = DateUtil.getDay(); + // 获取前17个场次 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.last("limit 17"); + List counts = countMapper.selectList(wrapper); + // 开始修改 这里可以用java8 的特性, 还不是很熟悉,后期优化一下 + for (Count count : counts) { + // 更改日期 + count.setBeginDate(day); + // 更改uuid + count.setUuid(UUIDUtils.flakesUUID()); + // 清空座位 + count.setSelectedSeats(""); + // 将走位状态清零 + count.setSeatStatus("0"); + // 插入 + countMapper.insert(count); + } + + // 删缓存,不删后果依然很严重 + String key1 = RedisConstants.COUNTS_EXPIRE + "0"; + String key2 = RedisConstants.COUNTS_EXPIRE + "1"; + if (redisUtils.hasKey(key1)) { + redisUtils.del(key1); + } + if (redisUtils.hasKey(key2)) { + redisUtils.del(key2); + } +} + +``` diff --git "a/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" new file mode 100644 index 00000000..be1fc864 --- /dev/null +++ "b/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" @@ -0,0 +1,74 @@ +# 用户服务 + + +## UserController + +### checkUsername +> 这个我就不需要再说了,这个没啥可说的 + + +### register +> 这个注意请求体的参数即可,也没什么可说的 + + +### getUserById +> 注意缓存是否存在,存在直接返回即可,没有就调用业务`getUserById`获取结果 + +### updateUserInfo +> 注意请求体的参数即可,并且若是更新数据,那么自然要删除UserInfo的缓存,保持数据一致,写不写缓存这一点都行,下次请求getUserById会写缓存。 + +### logout +> 注意redis删除缓存即可 + + +## UserServiceImpl + +### checkUsername +```java +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 查询user_name列与getUsername相匹配的数据 +queryWrapper.eq("user_name", request.getUsername()); +User user = userMapper.selectOne(queryWrapper); +``` + +### regsiter +```java +// 密码采用了md5加密 +String md5Password = MD5Util.encrypt(user.getUserPwd()); +user.setUserPwd(md5Password); +``` + +### login +```java +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 根据user_name查询用户是否存在 +queryWrapper.eq("user_name", request.getUsername()); +User user = userMapper.selectOne(queryWrapper); +if (user != null && user.getUuid() > 0) { + // 用户不为空,用户id > 0 + String md5Password = MD5Util.encrypt(request.getPassword()); + if (user.getUserPwd().equals(md5Password)) { + // 数据库密码和所加密的md5密码一致 + res.setUserId(user.getUuid()); + res.setCode(SbCode.SUCCESS.getCode()); + res.setMsg(SbCode.SUCCESS.getMessage()); + } +} +``` + +### getUserById +```java +//根据id查询用户 +User user = userMapper.selectById(request.getId()); +UserDto userDto = userConverter.User2Res(user); +``` + +### updateUserInfo +```java +// 这里一采用了mapstruct映射,有兴趣的可以去网上找资料学习学习 +User user = userConverter.res2User(request); +``` + +## 总结 + +用户服务,没有什么比较复杂的业务,复杂的业务在订单服务、场次服务、支付服务、 diff --git "a/Interview/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" "b/Interview/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" new file mode 100644 index 00000000..857d0fbe --- /dev/null +++ "b/Interview/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" @@ -0,0 +1,282 @@ +# 订单服务 +## OrderController + +### getNoTakeOrdersById +> 未乘坐订单 + +```java +if (redisUtils.hasKey(key)) { + // redis是否有该缓存 + Object obj = redisUtils.get(key); + NoTakeBusResponse response = (NoTakeBusResponse) obj; + for (NoTakeDto noTakeDto : response.getNoTakeDtos()) { + // 如果场次发车时间-当前时间是大0的,那么说明已经发车了 + String beginTime = noTakeDto.getBeginTime(); + if (beginTime.compareTo(DateUtil.getHours()) <= -1) { + // 删掉当前缓存 + redisUtils.del(key); + // 重新获取最新的数据 + response = orderService.getNoTakeOrdersById(request); + // 写缓存 + redisUtils.set(key, response, RedisConstants.NO_TAKE_OREDERS_EXPIRE.getTime()); + return new ResponseUtil().setData(response); + } + } + log.warn("getNoTakeOrdersById->redis\n"); + return new ResponseUtil().setData(obj); +} +``` + +### getNoPayOrdersById +> 获取未支付订单接口 +```java +// 从redis中是否有缓存 +if (redisUtils.hasKey(key)) { + // 有就获取 + Object obj = redisUtils.get(key); + log.warn("getNoPayOrdersById->redis\n"); + return new ResponseUtil().setData(obj); +} +``` +### getEvaluateOrdersById +> 根据评价状态获取用户订单接口 +略 + +### addOrder +> 添加订单接口 + +```java +String countKey = RedisConstants.COUNT_DETAIL_EXPIRE.getKey() + request.getCountId(); +// 座位缓存失效 +if (redisUtils.hasKey(countKey)) { + redisUtils.del(countKey); +} +String noPayKey = RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey() + userId; +// 未支付列表缓存失效 +if (redisUtils.hasKey(noPayKey)) { + redisUtils.del(noPayKey); +} +``` +### selectOrderById +> 根据订单id获取详情订单 +略 + +### updateOrderStatus +> 更改订单状态 +```java +// 删掉订单详情缓存 +if (redisUtils.hasKey(selectOrderKey)) { + redisUtils.del(selectOrderKey); +} +// 删除未乘坐缓存 +if (redisUtils.hasKey(noTakeKey)) { + redisUtils.del(noTakeKey); +} +// 删除未支付缓存 +if (redisUtils.hasKey(noPayKey)) { + redisUtils.del(noPayKey); +} +// 删除评价缓存 +if (redisUtils.hasKey(evaluateKey)) { + redisUtils.del(evaluateKey); +} +``` + +## OrderServiceImpl + +### getNoTakeOrdersById +```java +IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 获取系统年月日 +// 比如5.30 +String day = DateUtil.getDay(); +// 比如20:00 +String hours = DateUtil.getHours(); +System.out.println("当前日期:" + day); +System.out.println("当前时间:" + hours); +queryWrapper + .eq("user_id", request.getUserId()) // 用户id + .eq("order_status", "1")// 1:已经支付 + .ge("sc.begin_date", day) // 比如订单日期大于等于今天 + .ge("sc.begin_time", hours) // 订单时间大于等于当前时间 + .orderByAsc("sc.begin_time") // 排序 + .orderByDesc("so.order_time"); // 排序 +``` + +### getEvaluateOrdersById +```java +IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 获取系统年月日 +String day = DateUtil.getDay(); +String hours = DateUtil.getHours(); +System.out.println("当前日期:" + day); +System.out.println("当前时间:" + hours); +queryWrapper + .eq("user_id", request.getUserId()) // 用户di + .eq("order_status", "1") // 状态为1 + // 两种情况: + // 1. 符合当天日期,并且订单场次发车时间小于当前日期 + // 2. 订单场次的发车日期小于当前日期 + .and(o -> o.eq("sc.begin_date", day) + .lt("sc.begin_time", hours) + .or().lt("sc.begin_date", day)) + .eq("evaluate_status", request.getEvaluateStatus()) // 评价状态 + .orderByDesc("sc.begin_time") // 排序 + .orderByDesc("so.order_time"); // 排序 +``` + +### getNoPayOrdersById +```java +IPage noPayDtoIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); +QueryWrapper queryWrapper = new QueryWrapper<>(); +// 获取系统年月日 +String day = DateUtil.getDay(); +String hours = DateUtil.getHours(); +System.out.println("当前日期:" + day); +System.out.println("当前时间:" + hours); +queryWrapper + .eq("so.user_id", request.getUserId()) // 用户id + .eq("so.order_status", "0") // 未支付状态 + .ge("sc.begin_date", day) // 比如,订单场次日期大于当前日期 + .ge("sc.begin_time", hours)// 订单场次时间大于当前日期 + .orderByDesc("sc.begin_time") // 排序 + .orderByDesc("so.order_time"); // 未支付 +``` + +### addOrder +```java +public AddOrderResponse addOrder(AddOrderRequest request) { + // 判断座位,如果重复,直接退出,否则更新场次的座位信息 + AddOrderResponse response = new AddOrderResponse(); + // 全局orderId + Long orderId = UUIDUtils.flakesUUID(); + // 1。 判断座位,如果重复,直接退出,否则下一步 + // 2。 更新座位,如果没有异常,这是写操作 + // 3。 计算总金额,如果没有异常 + // 4。 添加订单,如果异常,这是写操作 + try { + // 1。 判断座位,如果重复,直接退出,否则下一步 + tag = MqTags.ORDER_SEATS_CANCEL.getTag(); + boolean repeatSeats = busService.repeatSeats(request.getSeatsIds(), request.getCountId()); + if (repeatSeats) { + // b:true 说明重复 + response.setCode(SbCode.SELECTED_SEATS.getCode()); + response.setMsg(SbCode.SELECTED_SEATS.getMessage()); + return response; + } +// CastException.cast(SbCode.SYSTEM_ERROR); + // 2。 更新座位,如果没有异常,这是写操作 + // 用tags来过滤消息 + tag = MqTags.ORDER_ADD_SEATS_CANCLE.getTag(); + boolean addSeats = busService.addSeats(request.getSeatsIds(), request.getCountId()); + if (!addSeats) { + response.setCode(SbCode.DB_EXCEPTION.getCode()); + response.setMsg(SbCode.DB_EXCEPTION.getMessage()); + return response; + } + // 模拟系统异常 +// CastException.cast(SbCode.SYSTEM_ERROR); + // 3。 计算总金额,如果没有异常 + tag = MqTags.ORDER_CALC_MONEY_CANCLE.getTag(); + String seatIds = request.getSeatsIds(); + Integer seatNumber = seatIds.split(",").length; + Double countPrice = request.getCountPrice(); + Double totalPrice = getTotalPrice(seatNumber, countPrice); + +// CastException.cast(SbCode.SYSTEM_ERROR); + // 4。 添加订单,如果异常,这是写操作 + Order order = orderConvertver.res2Order(request); + order.setOrderPrice(totalPrice); + order.setEvaluateStatus("0"); // 未评价 + order.setOrderStatus("0"); // 未支付 + order.setUuid(orderId); // 唯一id + tag = MqTags.ORDER_ADD_CANCLE.getTag(); + int insert = orderMapper.insert(order);// 插入 不判断了 +// CastException.cast(SbCode.SYSTEM_ERROR); + // 这里就不读了,耗时 +// QueryWrapper wrapper = new QueryWrapper<>(); +// wrapper.eq("so.uuid", order.getUuid()); +// OrderDto orderDto = orderMapper.selectOrderById(wrapper); + response.setCode(SbCode.SUCCESS.getCode()); + response.setMsg(SbCode.SUCCESS.getMessage()); + response.setOrderId(orderId); +// response.setOrderDto(orderDto); + // 这里放redis 未支付缓存,时间前端给定 + redisUtils.set(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + orderId, orderId, request.getExpireTime()); + return response; + } catch (Exception e) { + // 以上操作如果程序都不发生异常的话, 是不会执行这里的代码的 + // 也就是说不会发送回退消息的。 + // 目的是在高并发的情况下,程序内部发生异常,依然高可用 +// e.printStackTrace(); + log.error("订单业务发生异常"); + // 发消息,将座位退回,将订单退回 + MQDto mqDto = new MQDto(); + mqDto.setOrderId(orderId); + mqDto.setCountId(request.getCountId()); + mqDto.setSeatsIds(request.getSeatsIds()); + try { + String key = RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(orderId); + sendCancelOrder(topic,tag, key, JSON.toJSONString(mqDto)); + log.warn("订单回退消息发送成功..." + mqDto); + } catch (Exception ex) { + ex.printStackTrace(); + } + response.setCode(SbCode.SYSTEM_ERROR.getCode()); + response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); + return response; + } +} +``` + +### selectOrderById +> 省略 + +### updateOrderStatus +```java +public OrderResponse updateOrderStatus(OrderRequest request) { + OrderResponse response = new OrderResponse(); + try { + // 获取orderDto + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("so.uuid", request.getUuid()); + OrderDto orderDto = orderMapper.selectOrderById(wrapper); + // 1, 检查状态是否为2 + if (request.getOrderStatus().equals("2")) { + // 说明关闭订单,回退座位 + busService.filterRepeatSeats(orderDto.getSeatsIds(), orderDto.getCountId()); + redisUtils.del(RedisConstants.COUNT_DETAIL_EXPIRE.getKey() + + orderDto.getCountId()); + // 清除场次详情的缓存 + } + if (request.getOrderStatus().equals("1")) { + // 说明已经支付,删掉5分钟的订单缓存 + redisUtils.del(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + request.getUuid()); + } + Order order = orderConvertver.res2Order(request); + // 更新状态 + orderMapper.updateById(order); + // 暂时就不获取了 + response.setCode(SbCode.SUCCESS.getCode()); + response.setMsg(SbCode.SUCCESS.getMessage()); + redisUtils.del(RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey()+order.getUserId()); + redisUtils.del(RedisConstants.SELECT_ORDER_EXPIRE.getKey() + request.getUuid()); + } catch (Exception e) { + log.error("updateOrderStatus", e); + response.setCode(SbCode.DB_EXCEPTION.getCode()); + response.setMsg(SbCode.DB_EXCEPTION.getMessage()); + return response; + } + return response; +} +``` + +### deleteOrderById +> 省略 + +### sendCancelOrder +> 发送订单回退消息 + +后边会单独介绍消息队列 \ No newline at end of file diff --git a/Interview/codes/.DS_Store b/Interview/codes/.DS_Store index a73c35f9d36e54d73c579eab7f1b64cc583b9bfc..dd7723cb4185b63dc9c41c6edc53c8dcc470dce4 100644 GIT binary patch delta 34 pcmZoEXeroWDZt1%`J8A5KPP9rfJAk*iIK66g0Y3k=8Xcu>HxRq3EBVv delta 16 XcmZoEXeroWDKPo0=H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 @@ -275,7 +275,7 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1 #### 对象创建 -![参考-JavaGuide-对象创建的过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Java%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%BF%87%E7%A8%8B.png) +![对象创建的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) 1. 类加载检查,虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 @@ -585,15 +585,6 @@ G1 收集器的运作大致分为以下几个步骤: -Xms128m -Xmx4096m -Xss1024K -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC ``` -- -Xms:初始大小内存,默认为物理内存1/64,等价于-XX:InitialHeapSize -- -Xmx:最大分配内存,默认物理内存1/4,等价于-XX:MaxHeapSize -- -Xss:设置单个线程栈的大小,默认542K~1024K ,等价于-XX:ThreadStackSize -- -Xmn:设置年轻代的大小 -- -XX:MetaspaceSize:设置元空间大小 -- -XX:+PrintGCDetails:输出详细GC收集日志信息,如[名称:GC前内存占用->GC后内存占用(该区内存总大小)] -- -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例,默认-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1 -- -XX:NewRatio:设置年轻代与老年代在堆结构的占比,如:默认-XX:NewRatio=2 新生代在1,老年代2,年轻代占整个堆的1/3,NewRatio值几句诗设置老年代的占比,剩下的1给新生代 -- -XX:MaxTenuringThreshold:设置垃圾的最大年龄,默认-XX:MaxTenuringThreshold=15 -- -XX:+UseSerialGC:串行垃圾回收器 -- -XX:+UseParallelGC:并行垃圾回收器 + +![解释](http://media.dreamcat.ink/uPic/iShot2020-05-26上午09.58.16.png) \ No newline at end of file diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\237\272\347\241\200.md" "b/Interview/crazy/Java\345\237\272\347\241\200.md" similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\237\272\347\241\200.md" rename to "Interview/crazy/Java\345\237\272\347\241\200.md" diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\244\232\347\272\277\347\250\213.md" "b/Interview/crazy/Java\345\244\232\347\272\277\347\250\213.md" similarity index 99% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\244\232\347\272\277\347\250\213.md" rename to "Interview/crazy/Java\345\244\232\347\272\277\347\250\213.md" index 60b68d47..828efb83 100644 --- "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\345\244\232\347\272\277\347\250\213.md" +++ "b/Interview/crazy/Java\345\244\232\347\272\277\347\250\213.md" @@ -855,7 +855,7 @@ public class CyclicBarrierDemo { ``` -总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,cout 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 +总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,count 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 #### Semaphore @@ -1300,7 +1300,7 @@ synchronized void setB() throws Exception { #### 分段锁 1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** -2. ** 以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** +2. **以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** 3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** 4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** @@ -1324,4 +1324,5 @@ synchronized void setB() throws Exception { - Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 - ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 -- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 \ No newline at end of file +- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 + diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\351\233\206\345\220\210.md" "b/Interview/crazy/Java\351\233\206\345\220\210.md" similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Java\351\233\206\345\220\210.md" rename to "Interview/crazy/Java\351\233\206\345\220\210.md" diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223MySQL.md" b/Interview/crazy/MySQL.md similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223MySQL.md" rename to Interview/crazy/MySQL.md diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Mybatis.md" b/Interview/crazy/Mybatis.md similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Mybatis.md" rename to Interview/crazy/Mybatis.md diff --git a/Interview/crazy/README.md b/Interview/crazy/README.md new file mode 100644 index 00000000..3d9d8964 --- /dev/null +++ b/Interview/crazy/README.md @@ -0,0 +1,11 @@ +- **个人吐血系列-总结Java基础**: [本地阅读](Java基础.md)->[博客阅读](http://dreamcat.ink/2020/03/27/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-chu/)->[掘金阅读](https://juejin.im/post/5e7e0615f265da795568754b) +- **个人吐血系列-总结Java集合**: [本地阅读](Java集合.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-he/)->[掘金阅读](https://juejin.im/post/5e801e29e51d45470b4fce1c) +- **个人吐血系列-总结Java多线程**: [本地阅读](Java多线程)->[博客阅读](http://dreamcat.ink/2020/03/25/ge-ren-tu-xie-xi-lie-zong-jie-java-duo-xian-cheng/)->[掘金阅读-1](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) [掘金阅读-2](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) +- **个人吐血系列-总结JVM**: [本地阅读](JVM.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-jvm/)->[掘金阅读](https://juejin.im/post/5e8344486fb9a03c786ef885) +- **个人吐血系列-总结Spring**: [本地阅读](Spring.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-spring/)->[掘金阅读](https://juejin.im/post/5e846a4a6fb9a03c42378bc1) +- **个人吐血系列-总结Mybatis**: [本地阅读](Mybatis.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-mybatis/)->[掘金阅读](https://juejin.im/post/5e889b196fb9a03c875c8f50) +- **个人吐血系列-总结MySQL**: [本地阅读](MySQL.md)->[博客阅读](http://dreamcat.ink/2020/03/30/ge-ren-tu-xie-xi-lie-zong-jie-mysql/)->[掘金阅读](https://juejin.im/post/5e94116551882573b86f970f) +- **个人吐血系列-总结Redis**: [本地阅读](Redis.md)->[博客阅读](http://dreamcat.ink/2020/03/31/ge-ren-tu-xie-xi-lie-zong-jie-redis/)->[掘金阅读](https://juejin.im/post/5e9d6a9ff265da47e34c0e8a) +- **个人吐血系列-总结计算机网络**: [本地阅读](计算机网络.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)->[掘金阅读](https://juejin.im/post/5ea383c251882573716ab496) +- **个人吐血系列-Dubbo**: [本地阅读](Dubbo.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)->[掘金阅读](https://juejin.im/post/5eb11127f265da7bb46bce26) +- **个人吐血系列-RocketMQ**: [本地阅读](RocketMQ)->[博客阅读](http://dreamcat.ink/2020/04/01/ge-ren-tu-xie-xi-lie-zong-jie-rocketmq/)-> [掘金阅读](https://juejin.im/post/5ecf1f716fb9a047f338b972) \ No newline at end of file diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Redis.md" b/Interview/crazy/Redis.md similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Redis.md" rename to Interview/crazy/Redis.md diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223RocketMQ.md" b/Interview/crazy/RocketMQ.md similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223RocketMQ.md" rename to Interview/crazy/RocketMQ.md diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Spring.md" b/Interview/crazy/Spring.md similarity index 96% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Spring.md" rename to Interview/crazy/Spring.md index 5e674e5a..c027f5ee 100644 --- "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223Spring.md" +++ b/Interview/crazy/Spring.md @@ -77,10 +77,13 @@ public void refresh() throws BeansException, IllegalStateException { // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) + // Tell the subclass to refresh the internal bean factory. + //获得容器ApplicationContext的子类BeanFactory。步骤如下: + //1.如果已经有了BeanFactory就销毁它里面的单例Bean并关闭这个BeanFactory。 + //2.创建一个新的BeanFactory。 + //3.对这个BeanFactory进行定制(customize),如allowBeanDefinitionOverriding等参数 + //4.转载BeanDefinitions(读取配置文件,将xml转换成对应得BeanDefinition) + //5.检查是否同时启动了两个BeanFactory。 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean @@ -88,16 +91,14 @@ public void refresh() throws BeansException, IllegalStateException { prepareBeanFactory(beanFactory); try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 + // 设置beanFactory的后置处理器 // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 postProcessBeanFactory(beanFactory); + // 调用beanFactory的后置处理器 // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 invokeBeanFactoryPostProcessors(beanFactory); - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 + // 注册 BeanPostProcessor 的实现类(bean的后置处理器) // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 registerBeanPostProcessors(beanFactory); @@ -108,7 +109,7 @@ public void refresh() throws BeansException, IllegalStateException { // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 initApplicationEventMulticaster(); - // 从方法名就可以知道,典型的模板方法(钩子方法), + // 模板方法(钩子方法), // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) onRefresh(); diff --git "a/Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/Interview/crazy/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" similarity index 100% rename from "Interview/crazy/\344\270\252\344\272\272\345\220\220\350\241\200\347\263\273\345\210\227-\346\200\273\347\273\223\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" rename to "Interview/crazy/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" diff --git a/Interview/mind/README.md b/Interview/mind/README.md new file mode 100644 index 00000000..49880ea5 --- /dev/null +++ b/Interview/mind/README.md @@ -0,0 +1,13 @@ +- [总体架构](https://www.processon.com/view/link/5e170217e4b0bcfb733ce553) **这边就不放图了,放图的字体小,放大可能模糊。该图还在持续总结中...** +- [Java常见基础问题](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) **常见的基础问题,这是必须要掌握。** +- [Java常见集合问题]() **还没总结,后续总结...** +- [Java常见多线程问题](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) **常见的多线程问题,也是必须掌握...** +- [JVM常见问题](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) **常见的JVM要掌握的点...** +- [Spring常见问题](https://www.processon.com/view/link/5e846de9e4b07b16dcdb63f0) **常见的Spring面试的问题...** +- [Mybatis常见问题](https://www.processon.com/view/link/5e4e3b7ae4b0369b916b2e71) **常见的Mybatis面试的问题...** +- [MySQL常见问题](https://www.processon.com/view/link/5e9b0cb15653bb1a686e17ea) **常见的MySQL面试的问题...** +- [Redis常见问题](https://www.processon.com/view/link/5ea2da5907912948b0d89a0a) **常见的Redis面试的问题...** +- [计算机网络常见问题](https://www.processon.com/view/link/5eb8c93be401fd16f42b5f77) **常见的计算机网络面试的问题...** +- [Dubbo常见问题](https://www.processon.com/view/link/5eb8c9715653bb6f2aff7c11) **常见的Dubbo的问题...** +- [RocketMQ常见问题](https://www.processon.com/view/link/5ecf208f7d9c08156c6c37e3) **常见的RocketMQ的问题...** +- [微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 \ No newline at end of file diff --git a/Interview/mode/README.md b/Interview/mode/README.md new file mode 100644 index 00000000..633732eb --- /dev/null +++ b/Interview/mode/README.md @@ -0,0 +1,6 @@ +- [单例](mode/单例模式.md) +- [工厂](mode/工厂模式.md) +- [代理](mode/代理模式.md) +- [模板方法](mode/模板方法模式.md) +- [观察者](mode/观察者模式.md) +- [装饰器](mode/装饰器模式.md) \ No newline at end of file diff --git "a/Interview/mode/\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/Interview/mode/\344\273\243\347\220\206\346\250\241\345\274\217.md" new file mode 100644 index 00000000..afaa720e --- /dev/null +++ "b/Interview/mode/\344\273\243\347\220\206\346\250\241\345\274\217.md" @@ -0,0 +1,86 @@ +## 代理模式 + +> 所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 + 一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。 + +### 静态代理 + +> 定义主题 + +```java +interface Subject { + void visit(); +} +``` + +> 实现subject的两个类 + +```java +class RealSubject implements Subject { + private String name = "dreamcat"; + @Override + public void visit() { + System.out.println(name); + } +} +``` +> 代理类 +```java +class ProxySubject implements Subject { + + private Subject subject; + + public ProxySubject(Subject subject) { + this.subject = subject; + } + + @Override + public void visit() { + subject.visit(); + } +} +``` +缺点: +> 代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。 + 每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。 + 代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。 + +### 动态代理 +> 动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 + +> 动态代理 + +```java +class DynamicProxy implements InvocationHandler { + + private Object object; + + public DynamicProxy(Object object) { + this.object = object; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result = method.invoke(object, args); + return result; + } +} +``` + +### 测试 +```java +public class ProxyMode { + public static void main(String[] args) { + // 静态代理 + ProxySubject proxySubject = new ProxySubject(new RealSubject()); + proxySubject.visit(); + + // 动态代理 + RealSubject realSubject = new RealSubject(); + DynamicProxy dynamicProxy = new DynamicProxy(realSubject); + ClassLoader classLoader = realSubject.getClass().getClassLoader(); + Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy); + subject.visit(); + } +} +``` diff --git "a/Interview/mode/\345\215\225\344\276\213\346\250\241\345\274\217.md" "b/Interview/mode/\345\215\225\344\276\213\346\250\241\345\274\217.md" new file mode 100644 index 00000000..d74b210b --- /dev/null +++ "b/Interview/mode/\345\215\225\344\276\213\346\250\241\345\274\217.md" @@ -0,0 +1,87 @@ +## 单例模式 +> 单例就不多说了 + +### 饿汉 + +```java +public class Singleton { + private static Singleton instance = new Singleton(); + private Singleton(){} + + public static Singleton getInstance() { + return instance; + } +} +``` +### 饿汉变种 + +```java +class Singleton { + private static Singleton instance = null; + static { + instance = new Singleton(); + } + private Singleton() {} + + public static Singleton getInstance() { + return instance; + } +} +``` + +### 懒汉(线程不安全) + +```java +class Singleton { + private static Singleton instance = null; + private Singleton(){} + + public static Singleton getInstance() { + if (instance == null){ + instance = new Singleton(); + } + return instance; + + } +} +``` + +### 懒汉(线程安全,但消耗资源较为严重) + +```java +class Singleton { + private static Singleton instance = null; + + private Singleton() { + } + + public static synchronized Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } +} +``` + +### 懒汉(线程安全,双重校验) + +```java +class Singleton { + private static volatile Singleton instance = null; + + private Singleton() { + } + + public static Singleton getInstance() { + if (instance == null) { + synchronized (Singleton.class) { + if (instance == null) { + instance = new Singleton(); + } + } + } + return instance; + } +} +``` \ No newline at end of file diff --git "a/Interview/mode/\345\267\245\345\216\202\346\250\241\345\274\217.md" "b/Interview/mode/\345\267\245\345\216\202\346\250\241\345\274\217.md" new file mode 100644 index 00000000..3ef26dba --- /dev/null +++ "b/Interview/mode/\345\267\245\345\216\202\346\250\241\345\274\217.md" @@ -0,0 +1,81 @@ +## 工厂模式 +> 大概意思就不要说了,直接举个例子,看例子讲解就知道是什么意思了。 + +### 举例子 + +> 定义一个面条抽象类 + +```java +abstract class INoodles { + /** + * 描述每种面条长什么样的... + */ + public abstract void desc(); +} +``` + +> 定义一份兰州拉面(具体产品) + +```java +class LzNoodles extends INoodles { + + @Override + public void desc() { + System.out.println("兰州拉面,成都的好贵 家里的才5-6块钱一碗"); + } +} +``` + +> 定义一份泡面(程序员挺喜欢的) + +```java +class PaoNoodles extends INoodles { + + @Override + public void desc() { + System.out.println("泡面可还行..."); + } +} +``` + +> 不得不说家乡的杂酱面了,好吃得不得了 + +```java +class ZaNoodles extends INoodles { + + @Override + public void desc() { + System.out.println("杂酱面,嗯? 真香..."); + } +} +``` + +> 重头戏,开面条馆了。(工厂) + +```java +class SimpleNoodlesFactory { + public static final int TYPE_LZ = 1; // 兰州拉面 + public static final int TYPE_PAO = 2; // 泡面撒 + public static final int TYPE_ZA = 3; // 杂酱面 + // 提供静态方法 + public static INoodles createNoodles(int type) { + switch (type) { + case TYPE_LZ:return new LzNoodles(); + case TYPE_PAO:return new PaoNoodles(); + case TYPE_ZA:return new ZaNoodles(); + default:return new ZaNoodles(); + } + } +} +``` + +> 测试 + +```java +public class FactoryMode { + public static void main(String[] args) { + INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_ZA); + noodles.desc(); + } +} +``` \ No newline at end of file diff --git "a/Interview/mode/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/Interview/mode/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" new file mode 100644 index 00000000..cb19467b --- /dev/null +++ "b/Interview/mode/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" @@ -0,0 +1,63 @@ +## 模板方法模式 + +> 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 + +例如: +> 去银行办业务,银行给我们提供了一个模板就是:先取号,排对,办理业务(核心部分我们子类完成),给客服人员评分,完毕。 + 这里办理业务是属于子类来完成的,其他的取号,排队,评分则是一个模板。 + +再例如: +> 去餐厅吃饭,餐厅给提供的一套模板就是:先点餐,等待,吃饭(核心部分我们子类完成),买单 + 这里吃饭是属于子类来完成的,其他的点餐,买单则是餐厅提供给我们客户的一个模板。 + +所以: +> 实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分容易变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。 + + +### 举例子 +> 银行办理业务抽象类 + +```java +abstract class BankTemplateMethod { + // 1. 取号排队 + public void takeNumber() { + System.out.println("取号排队..."); + } + + // 2. 每个子类不同的业务实现,各由子类来实现 + abstract void transact(); + + // 3. 评价 + public void evaluate() { + System.out.println("反馈评价..."); + } + + public void process() { + takeNumber(); + transact(); + evaluate(); + } +} +``` + +> 具体的业务,比如取钱 +```java +class DrawMoney extends BankTemplateMethod { + + @Override + void transact() { + System.out.println("我要取款..."); + } +} +``` + +### 测试 + +```java +public class TemplateMode { + public static void main(String[] args) { + BankTemplateMethod drawMoney = new DrawMoney(); + drawMoney.process(); + } +} +``` \ No newline at end of file diff --git "a/Interview/mode/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" "b/Interview/mode/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" new file mode 100644 index 00000000..1513ee9d --- /dev/null +++ "b/Interview/mode/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" @@ -0,0 +1,136 @@ +## 装饰器模式 + +> 按照单一职责原则,某一个对象只专注于干一件事,而如果要扩展其职能的话,不如想办法分离出一个类来“包装”这个对象,而这个扩展出的类则专注于实现扩展功能。 + 装饰器模式就可以将新功能动态地附加于现有对象而不改变现有对象的功能。 + +> 代理模式专注于对被代理对象的访问; + 装饰器模式专注于对被装饰对象附加额外功能。 + +### 举例子 +> 假设我去买咖啡,首先服务员给我冲了一杯原味咖啡,我希望服务员给我加些牛奶和白糖混合入原味咖啡中。使用装饰器模式就可以解决这个问题。 + +> 咖啡接口 + +```java +interface Coffee { + // 获取价格 + double getCost(); + + // 获取配料 + String getIngredients(); +} +``` + +> 原味咖啡 + +```java +class SimpleCoffee implements Coffee { + + @Override + public double getCost() { + return 1; + } + + @Override + public String getIngredients() { + return "Coffee"; + } +} +``` +> 咖啡对象的装饰器类 +```java +abstract class CoffeeDecorator implements Coffee { + protected final Coffee decoratedCoffee; + + /** + * 在构造方法中,初始化咖啡对象的引用 + */ + + protected CoffeeDecorator(Coffee coffee) { + this.decoratedCoffee = coffee; + } + + /** + * 装饰器父类中直接转发"请求"至引用对象 + */ + public double getCost() { + return decoratedCoffee.getCost(); + } + + public String getIngredients() { + return decoratedCoffee.getIngredients(); + } +} +``` + +注意: +> 具体的装饰器类,负责往咖啡中“添加”牛奶,注意看getCost()方法和getIngredients()方法,可以在转发请求之前或者之后,增加功能。 + 如果是代理模式,这里的结构就有所不同,通常代理模式根据运行时的条件来判断是否转发请求。 + + +> 混合牛奶 + +```java +class WithMilk extends CoffeeDecorator { + + public WithMilk(Coffee coffee) { + super(coffee); + } + + @Override + public double getCost() { + double additionalCost = 0.5; + return super.getCost() + additionalCost; + } + + @Override + public String getIngredients() { + String additionalIngredient = "milk"; + return super.getIngredients() + ", " + additionalIngredient; + } +} +``` + +```java +class WithSugar extends CoffeeDecorator { + + public WithSugar(Coffee coffee) { + super(coffee); + } + + @Override + public double getCost() { + return super.getCost() + 1; + } + + @Override + public String getIngredients() { + return super.getIngredients() + ", Sugar"; + } +} +``` + +### 测试 +```java +public class DecoratorMode { + static void print(Coffee c) { + System.out.println("花费了: " + c.getCost()); + System.out.println("配料: " + c.getIngredients()); + System.out.println("============"); + } + + public static void main(String[] args) { + //原味咖啡 + Coffee c = new SimpleCoffee(); + print(c); + + //增加牛奶的咖啡 + c = new WithMilk(c); + print(c); + + //再加一点糖 + c = new WithSugar(c); + print(c); + } +} +``` \ No newline at end of file diff --git "a/Interview/mode/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/Interview/mode/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" new file mode 100644 index 00000000..4e209295 --- /dev/null +++ "b/Interview/mode/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" @@ -0,0 +1,113 @@ +## 观察者模式 +> 在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。 + 其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。 +> 四个角色 +> - 抽象被观察者角色 +> - 抽象观察者角色 +> - 具体被观察者角色 +> - 具体观察者角色 + +### 举例子(如:微信公众号) + +> 定义一个抽象被观察者接口,声明了添加、删除、通知观察者方法。 + +```java +interface Observerable { + void registerObserver(Observer o); + void removeObserver(Observer o); + void notifyObserver(); +} +``` + +> 定义一个抽象观察者接口,定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。 + +```java +interface Observer { + void update(String message); +} +``` + +> 定义被观察者,实现三个接口,并且list保存注册的Observer + +```java +class WechatObserver implements Observerable { + + private List list; + private String message; + + public WechatObserver() { + list = new ArrayList(); + } + + @Override + public void registerObserver(Observer o) { + list.add(o); + } + + @Override + public void removeObserver(Observer o) { + list.remove(o); + } + + @Override + public void notifyObserver() { + for (Observer observer : list) { + observer.update(message); + } + } + + public void setMessage(String message) { + this.message = message; + System.out.println("微信服务更新消息:" + message); + // 消息更新,通知所有观察者 + notifyObserver(); + } +} +``` +> 定义具体观察者,User + +```java +class User implements Observer { + + private String name; + private String message; + + public User(String name) { + this.name = name; + } + + + @Override + public void update(String message) { + this.message = message; + read(); + } + + void read() { + System.out.println(name+ " 收到推送消息:" + message); + } +} +``` + +### 测试 + +```java +public class ObserverMode { + public static void main(String[] args) { + WechatObserver wechatObserver = new WechatObserver(); + User maifeng = new User("Maifeng"); + User xiaofeng = new User("Xiaofeng"); + User fengfeng = new User("Fengfeng"); + + wechatObserver.registerObserver(maifeng); + wechatObserver.registerObserver(xiaofeng); + wechatObserver.registerObserver(fengfeng); + wechatObserver.setMessage("PHP是世界上最好的语言..."); + + System.out.println("---------------"); + + wechatObserver.removeObserver(fengfeng); + wechatObserver.setMessage("JAVA是世界上最好的语言..."); + } +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 6f192535..e9eedb72 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ ## 我是这样回答的 > 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) -- [test](/Interview/classify/README.md) +- [知识分类](/Interview/classify/README.md) ## 刷题系列 - [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) @@ -70,36 +70,13 @@ > 可以从牛客找笔试题的感觉,也可以练习输入输出,做多了,你就发现基本没有树和链表! ## Java面试思维导图(包括分布式架构) -- [总体架构](https://www.processon.com/view/link/5e170217e4b0bcfb733ce553) **这边就不放图了,放图的字体小,放大可能模糊。该图还在持续总结中...** -- [Java常见基础问题](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) **常见的基础问题,这是必须要掌握。** -- [Java常见集合问题]() **还没总结,后续总结...** -- [Java常见多线程问题](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) **常见的多线程问题,也是必须掌握...** -- [JVM常见问题](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) **常见的JVM要掌握的点...** -- [Spring常见问题](https://www.processon.com/view/link/5e846de9e4b07b16dcdb63f0) **常见的Spring面试的问题...** -- [Mybatis常见问题](https://www.processon.com/view/link/5e4e3b7ae4b0369b916b2e71) **常见的Mybatis面试的问题...** -- [MySQL常见问题](https://www.processon.com/view/link/5e9b0cb15653bb1a686e17ea) **常见的MySQL面试的问题...** -- [Redis常见问题](https://www.processon.com/view/link/5ea2da5907912948b0d89a0a) **常见的Redis面试的问题...** -- [计算机网络常见问题](https://www.processon.com/view/link/5eb8c93be401fd16f42b5f77) **常见的计算机网络面试的问题...** -- [Dubbo常见问题](https://www.processon.com/view/link/5eb8c9715653bb6f2aff7c11) **常见的Dubbo的问题...** -- [RocketMQ常见问题](https://www.processon.com/view/link/5ecf208f7d9c08156c6c37e3) **常见的RocketMQ的问题...** - -## 微服务班车在线预约系统 -- [微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 -- [BUG排查之一](https://github.com/DreamCats/school-bus/blob/master/doc/%E4%B8%8A%E7%BA%BF%E9%81%87%E5%88%B0%E7%9A%84bug.md) +- [知识架构思维导图](Interview/mind/README.md) ## 吐血系列 -- **个人吐血系列-总结Java基础**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java基础.md)->[博客阅读](http://dreamcat.ink/2020/03/27/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-chu/)-> [掘金阅读](https://juejin.im/post/5e7e0615f265da795568754b) -- **个人吐血系列-总结Java集合**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java集合.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-he/)-> [掘金阅读](https://juejin.im/post/5e801e29e51d45470b4fce1c) -- **个人吐血系列-总结Java多线程**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java多线程.md)->[博客阅读](http://dreamcat.ink/2020/03/25/ge-ren-tu-xie-xi-lie-zong-jie-java-duo-xian-cheng/)-> [掘金阅读-1](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) [掘金阅读-2](https://juejin.im/post/5e7e10b5518825739b2d1fb1) -- **个人吐血系列-总结JVM**: [本地阅读](/Interview/crazy/个人吐血系列-总结JVM.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-jvm/)-> [掘金阅读](https://juejin.im/post/5e8344486fb9a03c786ef885) -- **个人吐血系列-总结Spring**: [本地阅读](/Interview/crazy/个人吐血系列-总结Spring.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-spring/)-> [掘金阅读](https://juejin.im/post/5e846a4a6fb9a03c42378bc1) -- **个人吐血系列-总结Mybatis**: [本地阅读](/Interview/crazy/个人吐血系列-总结Mybatis.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-mybatis/)-> [掘金阅读](https://juejin.im/post/5e889b196fb9a03c875c8f50) -- **个人吐血系列-总结MySQL**: [本地阅读](/Interview/crazy/个人吐血系列-总结MySQL.md)->[博客阅读](http://dreamcat.ink/2020/03/30/ge-ren-tu-xie-xi-lie-zong-jie-mysql/)-> [掘金阅读](https://juejin.im/post/5e94116551882573b86f970f) -- **个人吐血系列-总结Redis**: [本地阅读](/Interview/crazy/个人吐血系列-总结Redis.md)->[博客阅读](http://dreamcat.ink/2020/03/31/ge-ren-tu-xie-xi-lie-zong-jie-redis/)-> [掘金阅读](https://juejin.im/post/5e9d6a9ff265da47e34c0e8a) -- **个人吐血系列-总结计算机网络**: [本地阅读](/Interview/crazy/个人吐血系列-总结计算机网络.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)-> [掘金阅读](https://juejin.im/post/5ea383c251882573716ab496) -- **个人吐血系列-Dubbo**: [本地阅读](/Interview/crazy/个人吐血系列-总结Dubbo.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)-> [掘金阅读](https://juejin.im/post/5eb11127f265da7bb46bce26) -- **个人吐血系列-RocketMQ**: [本地阅读](/Interview/crazy/个人吐血系列-总结RocketMQ.md)->[博客阅读](http://dreamcat.ink/2020/04/01/ge-ren-tu-xie-xi-lie-zong-jie-rocketmq/)-> [掘金阅读](https://juejin.im/post/5ecf1f716fb9a047f338b972) +- [疯狂吐血系列](Interview/crazy/README.md) +## 项目 +- [微服务班车在线预约系统](/Interview/bus/README.md) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 ## 基础 - [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) @@ -175,27 +152,8 @@ ## Linux - [linux-基础](/Interview/linux/linux-基础.md) -### 项目 -- 项目地址:[微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) - -- [环境搭建文档](/Interview/codes/bus/环境搭建文档.md) -- [Redis绑定Token分析文档](/Interview/codes/bus/Redis绑定Token.md) -- [用户服务所有接口分析文档](/Interview/codes/bus/用户服务.md) -- [班车服务所有接口分析文档](/Interview/codes/bus/班车服务.md) -- [订单服务所有接口分析文档](/Interview/codes/bus/订单服务.md) -- [支付服务所有接口分析文档](/Interview/codes/bus/支付服务.md) -- [添加订单、支付和退款的业务结合消息队列](/Interview/codes/bus/RocketMQ最终一致性.md) -- [Redis的key过期事件结合自动取消订单业务](/Interview/codes/bus/Redis的key过期事件.md) -- [SQL语句调优](/Interview/codes/bus/业务逻辑SQL语句.md) -- [Zookeeper的bug之一](/Interview/codes/bus/上线遇到的bug.md) - -### 设计模式 -- [单例模式](/Interview/codes/modes/单例模式.md) -- [工厂模式](/Interview/codes/modes/工厂模式.md) -- [代理模式](/Interview/codes/modes/代理模式.md) -- [模版方法模式](/Interview/codes/modes/模板方法模式.md) -- [观察者模式](/Interview/codes/modes/观察者模式.md) -- [装饰器模式](/Interview/codes/modes/装饰器模式.md) +## 设计模式 +- [面试必问设计模式](/Interview/mode/README.md) From 28c5b0beab648177d35e7735d147a5923c3c9371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Thu, 31 Dec 2020 18:07:43 +0800 Subject: [PATCH 062/366] delete ... --- ...70\350\247\201\351\227\256\351\242\230.md" | 21 - ...\344\272\272\344\275\223\347\263\273v2.md" | 5110 ----------------- ...72\345\210\267\347\206\237\351\242\230.md" | 3375 ----------- .../myself/\345\211\221\346\214\207offer.md" | 3147 ---------- ...13\347\274\226\347\250\213\351\242\230.md" | 416 -- ...\345\272\246\346\200\273\347\273\223lc.md" | 4513 --------------- ...70\350\247\201\351\227\256\351\242\230.md" | 16 - ...01\347\232\204\351\227\256\351\242\230.md" | 209 - ...ID\347\232\204\345\216\237\347\220\206.md" | 105 - ...30\345\202\250\345\274\225\346\223\216.md" | 40 - ...73\347\273\237\345\216\237\347\220\206.md" | 460 -- ...347\264\242\345\274\225-B-\346\240\221.md" | 134 - Interview/mysql/test.sql | 170 - Interview/mysql/test2.sql | 72 - ...62\344\273\200\344\271\210\347\232\204.md" | 74 - ...43\344\272\233\344\272\213\345\204\277.md" | 347 -- ...70\350\247\201\351\227\256\351\242\230.md" | 25 - ...01\347\232\204\351\227\256\351\242\230.md" | 261 - ...70\350\247\201\351\227\256\351\242\230.md" | 18 - README.md | 23 +- 20 files changed, 1 insertion(+), 18535 deletions(-) delete mode 100644 "Interview/mybatis/MyBatis\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" delete mode 100644 "Interview/myself/\344\270\252\344\272\272\344\275\223\347\263\273v2.md" delete mode 100644 "Interview/myself/\344\270\252\344\272\272\345\210\267\347\206\237\351\242\230.md" delete mode 100644 "Interview/myself/\345\211\221\346\214\207offer.md" delete mode 100644 "Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" delete mode 100644 "Interview/myself/\346\214\211\347\203\255\345\272\246\346\200\273\347\273\223lc.md" delete mode 100644 "Interview/mysql/MySQL\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" delete mode 100644 "Interview/mysql/Mysql-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" delete mode 100644 "Interview/mysql/Mysql\344\270\255ACID\347\232\204\345\216\237\347\220\206.md" delete mode 100644 "Interview/mysql/sql-\345\255\230\345\202\250\345\274\225\346\223\216.md" delete mode 100644 "Interview/mysql/sql-\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" delete mode 100644 "Interview/mysql/sql-\347\264\242\345\274\225-B-\346\240\221.md" delete mode 100644 Interview/mysql/test.sql delete mode 100644 Interview/mysql/test2.sql delete mode 100644 "Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-DNS\346\230\257\345\271\262\344\273\200\344\271\210\347\232\204.md" delete mode 100644 "Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-http\351\202\243\344\272\233\344\272\213\345\204\277.md" delete mode 100644 "Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" delete mode 100644 "Interview/redis/Redis-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" delete mode 100644 "Interview/spring/Spring\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" diff --git "a/Interview/mybatis/MyBatis\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Interview/mybatis/MyBatis\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 3ab3c97b..00000000 --- "a/Interview/mybatis/MyBatis\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,21 +0,0 @@ -- [什么是数据持久化](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E6%95%B0%E6%8D%AE%E6%8C%81%E4%B9%85%E5%8C%96) -- [Mybatis框架简介](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E6%A1%86%E6%9E%B6%E7%AE%80%E4%BB%8B) -- [什么是ORM](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#orm) -- [MyBatis框架的优缺点及其适用的场合](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E6%A1%86%E6%9E%B6%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9%E5%8F%8A%E5%85%B6%E9%80%82%E7%94%A8%E7%9A%84%E5%9C%BA%E5%90%88) -- [MyBatis与Hibernate有哪些不同?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E4%B8%8Ehibernate%E6%9C%89%E5%93%AA%E4%BA%9B%E4%B8%8D%E5%90%8C) -- [#{}和${}的区别是什么?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E5%92%8C%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88) -- [当实体类中的属性名和表中的字段名不一样,怎么办](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E5%BD%93%E5%AE%9E%E4%BD%93%E7%B1%BB%E4%B8%AD%E7%9A%84%E5%B1%9E%E6%80%A7%E5%90%8D%E5%92%8C%E8%A1%A8%E4%B8%AD%E7%9A%84%E5%AD%97%E6%AE%B5%E5%90%8D%E4%B8%8D%E4%B8%80%E6%A0%B7-%E6%80%8E%E4%B9%88%E5%8A%9E-) -- [模糊查询like语句该怎么写?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E6%A8%A1%E7%B3%8A%E6%9F%A5%E8%AF%A2like%E8%AF%AD%E5%8F%A5%E8%AF%A5%E6%80%8E%E4%B9%88%E5%86%99) -- [这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E9%80%9A%E5%B8%B8%E4%B8%80%E4%B8%AAxml%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E9%83%BD%E4%BC%9A%E5%86%99%E4%B8%80%E4%B8%AAdao%E6%8E%A5%E5%8F%A3%E4%B8%8E%E4%B9%8B%E5%AF%B9%E5%BA%94%E8%AF%B7%E9%97%AE%E8%BF%99%E4%B8%AAdao%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88dao%E6%8E%A5%E5%8F%A3%E9%87%8C%E7%9A%84%E6%96%B9%E6%B3%95%E5%8F%82%E6%95%B0%E4%B8%8D%E5%90%8C%E6%97%B6%E6%96%B9%E6%B3%95%E8%83%BD%E9%87%8D%E8%BD%BD%E5%90%97) -- [Mybatis是如何进行分页的?分页插件的原理是什么?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E6%98%AF%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E5%88%86%E9%A1%B5%E7%9A%84%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6%E7%9A%84%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88) -- [Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E6%98%AF%E5%A6%82%E4%BD%95%E5%B0%86sql%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C%E5%B0%81%E8%A3%85%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%AF%B9%E8%B1%A1%E5%B9%B6%E8%BF%94%E5%9B%9E%E7%9A%84%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%98%A0%E5%B0%84%E5%BD%A2%E5%BC%8F) -- [Mybatis动态sql有什么用?执行原理?有哪些动态sql?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E5%8A%A8%E6%80%81sql%E6%9C%89%E4%BB%80%E4%B9%88%E7%94%A8%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86%E6%9C%89%E5%93%AA%E4%BA%9B%E5%8A%A8%E6%80%81sql) -- [Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E7%9A%84xml%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E4%B8%AD%E4%B8%8D%E5%90%8C%E7%9A%84xml%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6id%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E9%87%8D%E5%A4%8D) -- [为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AF%B4mybatis%E6%98%AF%E5%8D%8A%E8%87%AA%E5%8A%A8orm%E6%98%A0%E5%B0%84%E5%B7%A5%E5%85%B7%E5%AE%83%E4%B8%8E%E5%85%A8%E8%87%AA%E5%8A%A8%E7%9A%84%E5%8C%BA%E5%88%AB%E5%9C%A8%E5%93%AA%E9%87%8C) -- [MyBatis实现一对一有几种方式?具体怎么操作的?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E5%AE%9E%E7%8E%B0%E4%B8%80%E5%AF%B9%E4%B8%80%E6%9C%89%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%85%B7%E4%BD%93%E6%80%8E%E4%B9%88%E6%93%8D%E4%BD%9C%E7%9A%84) -- [MyBatis实现一对多有几种方式,怎么操作的?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E5%AE%9E%E7%8E%B0%E4%B8%80%E5%AF%B9%E5%A4%9A%E6%9C%89%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%80%8E%E4%B9%88%E6%93%8D%E4%BD%9C%E7%9A%84) -- [Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E6%98%AF%E5%90%A6%E6%94%AF%E6%8C%81%E5%BB%B6%E8%BF%9F%E5%8A%A0%E8%BD%BD%E5%A6%82%E6%9E%9C%E6%94%AF%E6%8C%81%E5%AE%83%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88) -- [Mybatis的一级、二级缓存](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#mybatis%E7%9A%84%E4%B8%80%E7%BA%A7%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98) -- [什么是MyBatis的接口绑定?有哪些实现方式?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E4%BB%80%E4%B9%88%E6%98%AFmybatis%E7%9A%84%E6%8E%A5%E5%8F%A3%E7%BB%91%E5%AE%9A%E6%9C%89%E5%93%AA%E4%BA%9B%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F) -- [使用MyBatis的mapper接口调用时有哪些要求?](https://github.com/DreamCats/SpringBooks/blob/master/MyBatis%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md#%E4%BD%BF%E7%94%A8mybatis%E7%9A%84mapper%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%97%B6%E6%9C%89%E5%93%AA%E4%BA%9B%E8%A6%81%E6%B1%82) -- [mybatis是如何防止SQL注入的](https://zhuanlan.zhihu.com/p/39408398) diff --git "a/Interview/myself/\344\270\252\344\272\272\344\275\223\347\263\273v2.md" "b/Interview/myself/\344\270\252\344\272\272\344\275\223\347\263\273v2.md" deleted file mode 100644 index 4441627e..00000000 --- "a/Interview/myself/\344\270\252\344\272\272\344\275\223\347\263\273v2.md" +++ /dev/null @@ -1,5110 +0,0 @@ -# 自我介绍 -- 面试官您好,我叫买峰,来自电子科技大学2018届应届硕士生,所学专业为软件工程。 - -- 首先在项目经历上,做了3个科研项目、2个自主研发项目(其中有一个自主研发项目是我最近分析本学校的一个班车预约平台并重新采用新的技术栈进行研发)。其次,在校期间也撰写了4个软件著作权。最后,在科研方面的方式属于深度学习中的音乐分离方向,同时发表了一篇会议论文。 - -- 平时比较喜欢关注互联网新技术,比如在今日头条、掘金、知乎和微信公众号上。其次,我会将自己的感受撰写文章发布在自己的博客、掘金和微信公众号上分享给大家。 - -- 最后,平时也喜欢听歌跑步陶冶自己的情操和锻炼自己的身体。 - -# Java - -## Java基础 -### 8大基础类型 - -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) - -- 装箱:将基本类型用它们对应的**引用类型**包装起来; -- 拆箱:将包装类型转换为**基本数据类型**; -- valueOf - ```java - // This method will always cache values in the range -128 to 127, - public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) // 条件 - return IntegerCache.cache[i + (-IntegerCache.low)];// 取缓存, - // Integeer的源码中: - // static final int low = -128; IntegerCache.low = -128 - return new Integer(i); - } - ``` -> 当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。 - -### ==、equals和hashcode - -#### == -它的作用是**判断两个对象的地址是不是相等**。即,判断两个对象是不是同一个对象: - -- 基本数据类型==比较的是**值** -- 引用数据类型==比较的是**内存地址** - -#### equals -它的作用也是判断两个对象是否相等。但它一般有两种使用情况: - -- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过`==`比较这两个对象。 -- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - -举个hashset的例子 - -### 面向对象 -#### 封装 -封装把一个对象的**属性私有化**,同时提供一些可以**被外界访问的属性的方法**。 - -#### 继承 -继承是使用**已存在的类**的定义作为基础建立新类的技术,新类的定义可以增加**新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类**。通过使用继承我们能够非常方便地复用以前的代码。 - -注意: -- 子类拥有父类对象**所有的属性和方法**(包括私有属性和私有方法),但是**父类中的私有属性和方法子类是无法访问,只是拥有**。 -- 子类可以拥有自己属性和方法,即子类可以对父类进行**扩展**。 -- 子类可以用自己的方式实现父类的方法。(以后介绍)。 - -#### 多态 - -三要素:加黑的地方! - -首先我觉得即**一个引用变量到底会指向哪个类的实例对象**,该**引用变量发出的方法调用到底是哪个类中实现的方法**,必须在由程序**运行期间**才能决定。 - -举个例子: - -任何事物的多个姿态,多个形态。比如,你说一个猫在吃东西,同样的,你也能说一个动物在吃东西。 - -```java - -public class Test { - public static void main(String[] args){ - Animal animal = new Cat(); - animal.eat() // 猫也会吃饭 - // 你看到了一只猫,同样它也是动物 - // 比如有很多其他种类继承了动物哈, - // 当编译期间的animal引用变量,到底指的哪个实例对象,(重要)(主语是引用变量) - // 或者该引用调用的eat方法,到底是哪个实例对象的eat,编译期间恐怕不知道哦(主语是引用变量) - // 只有运行期间,哦哦, 原来是猫的eat方法哇... - } -} -``` - -##### 表现形式 - -所以多态的表现形式: - -- **Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。调用方法时通过传递的参数类型来决定具体使用哪个方法**,这就是多态性。 -- **Java的方法重写,是父类与子类之间的多态性,子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改**。这也是多态性。 - -##### 底层 - -首先要说:首先当程序运行需要某个类时,类加载器会将相应的class文件载入到JVM中,并在方法区建立该类的类型信息(包括方法代码,类变量、成员变量以及**方法表**。(标黑的这个玩意) - - - -面试官:方法表有啥? - -我:方法表的结构如同字段表一样,依次包括了**访问标志、名称索引、描述符索引、属性表集合**几项。 - -接着回答:**方法表是实现动态调用的核心。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向记录该类方法的方法表,方法表中的每一个项都是对应方法的指针**。 - -到这里:就要分情况讨论了,一个是方法调用,一个是接口 - -###### 方法调用 - -先说方法调用:举个例子 - -```java -class Person { - // 重写object的toString - public String toString(){ - return "I'm a person."; - } - public void eat(){} - public void speak(){} - -} - -class Boy extends Person{ - // 重写object的toString - public String toString(){ - return "I'm a boy"; - } - // 继承Person的speak - public void speak(){} - // 自己实现的自定义方法 - public void fight(){} -} - -class Girl extends Person{ - // 重写object的toString - public String toString(){ - return "I'm a girl"; - } - // 继承Person的speak - public void speak(){} - // 自己实现的自定义方法 - public void sing(){} -} -``` - -![参考](https://imgkr.cn-bj.ufileos.com/aba89335-fb8c-44b8-84ea-ff6a0bfa9548.png) - -这张图的指向:你可以根据颜色对应上,注意方法表条目指向的具体的**方法地址**。其次注意蓝色部分其继承自于 Person 的方法 eat() 和 speak() 分别指向 **Person 的方法实现和本身的实现**。如果子类改写了父类的方法,那么子类和父类的那些**同名的方法共享一个方法表项**。因此,**所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值**。Person 或 Object中的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。 - - - -调用过程: - -1. 在常量池里找到方法调用的**符号引用**(肯定先看到Person定义引用类型) -2. 查看Person的方法表,得到speak方法在该**方法表的偏移量**(假设为15),这样就得到该方法的直接引用。 -3. 根据this(invoker this字节码)指针得到**具体的对象**(即 girl 所指向的位于堆中的对象)。 -4. 根据对象得到该对象对应的方法表,根据偏移量15查看**有无重写(override)该方法**,如果重写,则可以直接调用(Girl的方法表的speak项指向自身的方法而非父类);如果没有重写,则需要拿到按照继承关系从下往上的基类(这里是Person类)的方法表,同样按照这个偏移量15查看有无该方法。 - - - -###### 接口调用 - -一个类可以实现多个接口,那么就像多继承一样,这样的话,在方法表中的索引就会不一样,所以Java 对于接口方法的调用是采用**搜索方法表**的方式。 - -补充一下: - - -#### 重载和重写的区别 -- 重载:发生在同一个类中,**方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同**。 -- 重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,**方法名、参数列表必须相同,返回值范围小于等于父类**,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。也就是说方法提供的行为改变,而方法的外貌并没有改变。 - -#### 接口和抽象类的区别 -1. 方法是否能实现:所有**方法在接口中不能有实现**(Java 8 开始接口方法可以有默认实现),而**抽象类可以有非抽象的方法**。 -2. 变量:接口中除了**static、final变量**,不能有其他变量,而抽象类中则不一定。 -3. 实现:一个类可以实现**多个接口**,但**只能实现一个抽象类**。接口自己本身可以通过implement关键字扩展多个接口。 -4. 修饰符:接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 - -#### [匿名内部类传参数为什么需要final](https://blog.csdn.net/tianjindong0804/article/details/81710268?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight) - -### String -#### String StringBuffer 和 StringBuilder 的区别是什么? -1. 可变性 - -- 简单的来说:`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 `String` 对象是不可变的。 -- `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串char[]value 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。 - -2. 线程安全 -- `String` 中的对象是不可变的,也就可以理解为常量,线程安全。 -- `AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。 - - -3. 性能 -- 每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。 -- `StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - - -- 操作少量的数据: 适用String -- 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder -- 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer - -#### String对象和常量池 -```java -public class StringTest { - public static void main(String[] args) { - String str1 = "todo"; // 常量池 - String str2 = "todo"; // 从常量池找了str1 - String str3 = "to"; // 常量池 - String str4 = "do"; // 常量池 - String str5 = str3 + str4; // 内部用StringBuilder拼接了一波。 因此, 并非常量池 - String str6 = new String(str1); // 创建对象了, 那还能是常量池的引用? - } -} -``` - -分析一波: -- 成的class文件中会在常量池中**保存“todo”、“to”和“do”三个String常量**。 -- 变量str1和str2均保存的是常量池中“todo”的引用,所以str1==str2成立; -- 在执行 str5 = str3 + str4这句时,**JVM会先创建一个StringBuilder对象,通过StringBuilder.append()方法将str3与str4的值拼接**,然后通过StringBuilder.toString()返回一个堆中的String对象的引用,赋值给str5,因此str1和str5指向的不是同一个String对象,str1 == str5不成立; -- String str6 = new String(str1)一句显式创建了一个新的String对象,因此str1 == str6不成立便是显而易见的事了。 - -#### intern -- jdk6: -执行intern()方法时,**若常量池中不存在等值的字符串,JVM就会在常量池中创建一个等值的字符串,然后返回该字符串的引用**。 -- jdk7: -执行intern操作时,如果常量池已经存在该字符串,则直接返回字符串引用,否则**复制该字符串对象的引用到常量池中并返回**。 - -### 关键字 -#### final -final关键字主要用在三个地方:变量、方法、类。 - -- 对于一个final变量,如果是**基本数据类型的变量,则其数值一旦在初始化之后便不能更改**;如果是引用类型的变量,则在对其初始化之后便**不能再让其指向另一个对象**。 -- 当用final修饰一个类时,表明**这个类不能被继承**。final类中的所有成员方法都会被隐式地指定为final方法。 -- 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 - -final修饰有啥好处 - -- final的关键字**提高了性能**,JVM和java应用会**缓存final变量**; -- final变量可以在多线程环境下保持**线程安全**; -- 使用final的关键字提高了性能,JVM会对方法变量类进行优化; - -#### static -- **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` -- **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. -- **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 -- **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 - -### Java值传递 -下面再总结一下 Java 中方法参数的使用情况: - -- 一个方法**不能修改一个基本数据类型的参数**(即数值型或布尔型)。 -- 一个方法可以改变**一个对象参数的状态**。 -- 一个方法**不能让对象参数引用一个新的对象**。 - -[个人写的例子](https://dreamcater.gitee.io/javabooks/#/codes/Java%E5%80%BC%E4%BC%A0%E9%80%92%E7%9A%84%E9%97%AE%E9%A2%98) - -### 异常 -#### Error - -面试官:给我讲讲什么是Error? - -我:其实就是明显的给自己挖坑,哈能咋滴!**Error描述了Java运行时系统的内部错误和资源耗尽错误,你比如栈溢出和堆溢出啦**?不好,面试官微笑了。 - -面试官:讲一下什么是栈溢出和堆溢出? - -我:哎,中枪了,这咋害能扯到虚拟机上了 - -- StackOverFlowError:**如果线程请求的栈深度大于虚拟机所允许的深度**,将抛出此异常。比如,**无限递归方法**,其实面试官按捺不住的问 - -面试官:为什么无限递归方法就可以抛出该异常? - -我:因为我们知道,**每次调用方法所产生资源都存放在了虚拟机栈中**,如果无限递归下去,那岂不是? - -面试官:虚拟机栈存了什么资源? - -我:我真的是!虚拟机栈存了**局部变量表、操作数栈、动态链接和方法出口**。 - -面试官:局部变量表中存了什么? - -我:啊?还好我会,存放了编译期可知的各种**基本数据类型(8大基本类型)**,**对象引用类型**,它不等同于对象本身,可能是一个指向对象**起始地址的引用指针**,也可能是指向一个**代表对象的句柄或其他与此对象相关的位置**。 - -面试官:好,开始讲堆溢出 - -我:害能给我绕回来...如果**虚拟机可动态扩展,如果扩展时无法申请到足够的内存**,就会抛出OutOfMemoryError异常,当然,**如果在堆中没有内存完成实例分配,并且堆也无法再扩展时**,也会抛出该异常。比如,我又挖坑,举例子:无限创建线程。这次我主动说原因:操作系统分配给每个进程内存是有限的,比如32位的windows限制为2G。虚拟机提供了参数来控制堆和方法区的内存的最大值,而剩下的内存,忽略其他因素,就由虚拟机栈和本地方法栈“瓜分天下了”。**每个线程分配到栈容越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。** - -面试官:**嘿嘿,方法区会溢出吗?** - -我:嘿嘿,会。比如方法区中有一个**运行时常量池**,晓得吧?其中String.intern()方法是一个native方法,它(1.6)的作用是:如果字符串常量池中已经包含了此String对象的字符串,则返回代表池中这个字符串String对象;**否则,将此String对象所包含的字符串添加到常量池中,并且返回此String对象的引用**。在1.7版本就不一样了,**而是从堆中实例String对象的引用复制到常量池并返回**。当然,还有很多带有**反射**机制的框架,大量使用反射创建类来填满方法区。 - -面试官:嘿嘿,直接内存会溢出吗? - -我:简直了,太能问了。那肯定也是能的哦,比如DirectByteBuffer。 - -#### Exception - -面试官:可以了,聊Exception - -我:无限退出递模式!Exception又分解为**RuntimeException**(运行时)和程序本身没有问题,由于像IO错误这类问题导致的异常(编译)。 - -面试官:RuntimeException中有哪些,举一些? - -我:好的,比如,NullPointerException,ArithmeticException,ClassCastException,ArrayIndexOutOfBoundsException等 - -面试官:什么是受检异常和非受检异常? - -我:派生于**Error类或RuntimeException类**的所有异常称为非受检异常,所有其他的异常称为受检异常。 - -#### 捕获异常 - -面试官:如何捕获异常? - -我: - -- `try` 块: 用于捕获异常。其后可接零个或多个`catch`块,如果没有`catch`块,则必须跟一个`finally`块。 - -- `catch` 块: 用于处理`try`捕获到的异常。 - -- `finally` 块: 无论是否捕获或处理异常,`finally`块里的语句都会被执行。当在`try`块或`catch`块中遇到`return`语句时,`finally`语句块将在方法返回之前被执行。 - -throw 抛出异常,throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常) - - - -[https://blog.csdn.net/efei7968/article/details/87077218](https://blog.csdn.net/efei7968/article/details/87077218) - -[https://blog.csdn.net/efei7968/article/details/87174324?](https://blog.csdn.net/efei7968/article/details/87174324?) - -### IO -#### BIO -**BIO (Blocking I/O)**:**同步阻塞I/O模式**,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 - -#### NIO -**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 的非阻塞模式来开发。 - -[NIO底层原理](https://blog.csdn.net/u013857458/article/details/82424104) - -#### AIO -**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,不过又放弃了。 - -### 反射 - -> 面试遇到这个问题,必须好好的想想如何回答这个问题 - -反射三要素? - -面试官:反射是什么? - -我:在Java的反射机制中是指在**运行状态**中,对于任意一个类都能够知道这个类所有的**属性和方法**;并且对于任意一个对象,都能够调用它的**任意一个方法**;这种**动态获取信息以及动态调用对象方法**的功能成为 Java 语言的反射机制。 - -面试官:哦?有什么好处? - -我:怎么说呢,跟多态是的,比如在Java程序中许多对象在运行是都会出现两种类型:**编译时类型和运行时类型**。其中,编译时类型由**声明对象时使用的类型来决定**,运行时的类型由**实际赋值给对象的类型决定** 。比如 - -`People = = new Man();`程序在运行的时候,有时候需要注入外部资源,那么这个外部资源在编译时是object,如果想要它的运行时类型中的某个方法,为了解决这些问题,程序在运行时发现对象和类的真实信息,但是编译时根本无法预知该对象和类属于哪些类,程序只能靠运行时信息来发现该对象和类的信息,那就要用到反射了。 - -面试官:举几个反射的API - -我: - -1. Class 类:反射的核心类,可以获取类的属性,方法等信息。 -2. Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。 -3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。 -4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法 - -面试官:获取class对象的三种方式? - -我: - -```java - -Student student = new Student(); *// 这一new 产生一个Student对象,一个Class对象。* - -Class studentClass2 = Student.class; // 调用某个类的 class 属性来获取该类对应的 Class 对象 - -Class studentClass3 = Class.forName("com.reflect.Student") // 使用 Class 类中的 forName() 静态方法 ( 最安全 / 性能最好 ) - -``` - -面试官:三者区别? - -我: - -- Class.class 的形式会使 JVM 将使用类装载器将类装入内存(前提是类还没有装入内存),不做类的初始化工作,返回 Class 对象。 -- Class.forName() 的形式会装入类并做类的静态初始化,返回 Class 对象。 -- getClass() 的形式会对类进行静态初始化、非静态初始化,返回引用运行时真正所指的对象(因为子对象的引用可能会赋给父对象的引用变量中)所属的类的 Class 对象。 - -> 静态属性初始化是在加载类的时候初始化,而非静态属性初始化是 new 类实例对象的时候初始化。它们三种情况在生成 Class 对象的时候都会先判断内存中是否已经加载此类。 - -面试官:除了通过反射创建对象,还有? - -我:new呗,clone一个呗 - -面试官:反射都有哪些应用场景 - -我:我可以说Spring,Dubbo,RocketMQ吗?这些优秀的框架背后都用到了反射,这说明,反射的优点之一灵活,提高了代码的灵活度,但同时性能受损。因为反射要进行一系列的解释操作。 - - - -[https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html](https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html) - -### 深浅拷贝 -- **浅拷贝**:对**基本数据类型进行值传递**,对**引用数据类型进行引用传递般的拷贝**,此为浅拷贝。 -- **深拷贝**:对**基本数据类型进行值传递**,对**引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝** -- 也就二者对引用数据类型有区别 - -[个人写的例子](https://dreamcater.gitee.io/javabooks/#/codes/%E6%B5%85%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%BE%8B%E5%AD%90) - -### Object -```java -public final native Class getClass(); -public native int hashCode(); // 返回对象的哈希代码值。 -public boolean equals(Object obj) -protected native Object clone() // 创建并返回此对象的副本。 -public String toString() // 返回对象的字符串表示形式。 -public final native void notify(); // 唤醒正在该对象的监视器上等待的单个线程。 -public final native void notifyAll(); // 唤醒正在该对象的监视器上等待的全部线程。 -public final native void wait(); // 使当前线程等待,直到另一个线程调用此对象的方法或方法。 -protected void finalize(); // 当垃圾回收确定不再有对对象的引用时,由对象上的垃圾回收器调用。 -``` - -### 四种修饰符的限制范围 -1. public:可以被所有其他类所访问。 - -2. private:只能被自己访问和修改。 - -3. protected:自身,子类及同一个包中类可以访问。 - -4. default(默认):同一包中的类可以访问 - -### 序列化 -1. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。 -2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。 -3. 如果想让某个变量不被序列化,使用transient修饰。 -4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。 -5. 反序列化时必须有序列化对象的class文件。 -6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。 -7. 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。 -8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。 -9. 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。 - -[https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf#heading-9](https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf#heading-9) - -### 泛型 -泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 - -**好处**: -- **类型安全**,提供编译期间的类型检测 -- **前后兼容** -- **泛化代码,代码可以更多的重复利用** -- **性能较高**,用GJ(泛型JAVA)编写的代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件 - -[泛型擦除原理](https://www.jianshu.com/p/328efeb01940) - -## Java集合 -### ArrayList和LinkedList的区别 -- ArrayList是实现了基于**动态数组**的数据结构,LinkedList基于**链表**的数据结构。 -- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 -- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 - -### ArrayList源代码 -#### 底层数据结构 -```java -private static final int DEFAULT_CAPACITY = 10; // 默认容量 -transient Object[] elementData; // Object 数组 -private int size; // 大小 -``` - -#### 构造方法 -- 无参数:`this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 默认10` -- 有参数:`this.elementData = new Object[initialCapacity];` - - -#### 扩容 -最主要的两句代码 -```java -int newCapacity = oldCapacity + (oldCapacity >> 1); // old + old / 2 就在这... -elementData = Arrays.copyOf(elementData, newCapacity); // 使用Arrays的复制 -``` - -#### add -```java -public boolean add(E e) { - // 扩容判断 - ensureCapacityInternal(size + 1); // Increments modCount!! 这个参数,起到并发异常作用。 - elementData[size++] = e; // 这一步非原子性,并发容易出错,好几种情况。 下次分析 - return true; -} -``` - -#### 并发问题 -> 其实就是size++ 这一步的问题。 越界就是两个线程临界值去扩容都满足,于是一个线程size++导致的,另外一个线程就溢出了,null就是element[size] = e,第一个线程还没来得及size++,第二个线程就在原先的索引上把值给覆盖了,并且在下一个索引为null。 - -越界 -- 列表大小为9,即size=9 -- 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 -- 线程B此时也进入add方法,它和获取的size的值也为9,也开始调用ensureCapacityInternal方法。 -- 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。 -- 线程B也发现需要大小为10,也可以容纳,返回。 -- 好了,**问题来了哈** -- 线程A开始进行设置值操作,elementData[size++] = e操作。此时size变为10。 -- 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为 -- 于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 - -null -- 列表大小为10,即size=0 -- 线程A开始添加元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。也就是说,线程挂在了`element[0] = e`上。 -- 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一条的操作。此时线程B获取的size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 -- **问题来了**,其实上面也是问题,覆盖了。。。 -- 线程A将size的值增加为1 -- 线程B开始将size的值增加为2 -- 当你获取1索引的时候,那不就是null了? - -#### get -```java -// 一般获取元素,第一步都要判断索引是否越界 -public E get(int index) { - rangeCheck(index); // 判断给定索引是否越界 - return elementData(index); -} -``` - - - -#### Fair-Fast机制 -ArrayList也采用了快速失败的机制,**通过记录modCount参数来实现**。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 - -### LinkedList源代码 -#### 底层数据结构 -```java -transient int size = 0; -transient Node first; // 经常用 -transient Node last; // 经常用 -// Node是私有的内部类,跟双向链表定义没什么区别 -... -``` - -#### getFirst和getLast -> 说白了,getFirst就是获取全局变量的first,getLast就是获取全局变量的last - -```java -public E getFirst() { - final Node f = first; // 第一个节点 - if (f == null) - throw new NoSuchElementException(); - return f.item; -} -public E getLast() { - final Node l = last; // 最后一个节点 - if (l == null) - throw new NoSuchElementException(); - return l.item; -} -``` - -#### add -> add内部调用的是linkLast,在后边插入 - -```java -void linkLast(E e) { - final Node l = last; // 要在尾部添加,所以要找last - final Node newNode = new Node<>(l, e, null); // last->e->null - last = newNode; - if (l == null) - first = newNode; // 如果l为空,说明没有链表没有元素,那么first和last都指向e, - else - l.next = newNode; // 否则就将l.next指向新节点 - size++; - modCount++; // 依然存在并发危险哦 -} -``` - -#### remove -> 其实就是遍历,unlink而已,这里考点,感觉是这样的,移除node,要先让pre和next互相链接,然后在移除node。从你代码里也可以看出,先prev.next = next; 然后x.prev = null; - -```java -E unlink(Node x) { - // assert x != null; - final E element = x.item; // 当前值 - final Node next = x.next; // 后 - final Node prev = x.prev; // 前 - - if (prev == null) { // 如果x的前是null,说明,x是首节点,直接让first指向next - first = next; - } else { // x不是首节点,意味着pre是有值哦。 - prev.next = next; // prev.next的箭头指向x的next - x.prev = null; // 将x.prev指向null 双向链表? - } - - if (next == null) { // 同上,说明x是最后节点了 - last = prev; // 让last指向x的prev - } else { - next.prev = prev; // 否则x不是最后节点, 让next.prev 指向prev - x.next = null; // x.next 指向null , 双向链表 - } - - x.item = null; // GC回收 - size--; - modCount++; - return element; -} -``` - -### HashMap(1.8) -#### 底层结构 -HashMap的底层结构是是**数组+链表**。关于为什么是链表,那是因为哈希冲突,采取链表一种方式。 - -#### 常见参数 -```java -static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始容量 -static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子0.75 -// 给定的默认容量为16,负载因子为0.75. -// Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容, -static final int TREEIFY_THRESHOLD = 8;// 成为红黑树的阈值,为什么是8? -static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值6 -static final int MIN_TREEIFY_CAPACITY = 64; -``` - -- 为什么容量要是 2 的整数次幂? -因为获取 key 在数组中对应的下标是通过 key 的哈希值与数组长度 -1 进行与运算,如:tab[i = (n - 1) & hash] - -1. n 为 2 的整数次幂,这样 n-1 后之前为 1 的位后面全是 1,这样就能保证 (n-1) & hash 后相应的位数既可能是 0 又可能是 1,这取决于 hash 的值,这样能保证散列的均匀,同时与运算效率高 -2. 如果 n 不是 2 的整数次幂,会造成更多的 hash 冲突 - -> 举个例子:如 16:10000, 16-1=15:1111, 1111 再与 hash 做 & 运算的时候,各个位置的取值取决于 hash,如果不是2的整数次幂,必然会有的0的位,这样再进行 & 操作的时候就为 0了,会造成哈希冲突。 - -> 注意:HashMap的tableSizeFor方法做了处理,能保证n永远都是2次幂 - -[https://zhuanlan.zhihu.com/p/90816780](https://zhuanlan.zhihu.com/p/90816780) - -- 为什么负载因子是0.75? -> 负载因子过低,频繁扩容,扩容会重新哈希,性能下降;负载因子过高,容易浪费容量. - -- 为什么红黑树的阈值是8? -> 在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。(泊松分布) - -- 为什么退化链表的阈值6? -> 6是因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。 - -#### hash -```java -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -``` - -hash 函数是先拿到通过 key 的 hashcode,**是 32 位的 int 值**,然后让 **hashcode 的高 16 位和低 16 位进行异或操作**。这个也叫扰动函数,这么设计有二点原因: - -- **一定要尽可能降低 hash 碰撞,越分散越好**; -- 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算; - -#### put -![put过程](https://imgkr.cn-bj.ufileos.com/b2206341-08ce-4e67-87bd-3a467ccac31e.png) - -- 判断数组是否为空,为空进行初始化;(初始化) -- 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;(通过hash计算index) -- 查看 table[index] 是否存在数据,没有数据就构造一个 Node 节点存放在 table[index] 中;(查看数组中是否哈希冲突) -- 存在数据,说明发生了 hash 冲突(存在二个节点 key 的 hash 值一样), 继续判断 key 是否相等,相等,用新的 value 替换原数据(onlyIfAbsent 为 false);(冲突,判断key是否相等,相等则替换) -- 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(判断是否红黑树) -- 如果不是树型节点,创建普通 Node 加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树;(判断是否转成红黑树) -- 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。(扩容) - -#### get - -> get比较简单,定位桶,逻辑来了。 - -1. 判断:是否为空,为空,返回null -2. 不为空,判断第一个位置是否为查询key,是,返回value -3. 不是,下一个节点继续判断是否为红黑树,是,按树查找 -4. 不是,按链表查找 - -#### 扩容 -> 先说1.7吧 -```java -for (HashMapEntry e : table) { - // 如果这个数组位置上有元素且存在哈希冲突的链表结构则继续遍历链表 - while (null != e) { - //取当前数组索引位上单向链表的下一个元素 - HashMapEntry next = e.next; - //重新依据hash值计算元素在扩容后数组中的索引位置 - int i = indexFor(e.hash, newCapacity); - e.next = newTable[i]; // 这一步和下一步就是头插法了,并且这两步出现线程不安全死循环问题 - newTable[i] = e; - e = next; // 遍历链表 - } -} -``` -> 可以适当画图讲, 最好! - -> 1.8 -HashMap的扩容使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。也就是说省略了重新计算hash值的时间,而且新增的1位是0还是1机会是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。如果在新表的数组索引位置相同,则链表元素不会倒置。 - -#### 1.8的优化 -- 数组+链表改成了**数组+链表或红黑树**; -- 链表的插入方式从**头插法改成了尾插法**,简单说就是插入时,如果数组位置上已经有元素,1.7 将新元素放到数组中,原始节点作为新节点的后继节点,1.8 遍历链表,将元素放置到链表的最后; -- 扩容的时候 1.7 需要对原数组中的元素进行**重新 hash 定位在新数组的位置**,1.8 采用更简单的判断逻辑,**位置不变或索引+旧容量大小**; -- 在插入时,**1.7 先判断是否需要扩容,再插入,1.8 先进行插入,插入完成再判断是否需要扩容**; - -#### 并发问题 -- HashMap扩容的时候会调用resize()方法,就是这里的并发操作容易在一个桶上形成环形链表 -- 这样当获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。 -- **但是1.7的头插法造成的问题,1.8改变了插入顺序,就解决了这个问题,但是为了内存可见性等安全性,还是需要ConCurrentHashMap** -- HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个数组,粒度比较大 -- Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现 -- ConcurrentHashMap 使用分段锁,降低了锁粒度,让并发度大大提高。(jdk1.8 CAS+ synchronized) - - -### ConcurrentHashMap -#### 1.7 -##### segment -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 `ReentrantLock`。 -- 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 -- **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** - -##### put -- 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 - - 尝试获取自旋锁 - - 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 - -总的来说: - -- 将当前的Segment中的table通过key的hashcode定位到HashEntry -- 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value -- 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 -- 最后会解除在1中所获取当前Segment的锁。 - -##### get -- 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 -- 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 -- ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** - -##### size -在 JDK1.7 中,第一种方案他会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给每个 Segment 加上锁,然后计算 ConcurrentHashMap 的 size 返回 - -#### 1.8 -1.7 查询遍历链表效率太低。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性 - -##### put -- 根据key计算出hashcode -- 判断是否需要进行初始化 -- 如果f为null,说明table中这个位置第一次插入元素,利用Unsafe.compareAndSwapObject方法插入Node节点。 - - 如果CAS成功,说明Node节点已经插入,随后addCount(1L, binCount)方法会检查当前容量是否需要进行扩容。 - - 如果CAS失败,说明有其它线程提前插入了节点,自旋重新尝试在这个位置插入节点。 -- 如果f的hash值为-1,说明当前f是ForwardingNode节点,意味有其它线程正在扩容,则一起进行扩容操作。 -- 如果都不满足,则利用`synchronized`锁写入数据 -- 如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。 - -##### get -- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 -- 如果是红黑树那就按照树的方式获取值。 -- 就不满足那就按照链表的方式遍历获取值。 - -##### size -ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是迭代 counterCells 来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 addCount() 方法。 - -在addCount()方法中: -- 如果 counterCells == null, 则对 baseCount 做 CAS 自增操作。 -- 如果并发导致 baseCount CAS 失败了使用 counterCells。 -- 如果counterCells CAS 失败了,在 fullAddCount 方法中,会继续死循环操作,直到成功。 -- CounterCell使用了 @sun.misc.Contended 标记的类 -> 缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。 - -实际上: -- JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。 -- JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。 - -[https://zhuanlan.zhihu.com/p/40627259](https://zhuanlan.zhihu.com/p/40627259) - -### HashSet -HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个`private static final Object PRESENT = new Object();`。 HashSet跟HashMap一样,都是一个存放链表的数组。 - -### 手写lru -> 双向链表+HashMap,Java中的LinkedHashMap就实现了该算法。 - -#### get -```java -public int get(int key) { - if (map.containsKey(key)) { - Node n = map.get(key); // 获取内存中存在的值,比如A - remove(n); //使用链表的方法,移除该节点 - setHead(n); //依然使用链表的方法,将该节点放入头部 - return n.value; - } - return -1; -} -``` -- 由于当一个节点通过key来访问到这个节点,那么这个节点就是刚被访问了, -- 就把这个节点删除掉,然后放到队列头,这样队列的头部都是最近访问的, -- 队列尾部是最近没有被访问的。 - -#### set -```java -public void set(int key, int value) { - if (map.containsKey(key)) { - Node old = map.get(key); - old.value = value; - remove(old); // 移除旧节点 - setHead(old); // 放到队头 - } else { - Node created = new Node(key, value); - if (map.size() >= capacity) { - map.remove(end.key); // clear该key - remove(end); //链表也是依次 - setHead(created); // 将created放入队头 - } else { - setHead(created); // 如果没满,直接放入队头 - } - map.put(key,created); - } -} -``` - - -## 多线程 -### 线程与进程的区别 -进程 - -**进程是程序的一次执行过程,是系统运行程序的基本单位**。系统运行一个程序即是一个进程从创建,运行到消亡的过程。 - -比如:当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。 - -线程 - -- **线程是一个比进程更小的执行单位** -- 一个进程在其执行的过程中可以产生**多个线程** -- 与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程 - -### 并行与并发 -- 并行:**单位时间内**,多个任务同时执行。 -- 并发:**同一时间段**,多个任务都在执行 (单位时间内不一定同时执行); - -### 上下文切换 -多线程编程中一般**线程的个数都大于 CPU 核心的个数**,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配**时间片并轮转**的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 - -实际上就是**任务从保存到再加载的过程就是一次上下文切换**。 - -### 什么是线程安全? -> 我的理解是:多个线程交替执行,本身是没有问题的,但是如果访问共享资源,结果可能会出现问题,于是就出现了线程不安全的问题。 -1. 访问共享变量或资源 -2. 依赖时序的操作 -3. 不同数据之间存在绑定关系 -4. 对方没有声明自己是线程安全的 - -### 线程周期 - -![](https://www.pdai.tech/_images/pics/ace830df-9919-48ca-91b5-60b193f593d2.png) - -- 线程创建之后它将处于`New`(新建)状态,调用 `start()` 方法后开始运行,线程这时候处于 `READY`(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 `RUNNING`(运行) 状态。 -- 当线程执行 `wait()`方法之后,线程进入 `WAITING`(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 `TIME_WAITING`(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 `TIMED WAITING` 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。 -- 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 `BLOCKED`(阻塞)状态。 -- 线程在执行 Runnable 的` run() `方法之后将会进入到 `TERMINATED`(终止) 状态。 - -#### 直接调用Thread的run方法不行吗? -**start** - -通过该方法启动线程的同时**也创建了一个线程,真正实现了多线程**。**无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码**。此时start()的这个线程处于**就绪状态**,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。 - -从源码当中可以看到: -当一个线程启动的时候,它的状态(threadStatus)被设置为0,如果不为0,则抛出`IllegalThreadStateException`异常。正常的话,将该**线程加入线程组**,最后尝试调用start0方法,**而start0方法是私有的native方法**(Native Method是一个java调用非java代码的接口)。 - -**run** - -通过run方法启动线程其实就是调用一个类中的方法,**当作普通的方法的方式调用**。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。 - -### wait/notify 和 sleep 方法的异同? -相同点: -1. 它们都可以让线程阻塞。 -2. 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。 - -不同点: -1. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。 -2. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。 -3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。 -4. wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。 - -### 4种创建方式 -Thread - -```java -public class Test extents Thread { - public void run() { - // 重写Thread的run方法 - System.out.println("dream"); - } - - public static void main(String[] args) { - new Test().start(); - } -} -``` -Runnable - -```java -public class Test { - public static void main(String[] args) { - new Thread(() -> { - System.out.println("dream"); - }).start(); - } -} -``` ---- - -源码: -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` -Callable -```java -public class Test { - public static void main(String[] args) { - // FutureTask 构造方法包装了Callable和Runnable。 - FutureTask task = new FutureTask<>(() -> { - System.out.println("dream"); - return 0; - }); - new Thread(task).start(); - } -} -``` ---- -源码: -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` -线程池 -```java -public class Test { - public static void main(String[] args) { - ExecutorService threadPool = Executors.newFixedThreadPool(1); - threadPool.submit(() -> { - System.out.println("dream"); - }); - threadPool.shutdown(); - } -} -``` - -### 死锁 -- **互斥条件**:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) -- **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) -- **不剥夺条件**:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) -- **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗) - ---- -```java -public class Test { - private static Object res1 = new Object(); - private static Object res2 = new Object(); - - public static void main(String[] args) { - new Thread(() -> { - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - // 延迟一下, 确保B拿到了res2 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - } - } - }, "ThreadA").start(); - - new Thread(() -> { - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - // 延迟一下,确保A拿到了res1 - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - } - } - }, "ThreadB").start(); - } -} -``` - -### 主线程等待子线程方式 -- sleep:好用是好用,但是缺点太明显,不可控的延迟 -- Thread.activeCount():缺点也明显,结合while一直判断 -- Join:值得考虑,但是不优雅 - -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - ArrayList threads = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - threads.add(new Thread(t1::m, "Thread " + i)); - } - threads.forEach(o -> o.start()); - threads.forEach(o -> { - try { - o.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - System.out.println("main thread"); - } -} -``` - -CountDownLatch - -```java -public class Test { - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - System.out.println(Thread.currentThread().getName()); - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(5); - Test t1 = new Test(countDownLatch); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - countDownLatch.await(); - System.out.println("main thread"); - } -} -``` - -### Java锁介绍 -#### 公平锁/非公平锁 -- 公平锁指多个线程按照**申请锁的顺序来获取锁**。 -- 非公平锁指多个线程获取锁的顺序**并不是按照申请锁的顺序**,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...)。 - -#### 可重入锁 -可重入锁又名递归锁,是指在同一个线程在**外层方法获取锁的时候,在进入内层方法会自动获取锁**,典型的synchronized,了解一下。 - -#### 独享锁/共享锁 -- 独享锁:是指该锁一次只能**被一个线程所持有**。 -- 共享锁:是该锁可**被多个线程所持有**。 - -#### 互斥锁/读写锁 -上面讲的独享锁/共享锁就是一种**广义的说法**,互斥锁/读写锁就是其具体的实现。 - -#### 乐观锁/悲观锁 -- 悲观锁认为对于同一个人数据的并发操作,**一定是会发生修改的,哪怕没有修改,也会认为修改**。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。**悲观的认为,不加锁的并发操作一定会出现问题**。 -- **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据**。乐观的认为,不加锁的并发操作时没有事情的。 -- **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** -- 悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁。 - -#### 分段锁 -- 分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。 -- 以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** -- 当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** -- **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** - -#### 偏向锁/轻量级锁/重量级锁 -- 这三种锁是锁的状态,并且是针对这synchronized。在Java5通过引入锁升级的机制来实现高效synchronized。这三种锁的状态是通过**对象监视器在对象头中的字段来表明的**。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。 -- 偏向锁的适用场景:**始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用**,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。 -- **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** -- **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** - -#### 自旋锁 -自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。 - -##### 手写自旋锁 -```java -public class SpinLock { - - // 原子引用线程 - AtomicReference atomicReference = new AtomicReference<>(); - - public void mylock() { - Thread thread = Thread.currentThread(); - System.out.println(Thread.currentThread().getName() + " como in..."); - while (!atomicReference.compareAndSet(null, thread)) { -// System.out.println("不爽,重新获取一次值瞧瞧..."); - } - } - - public void myUnlock() { - Thread thread = Thread.currentThread(); - atomicReference.compareAndSet(thread, null); - System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); - } - - public static void main(String[] args) { - SpinLock spinLock = new SpinLock(); - new Thread(() -> { - spinLock.mylock(); - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - spinLock.myUnlock(); - }, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - - new Thread(() -> { - spinLock.mylock(); - spinLock.myUnlock(); - }, "t2").start(); - } - -} -``` - -### volatile -#### 可见性 -> 问题所在:首先有一个这样的程序,首先有个变量isStart为true,然而有一个方法,该方法有个while(isStart)。也就是说,只要不改变isStart为false,此方法的while一直循环。倘若,我在主线程更改为false。那么理论上应该会while会停止,但是实际上个没有。此时要分析JMM - -分析:一开始isReady为true,m方法中的while会一直循环,而主线程开启开线程之后会延迟1s将isReady赋值为false,若不加volatile修饰,则程序一直在运行,若加了volatile修饰,则程序最后会输出t1 m end... - -**这里要聊一下JMM内存模型** -[https://www.processon.com/view/link/5e129d57e4b0da16bb11d127](https://www.processon.com/view/link/5e129d57e4b0da16bb11d127) - -#### 可序性 -```java -public class Test { - private volatile static Test instance = null; - private Test(){} - - private static Test getInstance() { - if (instance != null) { - synchronized (Test.class) { - if (instance != null) { - instance = new Test(); - } - } - } - return instance; - } -} -``` -上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为`instance = new Singleton();`这个初始化操作并不是原子的,在JVM上会对应下面的几条指令: - -```c -memory =allocate(); //1. 分配对象的内存空间 -ctorInstance(memory); //2. 初始化对象 -instance = memory; //3. 设置instance指向刚分配的内存地址 -``` -上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下: - -```c -memory =allocate(); //1. 分配对象的内存空间 -instance = memory; //3. 设置instance指向刚分配的内存地址 -ctorInstance(memory); //2. 初始化对象 -``` - -这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。 - -#### 不能保证原子性 -> 假设:我们这边有20个线程,每个线程分别执行10000次count++;那么最终全部执行完毕的结果理论上是200000,但实际运行却达不到。分析一波 - -- JMM都了解,暂时不说了 -- 当T1从主内存读取count拷贝到自己的工作内存为0,那么T2也是如此。 -- T1开始执行一次count++,那么,T1的count为1并挂起,T2也是如此执行count++,T2的count也是1。 -- T1唤醒,将count写入总内存,此时问题来了。 -- **此时会通过总线嗅探机制将T2的工作内存的count失效。也就意味着,T2虽然读取了内存的count为1,但是它终归丢失了一次操作呀,也就意味着这两次执行的count++,在总内存上理论上是2的,但实际上是1**。 - -分析图:[https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) - -#### 内存屏障(补充) -**Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的;为了实现volatile内存语义,JMM会分别限制重排序类型。** - -- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 -- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 -- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 - -**为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:** - -- 在每个volatile写操作的前面插入一个StoreStore屏障。 -- 在每个volatile写操作的后面插入一个StoreLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadStore屏障。 - -#### volatile汇编 -**可见其本质是通过一个lock指令来实现的。** - -**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - -- 锁住内存 -- 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 - - -### synchronized - -#### 修饰范围 -- 实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 -- 静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 -- 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁 - -#### 底层原理 -代码块 - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** - -方法 - -**synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。** - -#### 1.6版本的优化 -##### 1.6前 -在 Java 早期版本中,`synchronized` 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 **Mutex Lock** 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要**操作系统**帮忙完成,而操作系统实现线程之间的切换时需要从**用户态转换到内核态**,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。 - -##### 1.6后 -庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如**自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等**技术来减少锁操作的开销。 - - -##### 偏向锁 -**引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - -- 访问Mark Word中**偏向锁的标识是否设置成1**,**锁标识位是否为01**,确认偏向状态 -- 如果为可偏向状态,则判断**当前线程ID是否为偏向线程** -- 如果偏向线程未当前线程,则通过**cas操作竞争锁**,如果竞争成功则操作Mark Word中线程ID设置为当前线程ID -- 如果cas偏向锁获取失败,则挂起当前偏向锁线程,偏向锁升级为**轻量级锁** - -##### 轻量级锁 -倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** -**轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁! - -- 线程由偏向锁升级为轻量级锁时,会先把**锁的对象头MarkWord复制一份到线程的栈帧中,建立一个名为锁记录空间(Lock Record),用于存储当前Mark Word的拷贝**。 -- 虚拟机使用cas操作尝试将**对象的Mark Word指向Lock Record的指针,并将Lock record里的owner指针指对象的Mark Word**。 -- 如果cas操作成功,则该线程拥有了对象的轻量级锁。第二个线程cas自旋锁等待锁线程释放锁。 -- 如果多个线程竞争锁,轻量级锁要膨胀为**重量级锁**,**Mark Word中存储的就是指向重量级锁(互斥量)的指针**。其他等待线程进入阻塞状态。 - -##### 锁消除 -锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -##### 自旋锁和自适应锁 -**一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - -**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定**。 - -##### 锁粗化 -原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -##### 总升级过程 -- 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 -- 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1 -- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。 -- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁 -- 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 -- 如果自旋成功则依然处于轻量级状态。 -- 如果自旋失败,则升级为重量级锁。 - -#### 和ReentractLock对比 -- **两者都是可重入锁**:两者都是可重入锁。“可重入锁”概念是:**自己可以再次获取自己的内部锁**。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -- **synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**:synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -- **ReenTrantLock 比 synchronized 增加了一些高级功能** - 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 2. **可实现公平锁** - 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” - 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** - - -### CAS -> **我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了`CAS`,并且在调用getAndAddInt方法中,会有compareAndSwapInt方法 - -`compareAndSwapInt(obj, offset, expect, update)`比较清楚,**意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作**,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。 - -调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现。 - -#### ABA问题 -> 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。 - -**目前在JDK的atomic包里提供了一个类`AtomicStampedReference`来解决ABA问题。** - -说白了,就是版本号 - - -### AQS -> AQS 使用一个 int 成员变量来表示同步状态(state),通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。(重点) - -state - -```java -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} - -``` - -常用方法 - -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 -``` - -#### ReentrantLock -##### 构造方法 -```java -// 默认是非公平锁 -public ReentrantLock() { - sync = new NonfairSync(); -} -// 可选参数,是否公平 -public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); -} -``` - -##### 公平锁的实现 -```java -protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (!hasQueuedPredecessors() && // 判断队列是否轮到该线程 - compareAndSetState(0, acquires)) { // 利用cas更换状态 - setExclusiveOwnerThread(current); // 如果都ok,就设置独占锁 - return true; - } - } - else if (current == getExclusiveOwnerThread()) {// 判断当前独占锁是否还是当前线程 - int nextc = c + acquires;// 状态累加 - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); // 设置状态 - return true; - } // 否则false - return false; -} -``` - -##### 非公平锁的实现 -```java -final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (compareAndSetState(0, acquires)) {// 在这里... - setExclusiveOwnerThread(current); - return true; - } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) // overflow - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; -} -``` - -#### CountDownLatch -> CountDownLatch是共享锁的一种实现,它默认构造 `AQS` 的 `state` 值为 `count`。当线程使用`countDown`方法时,其实使用了`tryReleaseShared`方法以CAS的操作来减少`state`,直至`state`为0就代表所有的线程都调用了`countDown`方法。当调用`await`方法的时候,如果`state`不为0,就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入**阻塞队列Park,并自旋CAS判断state == 0**,直至最后一个线程调用了countDown,使得state == 0,于是阻塞的线程便判断成功,全部往下执行。 - -##### 构造方法 -```java -public CountDownLatch(int count) { - if (count < 0) throw new IllegalArgumentException("count < 0"); - this.sync = new Sync(count); // 这里设置state的次数 -} -``` -##### countDown -```java -public void countDown() { - sync.releaseShared(1); -} -``` - -##### tryReleaseShared -```java -protected boolean tryReleaseShared(int releases) { - // Decrement count; signal when transition to zero - for (;;) { - int c = getState(); - if (c == 0) - return false; - int nextc = c-1; // 每执行一次该方法,状态减一 - if (compareAndSetState(c, nextc)) - return nextc == 0; - } -} -``` - -##### 用法 -- 某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :`new CountDownLatch(n)`,每当一个任务线程执行完毕,就将计数器减 1 `countdownlatch.countDown()`,当计数器的值变为 0 时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 -- 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1)`,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。 -- 死锁检测:一个非常方便的使用场景是,你可以使用 n 个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 - - -#### CyclicBarrier -> CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - - -##### dowait -当调用 `CyclicBarrier` 对象调用 `await()` 方法时,实际上调用的是`dowait(false, 0L)`方法。 `await()` 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。 - -代码就不贴了。要看,就去jdk上看 - -总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,count 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 - -#### Semaphore -**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** - -### ThreadLocal -> 如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 - - -#### 原理 -```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() `方法。 - -```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` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -#### 内存泄漏 -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。 - - -### BlockingQueue -- ArrayBlockingQueue:由数组结构组成的有界阻塞队列. -- LinkedBlockingQueue:由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列. -- PriorityBlockingQueue:支持优先级排序(堆)的无界阻塞队列. -- DelayQueue:使用优先级队列实现的延迟无界阻塞队列. -- SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列. -每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 个人感觉是生产者生产一个元素,消费者必须消费,生产者才能继续生产。内部也维护了一个TransferQueue,其中部分操作是利用cas。 -- LinkedTransferQueue:基于链接节点的无界TransferQueue 。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。 -- LinkedBlockingDuque:由了解结构组成的双向阻塞队列. - -#### 常见方法 -- 抛出异常:add/remove -- 不抛出异常:offer/poll -- 阻塞:put/take -- 带时间:offer/poll - -### 生产者和消费者 -#### synchronized -```java -public class Test { - - private final LinkedList lists = new LinkedList<>(); - - public synchronized void put(String s) { - while (lists.size() != 0) { // 用while怕有存在虚拟唤醒线程 - // 满了, 不生产了 - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - this.notifyAll(); // 这里可是通知所有被挂起的线程,包括其他的生产者线程 - } - - public synchronized void get() { - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - } - - public static void main(String[] args) { - Test test = new Test(); - - // 启动消费者线程 - for (int i = 0; i < 5; i++) { - new Thread(test::get, "ConsA" + i).start(); - } - - // 启动生产者线程 - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - test.put("" + tempI); - }, "ProdA" + i).start(); - } - } -} -``` - -#### ReentrantLock -```java -public class Test { - - private LinkedList lists = new LinkedList<>(); - private Lock lock = new ReentrantLock(); - private Condition prod = lock.newCondition(); - private Condition cons = lock.newCondition(); - - public void put(String s) { - lock.lock(); - try { - // 1. 判断 - while (lists.size() != 0) { - // 只要队列有元素,就不生产了,就停会儿 - prod.await(); - } - // 2.干活 - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - // 3. 通知 - cons.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public void get() { - lock.lock(); - try { - // 1. 判断 - while (lists.size() == 0) { - // 队列为空,消费者肯定等待呀 - cons.await(); - } - // 2.干活 - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - // 3. 通知 - prod.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public static void main(String[] args) { - Test test = new Test(); - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - test.put(tempI + ""); - }, "ProdA" + i).start(); - } - for (int i = 0; i < 5; i++) { - new Thread(test::get, "ConsA" + i).start(); - } - } -} -``` -这里讲一下为什么用while,不用if? -假如,此时队列元素为空,那么消费者肯定都挂起来了哈。在挂起前通知了生产者线程去生产,那么,生产者产了一个之后唤醒消费者,所有消费者醒了以后,就一个消费者抢到锁,开始消费,当消费过后释放锁,其他消费者线程的某一个抢到锁之后,从唤醒处走代码,如果是if,往下走取元素发现队列空的,直接抛异常。如果是while的话,还会继续判断队列是否为空,空就挂起。不会抛异常。 - -#### BlockingQueue -```java -public class Test { - public static void main(String[] args) { - ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10); - // 生产者 - Runnable product = () -> { - while (true) { - try { - String s = "生产者:" + Thread.currentThread().getName() + " "+ new Object(); - System.out.println(s); - queue.put(s); - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - new Thread(product, "p1").start(); - new Thread(product, "p2").start(); - // 消费者 - Runnable consume = () -> { - while (true) { - try { - Object o = queue.take(); - System.out.println("消费者:" + Thread.currentThread().getName() + " " + o); - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - new Thread(consume, "c1").start(); - new Thread(consume, "c2").start(); - } -} -``` -利用 BlockingQueue 实现生产者消费者模式的代码。虽然代码非常简单,但实际上 ArrayBlockingQueue 已经在背后完成了很多工作,比如队列满了就去阻塞生产者线程,队列有空就去唤醒生产者线程等。 - -### 线程池 -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要要等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -#### FixedThreadPool -```java -/** - * 创建一个可重用固定数量线程的线程池 - */ -public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutor(nThreads, nThreads, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory); -} -``` - -**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。** - -- 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务; -- 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`; -- 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行; - -不推荐使用 - -**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :** - -- 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize; -- 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。 -- 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数; -- 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。 - -#### SingleThreadExecutor -```java -/** - *返回只有一个线程的线程池 - */ -public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory)); -} -``` - -和上面一个差不多,只不过core和max都被设置为1 - -#### CachedThreadPool -```java -/** - * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。 - */ -public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); -} -``` - -`CachedThreadPool` 的` corePoolSize` 被设置为空(0),`maximumPoolSize `被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -- 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2; -- 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成; - -#### ThreadPoolExecutor -- corePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量 -- maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数 -- keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间 -- TimeUnit:时间单位 -- BlockingQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中 -- ThreadFactory:线程工厂,用来创建线程,一般默认即可 -- RejectedExecutionHandler:拒绝策略 - -##### RejectedExecutionHandler -- AbortPolicy:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- DiscardPolicy:不处理新任务,直接丢弃掉。 -- DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。 - -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) - -### 线程池的线程数量怎么确定 -1. 一般来说,如果是CPU密集型应用,则线程池大小设置为N+1。 -2. 一般来说,如果是IO密集型应用,则线程池大小设置为2N+1。 -3. 在IO优化中,线程等待时间所占比例越高,需要越多线程,线程CPU时间所占比例越高,需要越少线程。这样的估算公式可能更适合:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 - -### 多线程题 -#### 一个多线程的问题,用三个线程,顺序打印字母A-Z,输出结果是1A 2B 3C 1D 2E…打印完毕最后输出一个Ok。 -```java -public class Test { - private static char c = 'A'; - private static int i = 0; - - public static void main(String[] args) { - Runnable r = new Runnable() { - @Override - public void run() { - synchronized (this) { - try { - int id = Integer.parseInt(Thread.currentThread().getName()); - while (i < 26) { - if (i % 3 == id - 1) { - System.out.println("线程id:" + id + " " + (char) c++); - i++; - notifyAll(); - } else { - wait(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - }; - new Thread(r, "1").start(); - new Thread(r, "2").start(); - new Thread(r, "3").start(); - } -} -``` - -#### 如何让线程顺序执行 -[https://blog.csdn.net/Evankaka/article/details/80800081](https://blog.csdn.net/Evankaka/article/details/80800081) - - -## JVM - -### 类文件结构 -```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文件被加载完成后,java虚拟机会将静态常量池里的内容转移到运行时常量池里,在静态常量池的**符号引用有一部分是会被转变为直接引用**的,比如说类的**静态方法或私有方法,实例构造方法,父类方法**,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的**一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的**。 - - -#### 字符串常量池 -字符串常量池的存在使JVM提高了性能和减少了内存开销。 - -- 每当我们使用字面量(String s=“1”;)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。 -- 每当我们使用关键字new(String s=new String(”1”);)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s。 - -#### 版本关系 - -##### 1.6 -- 静态常量池在Class文件中。 - -- 运行时常量池在Perm Gen区(也就是方法区)中。 - -- 字符串常量池在运行时常量池中。 - -##### 1.7 -- 静态常量池在Class文件中。 -- 运行时常量池依然在Perm Gen区(也就是方法区)中。在JDK7版本中,永久代的转移工作就已经开始了,将譬如符号引用转移到了native heap;字面量转移到了java heap;类的静态变量转移到了java heap。但是运行时常量池依然还存在,只是很多内容被转移,其只存着这些被转移的引用。 -- 字符串常量池被分配到了Java堆的主要部分。也就是字符串常量池从运行时常量池分离出来了。 - -##### 1.8 -- 静态常量池在Class文件中。 -- JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。同时永久代被移除,以元空间代替。元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。其主要用于存放一些元数据。 -- 字符串常量池存在于Java堆中。 - -### 类加载过程 -#### 加载 -类加载过程的第一步,主要完成下面3件事情: - -- 通过全类名获取定义此类的二进制字节流 -- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 -- 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口 - -**加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了**。 - -#### 验证 -- 文件格式验证:主要验证Class文件是否规范等。 -- 元数据验证:对字节码描述的信息语义分析等。 -- 字节码验证:确保语义是ok的。 -- 符号引用验证:确保解析动作能执行。 - -#### 准备 -**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: - -- 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 -- 这里所设置的初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字`public static final int value=111` ,那么准备阶段 value 的值就被复制为 111。 - -#### 解析 -解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。 - -#### 初始化 -初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 ` ()`方法的过程。 - -#### 类加载器 -- BootstrapClassLoader(启动类加载器):最顶层的加载类,由C++实现,负责加载 `%JAVA_HOME%/lib`目录下的jar包和类或者或被 `-Xbootclasspath`参数指定的路径中的所有类。 -- ExtensionClassLoader(扩展类加载器):主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。 -- AppClassLoader(应用程序类加载器) - -#### 双亲委派 -每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。 - -![ClassLoader](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS图片.png) - -#### 好处 -双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。 - -#### 什么情况下需要开始类加载过程的第一个阶段加载 -1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是: -2. 使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。 -3. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。 -4. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 -5. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。 - -### JVM内存 -#### 线程计数器 -- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 - -**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。** - -#### 虚拟机栈 -与程序计数器一样,Java 虚拟机栈也是线程**私有**的,它的生命周期和线程相同,描述的是 Java **方法执行的内存模型**,每次方法调用的数据都是通过栈传递的。 - -**Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** - -- 局部变量表 - - 8大基本类型 - - 对象引用:可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 操作数栈 -- 动态链接 -- 方法出口 - -- StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。 -- OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。 - -#### 本地方法栈 -和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 其他和Java虚拟机差不多的 - -#### 方法区 -方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。 - -JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。 - -#### 堆 -Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** - - -- 分为四区,分别为eden区、s0("From)区、s1("To")和tentired -- 在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。 -- 当Eden区满了的时候,minor GC触发 -- 经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收 -- 在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的相同存活的对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象(重点) -- 再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。 - -注意:逃逸分析 -[https://zhuanlan.zhihu.com/p/69136675](https://zhuanlan.zhihu.com/p/69136675) - -#### 元空间 - -#### 直接内存 -直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 - -#### 对象创建 -![对象创建的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) - -##### 类加载检查 -虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 - -##### 分配内存 -在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。 - -- 指针碰撞 - - 堆规整(没有内存碎片) - - 复制算法 - - GC:Serial、ParNew -- 空闲列表 - - 堆内存不规整的情况下 - - 虚拟机会维护一个列表,该列表会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块来划分给对象实例,最后更新列表激励 - - GC:CMS -- 并发问题 - - **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** - - **TLAB:** 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配 - -##### 初始化零值 -内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 - -##### 设置对象头 -初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 - -##### 指向init方法 -在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - -#### 内存布局 -##### 对象头 -**Hotspot 虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC 分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 - -##### 实例数据 -**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 - -##### 对齐填充 -**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -##### 对象的访问方式 -使用句柄 - -如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了**对象实例数据**与**类型数据**各自的具体地址信息; - -![](https://imgkr.cn-bj.ufileos.com/c4580bee-7ce3-43f8-991b-c28aed9b84c6.png) - -直接指针 - -如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。 - -![](https://imgkr.cn-bj.ufileos.com/75abc5ff-0eec-403a-b7ff-3dd25c7fd6e5.png) - -总结 - -这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是**稳定**的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 **reference 本身不需要修改**。使用直接指针访问方式最大的好处就是**速度快**,它节省了一次指针定位的时间开销。 - -### 垃圾回收 -#### 对象优先在Eden区分配 -大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。 - - -- **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。 -- **老年代 GC(Major GC/Full GC)**:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。 - -#### Minor GC和Full GC触发条件 -- Minor GC触发条件:当Eden区满时,触发Minor GC。 -- Full GC触发条件: - 1. 调用System.gc时,系统建议执行Full GC,但是不必然执行 - 2. 老年代空间不足 - 3. **方法区**空间不足 - 4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存 - 5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小 - -#### 大对象直接进入老年代 -大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。 - -**为什么要这样呢?**为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 - -#### 长期存活的对象进入老年代 -如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 - -#### 如何判断对象死亡 - -##### 引用计数法 -给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。 - -**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** - -##### 可达性分析 -这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。 - - -哪些可以作为GC Roots的根 - -- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中应用的对象。 -- 本地方法栈中JNI(native方法)引用的对象 -- 方法区中的类静态属性引用的对象 -- 方法区中常量引用的对象 - -[内存泄露](http://www.ityouknow.com/java/2019/05/23/memory-leak.html) - -#### 四大引用 -##### 强引用 -以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 - -##### 软引用 -如果一个对象只具有软引用。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 - -##### 弱引用 -弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 - - -##### 虚引用 -"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 - -**虚引用主要用来跟踪对象被垃圾回收的活动**。 - -#### 如何判断一个常量是废弃常量 -运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢? - -假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。 - -#### 如何判断一个类是无用的类 -判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 **“无用的类”** : - -- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 -- 加载该类的 ClassLoader 已经被回收。 -- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 - -虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 - -#### 垃圾回收算法 - -##### 标记-清除算法 -该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: - -- 效率问题 -- 空间问题(标记清除后会产生大量不连续的碎片) - - -##### 标记-整理算法 - -根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - - -##### 复制算法 - -为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - - -##### 分代收集 -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** - -#### 垃圾收集器 -##### Serial收集器 -它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 - -**新生代采用复制算法,老年代采用标记-整理算法。** - -虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 - -但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。 - -##### ParNew收集器 -**ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。** - - **新生代采用复制算法,老年代采用标记-整理算法。** - - -它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。 - -**并行和并发概念补充:** - -- **并行(Parallel)** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 -- **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。 - -##### Parallel Scavenge收集器 - -Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 - -**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。 - -**新生代采用复制算法,老年代采用标记-整理算法。** - -##### Serial Old 收集器 - -**Serial 收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。 - - -##### Parallel Old 收集器 - -**Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 - -##### CMS收集器 -**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。** - -**CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。** - -从名字中的**Mark Sweep**这两个词可以看出,CMS 收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤: - - -- 初始标记:暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; -- 并发标记:同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 -- 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 -- 并发清除:开启用户线程,同时 GC 线程开始对为标记的区域做清扫。 - -从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点: - -- **对 CPU 资源敏感;** -- **无法处理浮动垃圾;** -- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。** - -##### G1收集器 -- 并行与并发: G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU来缩短 Stop-The-World 停顿时间。 -- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。 -- 空间整合:G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。 -- 可预测停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型。 - -过程: -- 初始标记 -- 并发标记 -- 最终标记 -- 筛选回收 - -##### G1和CMS的比较 -1. CMS收集器是**获取最短回收停顿时间**为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。 -2. CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。 -3. CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留在下一次GC时将其清理掉。 -4. G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU或核心来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。 -5. 从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。 -6. G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。 -7. G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。 - - -### JVM锁优化和膨胀过程 -1. 自旋锁:自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。 -2. 锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。 -3. 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。 -4. 偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。 -5. 轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。 -6. 重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。 - - -### GC参数 -![解释](http://media.dreamcat.ink/uPic/iShot2020-05-26上午09.58.16.png) - - -## Spring -### 什么是Spring框架 -Spring 框架是有很多模块的集合,使用这些模块可以很方便地协助我们进行开发。 - -### 列举一些重要的Spring模块 -- **Spring Core:** 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。 -- **Spring AOP** :提供了面向切面的编程实现。 -- **Spring JDBC** : Java数据库连接。 -- **Spring Web** : 为创建Web应用程序提供支持。 -- **Spring Test** : 提供了对 JUnit 和 TestNG 测试的支持。 - - -### IoC -IoC(Inverse of Control:控制反转)是一种**设计思想**,就是 **将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。** - -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 **IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** - -#### 初始化流程 -- Resource资源定位:这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。 -- BeanDefinition的载入和解析:这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。 -- BeanDefinition注册 - - prepareRefresh():预备一下, 标记启动时间,上下文环境,我要的材料(beanDefinition)准备好了嘛? - - obtainFreshBeanFactory(): - - 如果已经有了BeanFactory就销毁它里面的单例Bean并关闭这个BeanFactory。 - - 创建一个新的BeanFactory。 - - 对这个BeanFactory进行定制(customize),如allowBeanDefinitionOverriding等参数 - - 转载BeanDefinitions(读取配置文件,将xml转换成对应得BeanDefinition) - - 检查是否同时启动了两个BeanFactory。 - - prepareBeanFactory(beanFactory):设置beanFactory的类加载器,材料(BeanDefinition)解析器等 - - postProcessBeanFactory(beanFactory): - - 设置beanFactory的后置处理器 - - 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - - invokeBeanFactoryPostProcessors(beanFactory): - - 调用beanFactory的后置处理器(BeanDefinitionRegisterPostProcessor和BeanFactoryPostProcessor) - - 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - - registerBeanPostProcessors(beanFactory): - - 注册 BeanPostProcessor 的实现类(bean的后置处理器) - - 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - - initMessageSource():对上下文中的消息源进行初始化 - - initApplicationEventMulticaster():初始化上下文的事件广播器 - - onRefresh():- 模版方法,具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - - registerListeners():注册事件监听器 - - finishBeanFactoryInitialization(beanFactory):初始化所有的 singleton beans - - finishRefresh():最后,广播事件,ApplicationContext 初始化完成 - -### AOP -AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。 - -- **Spring AOP就是基于动态代理的** -- 如果要代理的对象,实现了某个接口,那么Spring AOP会使用**JDK Proxy**, -- 而对于没有实现接口的对象,这时候Spring AOP会使用 **Cglib** 生成一个被代理对象的子类来作为代理。 - - -![](https://user-gold-cdn.xitu.io/2018/9/14/165d631e56799a5c?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -#### 初始化流程 -registerAspectJAnnotationAutoProxyCreatorIfNecessary - -- 第一句,注册一个AnnotationAwareAspectJAutoProxyCreator(称它为自动代理器),这个Creator是AOP的操作核心,也是扫描Bean,代理Bean的操作所在。 -- 第二句,解析配置元素,决定代理的模式。其中有JDK动态代理,还有CGLIB代理,这部分后续会再细讲。 -- 第三句,作为系统组件,把Creator这个Bean,放到Spring容器中。让Spring实例化,启动这个Creator。 - -总结: -- Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator,当作一个系统组件。 -- 当一个bean加载到Spring中时,会触发自动代理器中的bean后置处理,然后会先扫描bean中所有的Advisor -- 然后用这些Adviosr和其他参数构建ProxyFactory -- ProxyFactory会根据配置和目标对象的类型寻找代理的方式(JDK动态代理或CGLIG代理) -- 然后代理出来的对象放回context中,完成Spring AOP代理 - -### bean - -#### bean的作用域 -- singleton 作用域:表示在 Spring 容器中只有一个 Bean 实例,以单例的形式存在,是默认的 Bean 作用域。 -- prototype 作用域:原型作用域,每次调用 Bean 时都会创建一个新实例,也就是说每次调用 getBean() 方法时,相当于执行了 new Bean()。 -- request 作用域:每次 Http 请求时都会创建一个新的 Bean,该作用域仅适应于 WebApplicationContext 环境。 -- session 作用域:同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。 -- application 作用域:全局的 Web 作用域,类似于 Servlet 中的 Application。 - - -#### 什么是三级缓存 -1. 第一级缓存:单例缓存池singletonObjects。 -2. 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化) -3. 第三级缓存:singletonFactories单例对象工厂缓存。 - -#### 创建Bean的整个过程 -1. 首先finishBeanFactoryInitialization->preInstantiateSingletons->getBean->doGetBean; -2. 在doGetBean中,transformedBeanName:主要负责判断一下有木有别名;getSingleton:从一级缓存singletonObjects拿bean,在getSingleton方法中,有一个判断条件就是isSingletonCurrentlyInCreation,判断为false,因为他是第一次进来,并且还没有正在创建该bean;dependsOn:依赖,暂时先不说他。 -3. 再来一次getSingleton:再一次的从singketonObjects缓存拿,依然没有的。接着有个重点beforeSingletonCreation:它把bean添加到临时的singletonsCurrentlyInCreation,这就意味着,下次再碰见它,那可就是true了。接着singletonFactory.getObject(),这里getObject调用的是传递的接口createBean方法。 -4. 在createBean方法中:有个doCreateBean->createBeanInstance方法:它就是直接实例化,实际上构造器有反应了(区分JVM创建对象和Spring创建对象),但是没有赋值(初始化);earlySingletonExposure:提前暴漏该bean。但要知道三个变量,为什么他是true:isSingleton(),是否单例,那肯定是哦;(这里解释了这里是单例才能提前曝漏,意味着才能存三级缓存)allowCircularReferences,默认变量为true,写死了;isSingletonCurrentlyInCreation,这里可就为true了,因为步骤3,已经将它设置为true了。那么会进来这个方法:addSingletonFactory -5. addSingletonFactory在这个方法中:将该bean放入到三级缓存singletonFactories中。(解决循环依赖) -6. 接下来,就是它了,populateBean:实际上就是属性赋值。(如果这里要有A依赖B,又发现三级缓存中没有B,那么它就会再次执行一次(递归开始)getBean->doGetBean->createBeanInstance(把B给实例化一下),同样的道理,这里会将B也会放入三级缓存中,B开始populateBean,那么它发现B依赖A,此时三级缓存中有A(精髓,牛逼),然后把A放到二级缓存中,同时从三级缓存中移除,接着得到A之后直接赋值,最后完成了初始化,然后来到addSingleton,将B仍到了一级缓存,同时将B从三级缓存仍出去)返回B,递归结束,得到B之后将B的赋值给A了。 -7. 最后将二级缓存的A删除,仍到一级缓存中。 - - -#### Spring的单例有线程安全问题吗 -大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 - -常见的有两种解决办法: - -- 在Bean对象中尽量避免定义可变的成员变量(不太现实)。 -- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。 - - -#### bean的生命周期 -![bean周期](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg) - -- Bean 容器找到配置文件中 Spring Bean 的定义。 -- Bean 容器利用 Java Reflection API 创建一个Bean的实例。 -- 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。 -- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。 -- 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。 -- 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。 -- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法 -- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。 -- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。 -- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法 -- 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。 -- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。 - -### SpringMVC -![springmvc工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) - -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使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。 -- **代理设计模式** : Spring AOP 功能的实现。 -- **单例设计模式** : Spring 中的 Bean 默认都是单例的。 -- **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 -- **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 -- **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。 -- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。 -- ...... - - -### Spring如何解决循环依赖问题 -Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。 - -### BeanFactory和ApplicationContext的区别 -1. BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。 -2. ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。 -3. BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化。ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化 - -### Spring的后置处理器 -1. BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。 -2. InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。 -3. BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。 -4. BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。 - -### Spring事务 -#### 隔离级别 -- **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的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 - -#### @Transactional(rollbackFor = Exception.class)注解了解吗 -1. @Transactional注解只能应用到public修饰符上,其它修饰符不起作用,但不报错。 -2. 默认情况下此注解会对unchecked异常进行回滚,对checked异常不回滚。 - -> checked异常:表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。unchecked异常:表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。 - -不回滚解决方案: -1. 检查方法是不是public -2. 检查异常是不是unchecked异常 -3. 如果是checked异常也想回滚的话,注解上写明异常类型即可@Transactional(rollbackFor=Exception.class) - -事务失效的8大原因: -1. 数据库引擎不支持事务 -2. 没有被 Spring 管理 -3. 方法不是 public 的 -4. 自身调用问题 -5. 数据源没有配置事务管理器 -6. 不支持事务(传播机制) -7. 异常被吃了(捕获异常) -8. 异常类型错误(checked异常失效) - - -#### Spring的的事务传播机制 -1. **required**(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 -2. **requires_new**:创建一个新事务,如果当前事务存在,把当前事务挂起。 -3. **supports**:支持使用当前事务,如果当前事务不存在,则不使用事务。 -4. **not_supported**:无事务执行,如果当前事务存在,把当前事务挂起。 -5. **mandatory**:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 -6. **never**:无事务执行,如果当前有事务则抛出Exception。 -7. **nested**:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 - -#### 事务源码 -- 开启@EnableTransactionManagement -- 利用TransactionManagementConfigurationSelector给容器中会导入组件 - - AutoProxyRegistrar - - 给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件 - - 利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用; - - ProxyTransactionManagementConfiguration(给容器中注册事务增强器) - - 事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解 - - 事务拦截器 - -### Springboot -#### SpringBootApplication的注解 -[https://www.jianshu.com/p/943650ab7dfd](https://www.jianshu.com/p/943650ab7dfd) - -- @SpringBootConfiguration:允许在上下文中注册额外的bean或导入其他配置类 -- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制 -- @ComponentScan: 扫描常用的注解 - -其中 @EnableAutoConfiguration 是实现自动配置的入口,该注解又通过 @Import 注解导入了AutoConfigurationImportSelector,在该类中加载 META-INF/spring.factories 的配置信息。然后筛选出以 EnableAutoConfiguration 为 key 的数据,加载到 IOC 容器中,实现自动配置功能! - -#### 源码过程 -- 创建计时器StopWatch -- 获取SpringApplicationRunListeners并启动 -- 创建ApplicationArguments -- 创建并初始化ConfigurableEnvironment -- 打印Banner -- 创建ConfigurableApplicationContext -- 准备ConfigurableApplicationContext -- 刷新ConfigurableApplicationContext,**这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看** -- 容器刷新后动作,啥都没做 -- 计时器停止计时 - -**总结:run() 方法主要调用了spring容器启动方法扫描配置,加载bean到spring容器中;启动的内置Web容器** - - -# 数据库 - -## MySQL - -### SQL执行顺序 -SQL的执行顺序:from---where--group by---having---select---order by - -### MySQL是如何执行一条SQL的 -![执行过程图](http://media.dreamcat.ink/uPic/SQL%E6%89%A7%E8%A1%8C%E7%9A%84%E5%85%A8%E9%83%A8%E8%BF%87%E7%A8%8B.png) - -**MySQL内部可以分为服务层和存储引擎层两部分:** - -1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 -2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 - -**Server层按顺序执行sql的步骤为**: -客户端请求: -- **连接器**(验证用户身份,给予权限) -- **查询缓存**(存在缓存则直接返回,不存在则执行后续操作) -- **分析器**(对SQL进行词法分析和语法分析操作) -- **优化器**(主要对执行的sql优化选择最优的执行方案方法) -- **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) -- **去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) - -### 数据库引Innodb与MyISAM的区别 -#### Innodb -- 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 -- 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ (Next-Key Locking)**防止幻影读**。 -- 主索引是**聚簇索引**,在**索引中保存了数据**,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 -- 支持真正的在**线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -#### MyISAM -- 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括**压缩表、空间数据索引**等。 -- **不支持事务**。 -- **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -#### 简单对比 -- **事务**: InnoDB 是事务型的,可以使用 `Commit` 和 `Rollback` 语句。 -- **并发**: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -- **外键**: InnoDB 支持外键。 -- **备份**: InnoDB 支持在线热备份。 -- **崩溃恢复**: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- **其它特性**: MyISAM 支持压缩表和空间数据索引。 - - -#### MySQL的ACID原理 -##### 原子性(Atomicity) -根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态! - -**如果无法保证原子性会怎么样?** - -OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~ - -##### 一致性(Consistency) -根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!** - ---- -**如果无法保证一致性会怎么样?** - -- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。 -- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。 - -##### 隔离性(Isolation) -根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 - -**如果无法保证隔离性会怎么样?** - -OK,假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,会出现下面的情形 - -##### 持久性(Durability) -根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。 - -**如果无法保证持久性会怎么样?** - -在MySQL中,为了解决CPU和磁盘速度不一致问题,MySQL是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。 - -设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是**数据不一致**的情形。 - -##### 保证一致性 -OK,这个问题分为两个层面来说。 - -- **从数据库层面**,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。**数据库必须要实现AID三大特性,才有可能实现一致性**。例如,原子性无法保证,显然一致性也无法保证。 -- **从应用层面**,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据! - - -##### 保证原子性 -OK,是利用Innodb的**undo log**。 **undo log**名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如 - -- 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据 -- 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作 -- 当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作 - -**undo log**记录了这些回滚需要的信息,当事务执行失败或调用了**rollback**,导致事务需要回滚,便可以利用**undo log**中的信息将数据回滚到修改之前的样子。 - - -##### 保证持久性 -OK,是利用Innodb的**redo log**。 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题? - -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用**redo log**解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在**redo log**中记录这次操作。当事务提交的时候,会将**redo log**日志进行刷盘(**redo log**一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据**undo log**和**binlog**内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将**redo log**进行刷盘比对数据页刷盘效率高,具体表现如下: - -- **redo log**体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- **redo log**是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -##### 保证隔离性 -**利用的是锁和MVCC机制。** - -### 并发事务带来的问题 -#### 脏读 - -第一个事务首先读取var变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取var为100,此时第一个事务做了回滚。最终第二个事务读取的var和数据库的var不一样。 - - -#### 丢弃修改 - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 - - -#### 不可重复读 - -T2 读取一个数据,T1 对该数据做了修改并提交。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -#### 幻读 - -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条,这样就导致了幻读。 - -### 数据库的隔离级别 -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ**(可重读) - -**这里需要注意的是**:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是**Next-Key Lock 锁**算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 - -InnoDB 存储引擎在分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 - -#### 未提交读 -事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** - -#### 提交读 -对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** - -#### 可重复读 -就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** - -#### 可串行读 -在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** - - -### 索引 -#### 索引类型 -- FULLTEXT:即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- HASH:由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- BTREE:BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- RTREE:RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 - - -#### 索引种类 -- 普通索引:仅加速查询 -- 唯一索引:加速查询 + 列值唯一(可以有null) -- 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 -- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- 全文索引:对文本的内容进行分词,进行搜索 -- 索引合并:使用多个单列索引组合搜索 -- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 -- 聚簇索引:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) - -#### 查询在什么时候不走(预期中的)索引 -1. 模糊查询 %like -2. 索引列参与计算,使用了函数 -3. 非最左前缀顺序 -4. where对null判断 -5. where不等于 -6. or操作有至少一个字段没有索引 -7. 需要回表的查询结果集过大(超过配置的范围) -8. - **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** - -#### 索引结构 -**MyISAM**: -1. MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址,同样使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址 -2. 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 -3. MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录 - -**InnoDB**: -1. InnoDB的数据文件本身就是索引文件,这棵树的叶节点data域保存了完整的数据记录(聚集索引) -2. InnoDB的辅助索引data域存储相应记录主键的值而不是地址 -3. 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。 - -#### 索引最左原则 -**举例子**: -如果索引列分别为A,B,C,顺序也是A,B,C: - -- 那么查询的时候,如果查询【A】【A,B】 【A,B,C】,那么可以通过索引查询 -- 如果查询的时候,采用【A,C】,那么C这个虽然是索引,但是由于中间缺失了B,因此C这个索引是用不到的,只能用到A索引 -- 如果查询的时候,采用【B】 【B,C】 【C】,由于没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了 -- 如果查询的时候,采用范围查询,并且是最左前缀,也就是第一列索引,那么可以用到索引,但是范围后面的列无法用到索引 - -#### 为什么使用索引 -- 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 -- 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 -- 帮助服务器**避免排序和临时表**。 -- 将**随机IO变为顺序IO**。 -- 可以**加速表和表之间的连接**,特别是在实现数据的参考完整性方面特别有意义。 - -缺点 - -- 当对表中的数据进行增加、删除和修改的时候,**索引也要动态的维护**,这样就降低了数据的维护速度。 -- 索引需要**占物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 -- **创建索引和维护索引要耗费时间**,这种时间随着数据量的增加而增加 - - -#### 注意事项(优化) -- 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 -- 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间 -- 在中到大型表索引都是非常有效的,但是特大型表的维护开销会很大,不适合建索引 -- 在经常用到连续的列上,这些列主要是由一些外键,可以加快连接的速度 -- 避免where子句中对字段施加函数,这会造成无法命中索引 -- 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 -- **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** -- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 -- 在使用limit offset查询缓存时,可以借助索引来提高性能。 - -### 慢查询优化 -1. 先运行看看是否真的很慢,注意设置SQL_NO_CACHE -2. where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高 -3. explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询) -4. order by limit 形式的sql语句让排序的表优先查 -5. 了解业务方使用场景 -6. 加索引时参照建索引的几大原则 -7. 观察结果,不符合预期继续从0分析 - -### 聊聊Explain -> 常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看 - -1. id : 表示SQL执行的顺序的标识,SQL从大到小的执行 -2. select_type:表示查询中每个select子句的类型 -3. table:显示这一行的数据是关于哪张表的,有时不是真实的表名字 -4. type:表示MySQL在表中找到所需行的方式,又称“访问类型”。常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好) -5. possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用 -6. Key:key列显示MySQL实际决定使用的键(索引),如果没有选择索引,键是NULL。 -7. key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的) -8. ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值 -9. rows: 表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,理论上行数越少,查询性能越好 -10. Extra:该列包含MySQL解决查询的详细信息 - -> 结合项目的SQL语句优化讲解会更好 - -#### (SQL调优)Explain - -> 分别对应: -> -> id select_type table type possible_keys key key_len ref rows Extra -> -> 参考:[https://juejin.im/post/5ec4e4a5e51d45786973b357](https://juejin.im/post/5ec4e4a5e51d45786973b357) - -##### checkUsername - -```sql -SELECT - * -FROM - sb_user su -WHERE - user_name = 'mai'; -``` - -- 不加索引 - -```sql -1 "SIMPLE" "su" "ALL" NULL NULL NULL NULL 11 "Using where" -``` - -- 唯一索引 - -```sql -1 "SIMPLE" "su" "const" "user_name" "user_name" "152" "const" 1 "" -``` - -- 普通索引 - -```sql -1 "SIMPLE" "su" "ref" "user_name" "user_name" "152" "const" 1 "Using index condition" -``` - -##### getCount - -```sql -SELECT - sc.uuid, - sc.begin_date, - sc.begin_time, - sc.bus_id, - sc.bus_status, - sc.seat_status -FROM - sb_count sc -WHERE - sc.bus_status = '1' - AND sc.begin_date = '2020-06-03' - AND sc.begin_time >= '14:00'; -``` - -- 不加索引 - -```sql -1 "SIMPLE" "sc" "ALL" NULL NULL NULL NULL 1293 "Using where" -``` - -- 给bus_status加普通索引 - -```sql -1 "SIMPLE" "sc" "ref" "bus_status" "bus_status" "152" "const" 639 "Using index condition; Using where" -``` - -- 给begin_date加普通索引 - -```sql -1 "SIMPLE" "sc" "ref" "bus_status,begin_date" "begin_date" "402" "const" 17 "Using index condition; Using where" -``` - -- 给bus_status,begin_date加联合索引 -```sql -1 "SIMPLE" "sc" "ref" "bus_status_date" "bus_status,begin_date" "554" "const,const" 8 "Using index condition; Using where" -``` - -- 给bus_status,begin_date,begin_time加联合索引 - -```sql -1 "SIMPLE" "sc" "range" "bus_status_date_time" "bus_status,begin_date,bgin_time" "706" NULL 4 "Using index condition" -``` - - -##### getNoTakeOrdersById - -```sql -SELECT - so.uuid, - so.bus_status, - so.seats_ids, - so.order_user, - so.order_status, - so.order_time, - sc.bus_id, - CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time -FROM - sb_order so - LEFT JOIN sb_count sc ON so.count_id = sc.uuid -WHERE - so.user_id = '4' - AND so.order_status = '1' - AND sc.begin_date >= '2020-06-03' - AND sc.begin_time >= '15:00'; -``` - -- 不加普通索引 - -``` -1 "SIMPLE" "so" "ALL" NULL NULL NULL NULL 59 "Using where" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -- user_id+order_status加联合索引 - -``` -1 "SIMPLE" "so" "ref" "user_id_order_status" "user_id,order_status" "160" "const,const" 10 "Using index condition" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -### 数据库结构优化 - -- 范式优化:比如消除冗余(节省空间。。) -- 反范式优化:比如适当加冗余等(减少join) -- 限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- 读/写分离:经典的数据库拆分方案,主库负责写,从库负责读; -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 - -案例: -- 案例: 简单购物系统暂设涉及如下表: -- 1.产品表(数据量10w,稳定) -- 2.订单表(数据量200w,且有增长趋势) -- 3.用户表 (数据量100w,且有增长趋势) -- 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 - -垂直拆分: - -- 解决问题:表与表之间的io竞争 -- 不解决问题:单表中数据量增长出现的压力 -- 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - -水平拆分: - -- 解决问题:单表中数据量增长出现的压力 -- 不解决问题:表与表之间的io争夺 - -总结: - -方案:**用户表** 通过性别拆分为男用户表和女用户表,**订单表** 通过已完成和完成中拆分为已完成订单和未完成订单,**产品表** 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。 - - -### 三范式 -#### 第一范式 -所谓第一范式是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -#### 第二范式 -第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,**第二范式就是非主属性非部分依赖于主关键字**。 - -#### 第三范式 -简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,**存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。**简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 读写分离原理 -主库(master)将变更写**binlog**日志,然后从库(slave)连接到主库之后,从库有一个**IO线程**,将主库的binlog日志**拷贝到自己本地**,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。 - -这里有一个非常重要的一点,就是从库同步主库数据的过程是**串行化**的,也就是说**主库上并行**的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 - -而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 - -所以mysql实际上在这一块有两个机制,一个是**半同步复制**,用来解决主库数据丢失问题;一个是**并行复制**,用来解决主从同步延时问题。 - -所谓并行复制,指的是从库**开启多个线程,并行读取relay log中不同库的日志**,然后并行重放不同库的日志,这是库级别的并行。 - - -![](https://static01.imgkr.com/temp/11238363926347a680ce5f7ef6ecbacf.png) - - -### MVCC -#### 快照读 -一个是行的创建版本,一个是行的删除(过期)版本。具体的版本号(trx_id)存在 information_schema.INNODB_TRX 表中。版本号(trx_id)随着每次事务的开启自增。 - -事务每次取数据的时候都会取创建版本小于当前事务版本的数据,以及过期版本大于当前版本的数据。 - - -**普通的 select 就是快照读**。 - -InnoDB的实现 - -1. 事务以**排他锁**的形式修改原始数据 -2. 把修改前的数据存放于**undo log**,通过回滚指针与主数据关联 -3. 修改成功(commit),数据放到**redo log**中,失败则恢复**undo log**中的数据(rollback) - -在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。 -innodb在快照读的情况下并没有真正的避免幻读, 但是在当前读的情况下避免了不可重复读和幻读!!! - -#### 当前读 - -- select ... lock in share mode -- select ... for update -- insert -- update -- delete - -next-key 锁包含两部分: - -- 记录锁(行锁) -- 间隙锁 - -记录锁是加在索引上的锁,间隙锁是加在索引之间的。 - -原理:将当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取的数据是一致的。 - - -#### 缺点 -MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。 要知道的,MVCC机制下,会在更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本。 而undo log这个关键的东西,记载的内容是串行化的结果,记录了多个事务的过程,不属于多版本共存。 这么一看,似乎mysql的mvcc也并没有所谓的多版本共存 - - -### 红黑树、b和b+ -- Hash索引:Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描 -- 二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。 -- 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。 -- 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。 -- B+树:在B树的基础上,将非叶节点改造为不存储数据纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。 - -#### 红黑树(补充) -- 每个结点要么是红的要么是黑的。(红或黑) -- 根结点是黑的。 (根黑) -- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 (叶黑) -- 如果一个结点是红的,那么它的两个儿子都是黑的。 (红子黑) -- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。(路径下黑相同) - -#### b(补充) -B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让**查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成**。B树,概括来说是一个**一般化的二叉查找树**(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。其中,概念较为复杂,给个简单的图理解: - - -- 关键字集合分布在整颗树中; -- 任何一个关键字出现且只出现在一个结点中; -- 搜索有可能在非叶子结点结束; -- 其搜索性能等价于在关键字全集内做一次二分查找; - -#### b+(补充) - -- 在B树基础上,为**叶子结点增加链表指针**(B树+叶子有序链表),所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -- B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 - -### 持久性 -OK,是利用Innodb的**redo log**。 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题? - -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用**redo log**解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在**redo log**中记录这次操作。当事务提交的时候,会将**redo log**日志进行刷盘(**redo log**一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据**undo log**和**binlog**内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将**redo log**进行刷盘比对数据页刷盘效率高,具体表现如下: - -- **redo log**体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- **redo log**是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -### MVCC,redolog,undolog,binlog -- undoLog 也就是我们常说的**回滚日志文件** 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事务用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 -- redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。 -- MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。 -- binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。 - -#### binlog和redolog的区别 -1. redolog是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层服务层产生的。 -2. 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。 -3. 两种日志与记录写入磁盘的时间点不同,binlog日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。 -4. binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用。 -5. binlog可以作为恢复数据使用,主从复制搭建,redolog作为异常宕机或者介质故障后的数据恢复使用。 - - -### left join right join inner join -- left join(左联接):返回左表中的所有记录以及和右表中的联接字段相等的记录。 -- right join(右联接):返回右表中的所有记录以及和左表中的联接字段相等的记录。 -- inner join(等值联接):只返回两个表中联接字段相等的记录。 - -### MySQl各种锁 -[https://blog.csdn.net/Jack__Frost/article/details/73347688](https://blog.csdn.net/Jack__Frost/article/details/73347688) - -## Redis - -### Redis是什么 -简单来说redis就是一个**数据库**,不过与传统数据库不同的是redis的数据库是存在**内存**中,所以**读写速度非常快**,因此redis被广泛应用于**缓存**方向。另外,redis也经常用来做**分布式锁**,redis提供了多种数据类型来支持不同的业务场景。除此之外,**redis 支持事务** 、**持久化**、**LUA脚本**、**LRU驱动事件**、**多种集群**方案。 - -### 为什么要用Redis? -1. 缓存 -2. 共享Session -3. 消息队列系统 -4. 分布式锁 - - -### 单线程的Redis为什么这么快 -1. 纯内存操作 -2. 单线程操作,避免了**频繁的上下文切换** -3. 合理高效的数据结构 -4. 采用了**非阻塞I/O多路复用**机制 - - -### 为什么要用Redis而不用map/guava做缓存 - -- 缓存分为**本地缓存**和**分布式缓存**。以 Java 为例,使用自带的 **map 或者 guava 实现的是本地缓存**,最主要的特点是**轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。** - -- 使用 **Redis 或 Memcached 之类的称为分布式缓存**,在多实例的情况下,各实例共用一份缓存数据,**缓存具有一致 性**。缺点是需要保持 **Redis 或 Memcached服务的高可用**,整个程序架构上较为复杂。 - - -### Redis相比Memcached有哪些优势 - -1. 存储方式上:memcache会把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。redis有部分数据存在硬盘上,这样能保证数据的持久性。 -2. 数据支持类型上:memcache对数据类型的支持简单,只支持简单的key-value,,而redis支持五种数据类型。 -3. 用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 -4. value的大小:redis可以达到1GB,而memcache只有1MB。 - -### Redis的线程模型 -redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是**单线程**的,所以 redis 才叫做**单线程的模型**。它采用 **IO 多路复用机制**同时监听多个 socket,根据 socket 上的事件来**选择对应的事件处理器**进行处理。 - -文件事件处理器的结构包含 4 个部分: - -- 多个 socket -- IO多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个 socket 可能会**并发产生不同的操作**,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的**事件放入队列中排队**,事件分派器每次从队列中取出一个事件,把该事件交给对应的**事件处理器**进行处理。 - -### Redis的LRU具体实现 -- 传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select的时候大量非热点数据占领头部数据,所以需要改进。 -- Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。 -- Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。 -- 在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的key,lru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的(最近被访问)一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。 - -### Redis常见数据结构以及使用场景分析 - -#### String -String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。 - -#### Hash -Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。 - -#### List -list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表, 消息列表等功能都可以用Redis的 list 结构来实现。 - -Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 - -另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 - -#### Set -set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 - -当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 - -比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:`sinterstore key1 key2 key3`将交集存在key1内 - - -#### Zset -和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 - -举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 - -### Redis设置过期时间 -定期删除+惰性删除 - -- 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! -- 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! - -如果定期删除漏掉了很多过期 key,然后你也没及时去查, 也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? - -**redis 内存淘汰机制。** - -### Mysql有2000万数据,redis只存20万,如何保证redis中的数据都是热点数据 -redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略: - -- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 -- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 -- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 -- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 -- no-enviction(驱逐):禁止驱逐数据 - -### 持久化 -#### RDB -**RDB**是一种**快照存储持久化**方式,具体就是将`Redis`某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为`dump.rdb`,而在`Redis`服务器启动时,会重新加载`dump.rdb`文件的数据到内存当中恢复数据。 - -特点: -1. RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据 -2. SAVE命令有服务器进程直接执行保存操作,所以该命令会阻塞服务器 -3. BGSAVE命令由子进程执行保存操作,所有该命令不会阻塞服务器 -4. RDB文件是一个经过压缩的二进制文件 - -![](https://static01.imgkr.com/temp/26e5e31351b44a0a8222014c3a6549de.png) - -#### AOF(append-only file) -**AOF**:把所有的**对Redis的服务器进行修改的命令都存到一个文件里,命令的集合**。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。 - -特点: -1. AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态 -2. 命令请求会先保存到AOF缓存区里面,之后再定期写入并同步到AOF文件 -3. 在执行bg rewrite aof命令的时候,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。 - - -![](https://static01.imgkr.com/temp/dd82cf8b9bba4a75900931618b07a75a.png) - - -### Redis的事务 -MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务的基础。 - -事务可以一次执行多个命令, 并且带有以下两个重要的保证: -- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 -- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 - - -#### 为什么 Redis 不支持回滚(roll back) -- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。 -- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 - -### Redis的同步机制了解吗? -- 第一次同步时,**主节点做一次bgsave**,并同时将后续修改操作记录到**内存buffer**,待完成后**将rdb文件全量同步到复制节点**,复制节点接受完成后**将rdb镜像加载到内存**。 -- 加载完成后,再通知主节点**将期间修改的操作记录同步到复制节点进行重放**就完成了同步过程。 - -### Redis的集群 -- Redis Sentinel着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 - -- Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 - -### 缓存穿透 -一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量 请求而崩掉。 - -1. 在接口做校验 -2. 存null值(缓存击穿加锁) -3. 布隆过滤器拦截: 将所有可能的查询key 先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。 - -### 缓存雪崩 -缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 - -1. 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉 -2. 缓存时间不一致,给缓存的失效时间,加上一个随机值,避免集体失效 -3. 限流降级策略:有一定的备案,比如个性推荐服务不可用了,换成热点数据推荐服务 - -### 深究Redis的底层结构 -#### 字符串SDS -- 获取字符串长度的复杂度为O(1)。 -- 杜绝缓冲区溢出。 -- 减少修改字符串长度时所需要的内存重分配次数。 -- 二进制安全。 -- 兼容部分C字符串函数。 - -#### 链表list -- 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。 -- 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。 -- 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。 -- 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。 -- 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。 - -#### 字典hash -- 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 -- Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 -- Redis使用MurmurHash2算法来计算键的哈希值。 -- 哈希表使用链地址法来解决键冲突。 - -注意:这里和Java的HashMap不同的rehash过程 -1. Redis的rehash过程是扩展和收缩,而且还是渐进式的rehash -2. Redis的字典有两个哈希表ht[0]和ht[1] -3. 为字典的ht[1]哈希表分配空间,如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used *2的2^n;如果执行的是收缩操作,那么ht[1]的大小第一个大于等于ht[0].used的2^n。(举个例子,ht[0]的长度为10,那么扩展就是2^5的32,如果是压缩的话2^4=16) -4. 如果ht[0]的键值非常多的话,一次性转移过去,是一个非常耗时的操作哦,因此并非一次性,采取渐进式rehash转移。 - -#### 跳跃表 -- 跳跃表是有序集合的底层实现之一 -- Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点 -- 每个跳跃表节点的层高都是1至32之间的随机数 -- 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。 -- 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。 - ---- -![](https://imgkr.cn-bj.ufileos.com/70696936-10cd-43af-aa86-39dc6fdeda18.png) - -![](https://imgkr.cn-bj.ufileos.com/932b42df-35a4-4e19-bf08-5200daeb3f38.png) - -#### 压缩列表 -一看名字,就是为了节省内存造的列表结构。压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 - -### Redis并发竞争key的解决方案 -1. 分布式锁+时间戳 -2. 利用消息队列 - -### Redis与MySQL双写一致性方案 -先更新数据库,再删缓存。数据库的读操作的速度远快于写操作的,所以脏数据很难出现。可以对异步延时删除策略,保证读请求完成以后,再进行删除操作。 -[https://www.cnblogs.com/rjzheng/p/9041659.html](https://www.cnblogs.com/rjzheng/p/9041659.html) - -### Redis分布式锁 -- [Redis锁的实现](https://juejin.im/post/5cc165816fb9a03202221dd5#heading-4) -- [补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) - -### Redis的管道pipeline -对于单线程阻塞式的Redis,Pipeline可以满足批量的操作,把多个命令连续的发送给Redis Server,然后一一解析响应结果。Pipelining可以提高批量处理性能,提升的原因主要是TCP连接中减少了“交互往返”的时间。pipeline 底层是通过把所有的操作封装成流,redis有定义自己的出入输出流。在 sync() 方法执行操作,每次请求放在队列里面,解析响应包。 - -### 项目哪里用到 -- 缓存,那肯定没得说,要说就说亮点。 -- Redis绑定Token,达到SSO的目的。(SSO:用户只需要登录一次,就可以访问其他子服务,比如订单服务,支付服务等) -- Redis作为延迟队列 - 1. 定时主动轮询Redis的延迟队列(底层原理)去修改场次状态变化(沙河->清水河四种状态);这里可以提一下之前用的Spring定时器。(为什么用Redis的延迟队列,为什么不用Spring定时器),后面再说。 - 2. 定时主动轮询Redis的延迟队列去修改自动取消订单业务;可以提一下之前用的Redis的key键过期事件(带上底层原理)。什么场景需要用Redis的延迟队列,什么场景需要用Redis的key过期事件。 -- 幂等性:作为保证RocketMQ消费的幂等性的判断。 -- 分布式锁:下订单的时候,要争抢座位。第二套:zookeeper - -### Redis绑定Token -第一种情况:第一次登录 - -- 获取用户的账号和密码 -- 登录获取用户id -- 针对以userId生成token -- 以userId为key,value为token存进Redis中-将token返回给前端 - -第二种情况:前端token过期 - -说明:Redis存入token的有效期为1小时 - -- 判断前端是否携带token,若没有,直接返回给前端,请前端去登录; -- 若携带,则通过JWT解析出其中加密的信息,其实就是userId,带着userId去Redis中找是否存在该id,若不存在,说明已经过期了,直接返回给前端,请前端去登录; - -第三种情况:前端token和Redis的token不一致 - -接着上面第二种情况 -- 若存在,判断携带的Token是否和Redis中的token是否一致。 -- 若不一致,说明其他终端正在登录该用户使用子业务,要么改密码,要么请前端重新去登录挤下去。 -- 若一致,说明符合Redis中的token正好是该用户 - -> 可以聊聊ThreadLocal和Token的配合 - -#### 为什么用JWT? -1. HTTP协议是**无状态的** -2. Cookie是服务器发送到用户浏览器并保存在**本地的一小块数据**,数据不能太大,并且明文容易被盗取。 -3. Session不能作为分布式系统,会出现session复制,影响效率。 -4. Token是在服务端将用户信息经过Base64Url编码过后传给在客户端。每次用户请求的时候都会带上这一段信息,因此服务端拿到此信息进行解密后就知道此用户是谁了。 -5. JWT三部分:header(什么算法)、payload(主题)和signature(对前两者加密) - -### 延迟队列 -#### 更新场次状态 -##### Spring定时器 -- 首先取出当天,其次取出开始时间或者结束时间符合当前时间 -- 最后遍历所有符合当天场次,如果是0,则更改为2,如果是1,则更改为3,如果是2,则更改为1,如果是3,则更为0 -- 要记得删除缓存每隔30分钟轮询 - -##### 使用Redis的延迟队列 -- 在用定时器添加场次的时候,可以将这些场次存入Redis的zset的队列中,元素为场次ID,元素对应的score是出发时间,这样就有17个(定时器每天凌晨添加17个) -- 还是用定时器轮询,采用每隔半小时轮询一次,我们取出队列中当前时间大于队列中权重的时间的场次ID,开始进行业务逻辑判断(更改场次状态) -- 更改过后,如果是发车时间,则删除队列中的场次id和score,重新添加队列中场次的id和结束时间score,或者直接修改score -- 如果是结束时间,则删除队列中的场次id和score - -#### 订单自动取消(无效订单) -##### Redis的key键过期事件 -- 下单的时候,会在redis存该id的key -- 当该key过期的时候,Redis的监听事件会监听到该key,于是分析该key做相应的子业务 -- 回退座位和更新订单状态 - -##### Redis的延迟队列 -- 下单之后,将该id和订单过期时间存到Redis的Zset的数据结构,key为订单id,score为过期时间的时间戳 -- 开启一个线程池或者Spring的定时器定时轮询该延迟队列,将当前时间大于score的,取出来 -- 遍历去执行回退座位和更新订单状态 - -### 幂等性 -> 我们知道,作为可靠消息的分布式事务场景,在消费者消费的时候要保证不能重复消费,也就是不能重复执行相应的业务逻辑,其实就是保证幂等性,那么可以采用Redis来保证。 - -- 题外话,这里可以借助数据库表,来保证,但是这里就要消耗MySQL的性能了,不断进行和磁盘交互,则为不妥。 -- Redis的key为xx_场次id,过期时间随机5-10s,每次拿到消息进行判断Redis是否存在该key,如果存在,说明已经消费,避免重复消费,如果没有,则开始执行相应的业务逻辑,并写入Redis。 - -### 分布式锁 -> 我们知道,我们在下订单的时候,需要判断座位是否存在,是吧?当然,不存在则进行绑定座位,但是,如果此时T1事务判断座位不重复,挂起了,T2也判断不重复,也挂起了,T1开始绑定座位,那么,T2也能绑定座位,这样就会出现两个订单同样的座位。因此,这里要用分布式锁,为什么不用JVM的?这是分布式系统哇,像Synchronized等同步锁,只存在JVM单个实例中,所以这里用两种方案:Redis和zookeeper - -#### 分布式锁的条件 -- 互斥性:在任意时刻,只有一个客户端能持有锁 其他尝试获取锁的客户端都将失败而返回或阻塞等待 -- 健壮性:一个客户端持有锁的期间崩溃而没有主动释放锁,也需要保证后续其他客户端能够加锁成功 -- 唯一性:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给释放了,自己持有的锁也不能被其他客户端释放 -- 高可用:不必依赖于全部Redis节点正常工作,只要大部分的Redis节点正常运行,客户端就可以进行加锁和解锁操作 - -#### Redis分布式锁的实现 -> 毕竟判断和绑定座位(或者下单)非原子性,为了降低锁的粒度,可以将判断和绑定座位锁在一个事务里。集群:Redisson - -- Key为xx_座位号,过期时间为随机1-5s(用setex的命令,该命令是key和过期时间是原子性的) -- 每次先Redis中判断该key存在不存在,如果存在,要么阻塞,要么就返回给用户,座位已被选择。 -- 如果不存在,先上锁,然后再判断和绑定座位(或者下单)。其实这里有个隐藏的问题。如果绑定座位非常耗时,超过了过期时间1-5s,就凉凉了。其实这里设置过期时间,就是防止一直因为某种原因阻塞而不释放锁 -- 前三步,少了个签证value,如果不设置,那么当锁过期了,业务逻辑才走完,准备删除的时候,B客户端获取到了该锁,但是A把B的key锁删除了,然而B还不知道。 -- 因此,要解决这个问题,可以设置value签证,结束的时候判断一次,该value是不是自己的value,这样就不会误删。 - -#### RedLock算法流程 - -首先有这样的问题: -1. 客户端 A 从 Master 上获取锁。 -2. 在锁未被复制到某 Slave 节点的时候,Master 节点 Down 掉了。 -3. 某 Slave 节点成为新的 Master。 -4. 客户端 B 可从新 Master 上获取锁。 - -假设有5个实例 - -1. 比如过期时间为TTL:10min -2. 记录当前时间:比如T1 = 12:00 -3. 客户端分别向5个实例获取锁,比如申请锁的时间依次为:12:01...12:05,最后获取实例的锁为T2:12:05(获取锁的超时时间要远远小于过期时间,防止死等。) -4. 如果获取锁的实例大于3个(过半机制),那么就相当于获取到锁了,该锁的真正的有效时间为TTL-(T2-T1) = 5min -5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 - - - -#### zookeeper分布式锁的实现 -> 我们知道,zookeeper中有一个临时顺序节点,根据这一个特点,来实现分布式锁。每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 - -- 多个事务进来该方法,都会按照顺序生成临时节点,那么获取锁的时候,当前事务会获取一堆该节点的临时顺时节点,他就判断自己的临时节点是否是排在第一个,因为多个事务发起,都会按照顺序生成,比如1,2,3,4,5. -- 如果自己是第一个,那么就获取这把锁,如果自己不是第一个,那么他就会在他上一个顺序节点加把监听器,一旦上个节点不见了,就会通知自己获取这把锁。 -- 走完业务逻辑,删除自己的临时节点即可。 - - - - -# 计算机网络 -## 网络模型 - -![分层模型](http://media.dreamcat.ink/uPic/分层模型.png) - -### 简要概括 - -- 物理层:底层数据传输,如网线;网卡标准。 - -- 数据链路层:定义数据的基本格式,如何传输,如何标识;如网卡MAC地址。 - -- 网络层:定义IP编址,定义路由功能;如不同设备的数据转发。 - -- 传输层:端到端传输数据的基本功能;如 TCP、UDP。 - -- 会话层:控制应用程序之间会话能力;如不同软件数据分发给不同软件。 - -- 标识层:数据格式标识,基本压缩加密功能。 - -- 应用层:各种应用软件,包括 Web 应用。 - -### 流程 - -比如,计算机 A 和 计算机 B 要进行信息交互,比如 A 上开发了一个网页,需要 B 去访问。B 发出一个请求给 A,那么请求数据从 B 的 **应用层开始向下传到表示层、再从表示层传到会话层直到物理层,通过物理层传递到 A,A 的物理层接到请求后将请求向上传递到自己的应用层,应用层再将要请求的数据向自己的物理层方向传递然后 B 接到数据传递数据到自己的应用层**。 - -说明: - -- 在四层,既传输层数据被称作**段**(Segments); -- 三层网络层数据被称做**包**(Packages); -- 二层数据链路层时数据被称为**帧**(Frames); -- 一层物理层时数据被称为**比特流**(Bits)。 - -### 常见的端口号和协议号 - -![协议端口号](http://media.dreamcat.ink/uPic/EuHz2s.png) - -### 总结 - -- 网络七层模型是一个标准,而非实现。 -- 网络四层模型是一个实现的应用模型。 -- 网络四层模型由七层模型简化合并而来。 - -### ping命令基于哪一层协议的原理是什么? - -ping命令基于网络层的命令,是基于ICMP协议工作的。 - -### ARP -ARP是一种解决地址问题的协议。以目标IP地址为线索,用来定位下一个应该接收数据分包的网络设备对应的MAC地址。 -起初要通过广播发送一个ARP请求包,这个包里存放了其MAC地址的主机IP地址,由于广播的包可以被同一个链路上所有的主机或路由器接收,因此ARP的请求包也就会被这同一个链路上所有的主机和路由器进行解析。如果ARP请求包中的目标IP地址与自己的IP地址与自己的IP地址一直,那么这个节点就将自己的MAC地址塞入ARP响应包返回给主机A。 - - -## DNS - -### DNS是什么 - -**官方解释**:DNS(Domain Name System,域名系统),因特网上作为**域名和IP地址相互映射**的一个**分布式数据库**,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 - -**通俗的讲**,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2。 - -### 谈谈DNS解析过程 -![DNS解析过程](http://media.dreamcat.ink/uPic/DNS解析过程.png) - -- 请求一旦发起,若是chrome浏览器,先在浏览器找之前**有没有缓存过的域名所对应的ip地址**,有的话,直接跳过dns解析了,若是没有,就会**找硬盘的hosts文件**,看看有没有,有的话,直接找到hosts文件里面的ip - -[字节问了修改hosts,浏览器会变吗?](https://blog.csdn.net/woshizhangliang999/article/details/51457864) - -- 如果本地的hosts文件没有能的到对应的ip地址,浏览器会发出一个**dns请求到本地dns服务器**,**本地dns服务器一般都是你的网络接入服务器商提供**,比如中国电信,中国移动等。 -- 查询你输入的网址的DNS请求到达本地DNS服务器之后,**本地DNS服务器会首先查询它的缓存记录**,如果缓存中有此条记录,就可以直接返回结果,此过程是**递归的方式进行查询**。如果没有,本地DNS服务器还要向**DNS根服务器**进行查询。 -- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 -- 最后,本地DNS服务器向**域名的解析服务器**发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。 - -### DNS查询方式 - -#### 递归解析 - -当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。此时有两种方式。**局部DNS服务器自己负责向其他DNS服务器进行查询,一般是先向该域名的根域服务器查询,再由根域名服务器一级级向下查询**。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。 - -#### 迭代解析 - -当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析。局部DNS服务器不是自己向其他DNS服务器进行查询,**而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序**,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。 - -### DNS负载均衡 - -当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。处理办法就是用DNS负载均衡技术,它的原理是在**DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器**,从而达到负载均衡的目的。例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等。 - -### 为什么域名解析用UDP协议? - -因为UDP快啊!UDP的DNS协议只要一个请求、一个应答就好了。而使用基于TCP的DNS协议要三次握手、发送数据以及应答、四次挥手。但是UDP协议传输内容不能超过512字节。不过客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。 - -### 为什么区域传送用TCP协议? - -因为TCP协议可靠性好啊!你要从主DNS上复制内容啊,你用不可靠的UDP? 因为TCP协议传输的内容大啊,你用最大只能传512字节的UDP协议?万一同步的数据大于512字节,你怎么办? - -## HTTP - -### GET和POST的区别? - -1. GET使用URL或Cookie传参,而POST将数据放在BODY中 -2. GET方式提交的数据有长度限制,则POST的数据则可以非常大 -3. POST比GET安全,因为数据在地址栏上不可见,没毛病 -4. **本质区别**:GET请求是幂等性的,POST请求不是。 - -> 这里的幂等性:幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。 - -正因为它们有这样的区别,所以不应该且**不能用get请求做数据的增删改这些有副作用的操作**。因为get请求是幂等的,**在网络不好的隧道中会尝试重试**。如果用get请求增数据,会有**重复操作**的风险,而这种重复操作可能会导致副作用(浏览器和操作系统并不知道你会用get请求去做增操作)。 - -#### 1xx 信息 - -**100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 - -#### 2xx 成功 - -- **200 OK** -- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 -- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 - -#### 3xx 重定向 - -- **301 Moved Permanently** :永久性重定向 -- **302 Found** :临时性重定向 -- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 -- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 -- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 - -#### 4xx 客户端错误 - -- **400 Bad Request** :请求报文中存在语法错误。 -- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 -- **403 Forbidden** :请求被拒绝。 -- **404 Not Found**:路由不存在,或者没找到 - -#### 5xx 服务器错误 - -- **500 Internal Server Error** :服务器正在执行请求时发生错误。 -- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 - -### HTTP首部 - -> 这块有点多,可参考[http首部](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/计算机网络原理-http那些事儿.md#http首部) - -### Cookies - -HTTP 协议是**无状态**的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 - -Cookie 是**服务器发送到用户浏览器并保存在本地的一小块数据**,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 - -用途 - -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) -- 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) - -### Session - -除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 - -Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 - -### Cookie和Session的选择 - -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; -- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; -- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 - -### JWT - -JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 - -cookie+session这种模式通常是保存在**内存**中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,**只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可**。 - -**JWT的构成**: - -第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。详情请见[官网](https://jwt.io/introduction/) - -**JWT总结**: - -1. 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。 -2. payload部分,JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。 -3. 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。它不需要在服务端保存会话信息, 所以它易于应用的扩展。 - -### 浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开? - -在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。所以虽然标准中没有设定,**某些服务器对 Connection: keep-alive 的 Header 进行了支持**。 - -**持久连接**:既然维持 TCP 连接好处这么多,HTTP/1.1 就把 Connection 头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。 - - -### 一个TCP连接可以对应几个HTTP请求? - -如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。 - -### 一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)? - -HTTP/1.1 存在一个问题,单个 TCP 连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。 - -在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的。在 HTTP2 中由于 Multiplexing 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行。 - -那么在 HTTP/1.1 时代,浏览器是如何提高页面加载效率的呢?主要有下面两点: - -- 维持和服务器已经建立的 TCP 连接,在同一连接上顺序处理多个请求。 -- 和服务器建立多个 TCP 连接。 - -### 为什么有的时候刷新页面不需要重新建立 SSL 连接? - -TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。 - -### 浏览器对同一 Host 建立 TCP 连接到数量有没有限制? - -**有。Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。** - - -### 在浏览器中输入url地址后显示主页的过程? - -> - 根据域名,进行DNS域名解析; -> - 拿到解析的IP地址,建立TCP连接; -> - 向IP地址,发送HTTP请求; -> - 服务器处理请求; -> - 返回响应结果; -> - 关闭TCP连接; -> - 浏览器解析HTML; -> - 浏览器布局渲染; - -### HTTP1.x的缺点 -1. HTTP/1.0 一次只允许在一个TCP连接上发起一个请求,HTTP/1.1使用的流水线技术也只能部分处理请求分析,仍然会存在队列头阻塞问题,因此客户端在需要发起多次请求时,通常会采用建立多连接来减少延迟 -2. 单向请求,只能由客户端发起 -3. 请求报文与响应报文首部信息冗余量大。 -4. 数据未压缩,导致数据的传输量大。 - -### HTTP2.0有哪些改动 -1. 多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。 -2. 二进制分帧:应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。 -3. 首部压缩(Header Compression) -4. 服务端推送(Server Push) - -## HTTPS - -### HTTPS是什么 - -HTTPS 并不是新协议,而是让 **HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信**。通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 - -### HTTP的缺点 - -- 使用明文进行通信,内容可能会被窃听; -- 不验证通信方的身份,通信方的身份有可能遭遇伪装; -- 无法证明报文的完整性,报文有可能遭篡改。 - -### 对称密钥加密 - -对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 - -- 优点:运算速度快 -- 缺点:无法安全地将密钥传输给通信方 - -### 非对称密钥加密 - -非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 - -公开密钥所有人都可以获得,**通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密**,**接收方收到通信内容后使用私有密钥解密**。 - -非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 - -- 优点:可以更安全地将公开密钥传输给通信发送方; -- 缺点:运算速度慢。 - -### HTTPS采用的加密方式 - -HTTPS 采用混合的加密机制,使用**非对称密钥加密用于传输对称密钥来保证传输过程的安全性**,之后使用**对称密钥加密进行通信来保证通信过程的效率**。 - -![rsa原理](http://media.dreamcat.ink/uPic/rsa原理.png) - -确保传输安全过程(其实就是rsa原理): - -1. Client给出**协议版本号**、一个客户端生成的**随机数**(Client random),以及客户端支持的**加密方法**。 -2. Server确认双方使用的**加密方法**,并给出**数字证书**、以及一个服务器生成的**随机数**(Server random)。 -3. Client确认**数字证书有效**,然后生成一个新的**随机数**(Premaster secret),并使用**数字证书中的公钥,加密这个随机数**,发给Server。 -4. Server使用自己的**私钥,获取Client发来的随机数**(Premaster secret)。 -5. Client和Server根据约定的加密方法,使用前面的**三个随机数,生成”对话密钥”**(session key),用来加密接下来的整个对话过程。 - -### 认证 - -通过使用 **证书** 来对通信方进行认证。 - -数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。 - -服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 - -进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 - -[加密全套流程](https://www.cnblogs.com/handsomeBoys/p/6556336.html) - -[https://www.cnblogs.com/xdyixia/p/11610102.html](https://www.cnblogs.com/xdyixia/p/11610102.html) - -### HTTPS的缺点 - -- 因为需要进行加密解密等过程,因此速度会更慢; -- 需要支付证书授权的高额费用。 - -## TCP/UDP - -### TCP - -#### TCP是什么? - -`TCP(Transmission Control Protocol 传输控制协议)`是一种面向连接的、可靠的、基于字节流的传输层通信协议。 - -#### TCP头部报文 - -##### source port 和 destination port - -> 两者分别为「源端口号」和「目的端口号」。源端口号就是指本地端口,目的端口就是远程端口。 - -可以这么理解,我们有很多软件,每个软件都对应一个端口,假如,你想和我数据交互,咱们得互相知道你我的端口号。 - -再来一个很官方的: - -> 扩展:应用程序的端口号和应用程序所在主机的 IP 地址统称为 socket(套接字),IP:端口号, 在互联网上 socket 唯一标识每一个应用程序,源端口+源IP+目的端口+目的IP称为”套接字对“,一对套接字就是一个连接,一个客户端与服务器之间的连接。 - -##### Sequence Number - -> 称为「序列号」。用于 TCP 通信过程中某一传输方向上字节流的每个字节的编号,为了确保数据通信的有序性,避免网络中乱序的问题。接收端根据这个编号进行确认,保证分割的数据段在原始数据包的位置。初始序列号由自己定,而后绪的序列号由对端的 ACK 决定:SN_x = ACK_y (x 的序列号 = y 发给 x 的 ACK)。 - -说白了,类似于身份证一样,而且还得发送此时此刻的所在的位置,就相当于身份证上的地址一样。 - -##### Acknowledge Number - -> 称为「确认序列号」。确认序列号是接收确认端所期望收到的下一序列号。确认序号应当是上次已成功收到数据字节序号加1,只有当标志位中的 ACK 标志为 1 时该确认序列号的字段才有效。主要用来解决不丢包的问题。 - -##### TCP Flag - -`TCP` 首部中有 6 个标志比特,它们中的多个可同时被设置为 `1`,主要是用于操控 `TCP` 的状态机的,依次为`URG,ACK,PSH,RST,SYN,FIN`。 - -当然只介绍三个: - -1. **ACK**:这个标识可以理解为发送端发送数据到接收端,发送的时候 ACK 为 0,标识接收端还未应答,一旦接收端接收数据之后,就将 ACK 置为 1,发送端接收到之后,就知道了接收端已经接收了数据。 -2. **SYN**:表示「同步序列号」,是 TCP 握手的发送的第一个数据包。用来建立 TCP 的连接。SYN 标志位和 ACK 标志位搭配使用,当连接请求的时候,SYN=1,ACK=0连接被响应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有 SYN 的数据包,如果对方主机响应了一个数据包回来 ,就表明这台主机存在这个端口。 -3. **FIN**:表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的 TCP 数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描。发送端只剩最后的一段数据了,同时要告诉接收端后边没有数据可以接受了,所以用FIN标识一下,接收端看到这个FIN之后,哦!这是接受的最后的数据,接受完就关闭了;**TCP四次分手必然问**。 - -##### Window size - -> 称为滑动窗口大小。所说的滑动窗口,用来进行流量控制。 - -### TCP三次握手 - -![TCP三次握手](http://media.dreamcat.ink/uPic/TCP三次握手.svg) - -- **初始状态**:客户端处于 `closed(关闭)`状态,服务器处于 `listen(监听)` 状态。 -- **第一次握手**:客户端发送请求报文将 `SYN = 1`同步序列号和初始化序列号`seq = x`发送给服务端,发送完之后客户端处于`SYN_Send`状态。 -- **第二次握手**:服务端收到 `SYN` 请求报文之后,如果同意连接,会以自己的同步序列号`SYN(服务端) = 1`、初始化序列号 `seq = y`和确认序列号(期望下次收到的数据包)`ack = x + 1` 以及确认号`ACK = 1`报文作为应答,服务器为`SYN_Receive`状态。(问题来了,两次握手之后,所以老哥,你需要给我三次握手来传个话告诉我一声。你要是不告诉我,万一我认为你跑了,然后我可能出于安全性的考虑继续给你发一次,看看你回不回我。) -- **第三次握手**: 客户端接收到服务端的 `SYN + ACK`之后,知道可以下次可以发送了下一序列的数据包了,然后发送同步序列号 `ack = y + 1`和数据包的序列号 `seq = x + 1`以及确认号`ACK = 1`确认包作为应答,客户端转为`established`状态。(分别站在双方的角度上思考,各自ok) - -1. 你吃饭了嘛?(seq=x),收到请回答(SYN=1) -2. 收到(ACK=1),吃饭了(ack=x+1),你吃饭了吗?(seq=y),收到请回答(SYN=1) -3. 收到(ACK=1),吃饭了(ack=y+1),那么我们聊一下接下里的事情(established) - -### TCP四次分手 - -![TCP四次分手](http://media.dreamcat.ink/uPic/TCP四次分手.png) - -- **初始化状态**:客户端和服务端都在连接状态,接下来开始进行四次分手断开连接操作。 -- **第一次分手**:第一次分手无论是客户端还是服务端都可以发起,因为 TCP 是全双工的。 - -> 假如客户端发送的数据已经发送完毕,发送FIN = 1 **告诉服务端,客户端所有数据已经全发完了**,**服务端你可以关闭接收了**,但是如果你们服务端有数据要发给客户端,客户端照样可以接收的。此时客户端处于FIN = 1等待服务端确认释放连接状态。 - -- **第二次分手**:服务端接收到客户端的释放请求连接之后,**知道客户端没有数据要发给自己了**,**然后服务端发送ACK = 1告诉客户端收到你发给我的信息**,此时服务端处于 CLOSE_WAIT 等待关闭状态。(服务端先回应给客户端一声,我知道了,但服务端的发送数据能力即将等待关闭,于是接下来第三次就来了。) -- **第三次分手**:此时服务端向客户端把所有的数据发送完了,然后发送一个FIN = 1,**用于告诉客户端,服务端的所有数据发送完毕**,**客户端你也可以关闭接收数据连接了**。此时服务端状态处于LAST_ACK状态,来等待确认客户端是否收到了自己的请求。(服务端等客户端回复是否收到呢,不收到的话,服务端不知道客户端是不是挂掉了还是咋回事呢) -- **第四次分手**:此时如果客户端收到了服务端发送完的信息之后,就发送ACK = 1,告诉服务端,客户端已经收到了你的信息。**有一个 2 MSL 的延迟等待**。 - -#### 为什么要有2MSL等待延迟? - -对应这样一种情况,最后客户端发送的ACK = 1给服务端的**过程中丢失**了,服务端没收到,服务端怎么认为的?我已经发送完数据了,怎么客户端没回应我?是不是中途丢失了?然后服务端再次发起断开连接的请求,一个来回就是2MSL。 - -客户端给服务端发送的ACK = 1丢失,**服务端等待 1MSL没收到**,**然后重新发送消息需要1MSL**。如果再次接收到服务端的消息,则**重启2MSL计时器**,**发送确认请求**。客户端只需等待2MSL,如果没有再次收到服务端的消息,就说明服务端已经接收到自己确认消息;此时双方都关闭的连接,TCP 四次分手完毕 - -#### 为什么四次分手? - -任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 - -### TCP粘包 - -**TCP粘包**是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。 - - -个人觉得:应用层的报文在以流的形式传输时,每一个报文的报头紧接着上一个报文的报文尾部,这就是所谓的“粘包”问题。 - -- 由TCP**连接复用**造成的粘包问题。 -- 因为TCP默认会使用**Nagle算法**,此算法会导致粘包问题。 - - 只有上一个分组得到确认,才会发送下一个分组; - - 收集多个小分组,在一个确认到来时一起发送。 -- **数据包过大**造成的粘包问题。 -- 流量控制,**拥塞控制**也可能导致粘包。 -- **接收方不及时接收缓冲区的包,造成多个包接收** - -**解决**: - -1. **Nagle算法**问题导致的,需要结合应用场景适当关闭该算法 -2. 尾部标记序列。通过特殊标识符表示数据包的边界,例如\n\r,\t,或者一些隐藏字符。 -3. 头部标记分步接收。在TCP报文的头部加上表示数据长度。 -4. 应用层发送数据时**定长**发送。 - -[https://blog.csdn.net/xp178171640/article/details/104746379/](https://blog.csdn.net/xp178171640/article/details/104746379/) - -[https://blog.csdn.net/songchuwang1868/article/details/87707127](https://blog.csdn.net/songchuwang1868/article/details/87707127) - -### TCP 协议如何保证可靠传输? - -- **确认和重传**:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就会重传。 -- **数据校验**:TCP报文头有校验和,用于校验报文是否损坏。 -- **数据合理分片和排序**:tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新排序后交给应用层。而UDP:IP数据报大于1500字节,大于MTU。这个时候发送方的IP层就需要分片,把数据报分成若干片,是的每一片都小于MTU。而接收方IP层则需要进行数据报的重组。由于UDP的特性,某一片数据丢失时,接收方便无法重组数据报,导致丢弃整个UDP数据报。 -- **流量控制**:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。 -- **拥塞控制**:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。 - -### TCP 利用滑动窗口实现流量控制的机制? - -> 流量控制是为了控制发送方发送速率,保证接收方来得及接收。TCP 利用滑动窗口实现流量控制。 - -TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着**接收方还有多大的缓冲区可以用于接收数据**。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据。 - -> 例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。 - -### TCP拥塞控制的机制以及算法? - -> 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。 - -TCP 发送方要维持一个 **拥塞窗口(cwnd) 的状态变量**。拥塞控制窗口的大小**取决于网络的拥塞程度**,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 - -### TCP的长连接和短连接 -[https://www.cnblogs.com/chinaops/p/9303041.html](https://www.cnblogs.com/chinaops/p/9303041.html) - -### UDP - -提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。 - -#### UDP的特点 - -- UDP是**无连接的**; -- UDP使用**尽最大努力交付**,即不保证可靠交付,因此主机不需要维持复杂的链接状态(这里面有许多参数); -- UDP是**面向报文**的; -- UDP**没有拥塞控制**,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等); -- UDP**支持一对一、一对多、多对一和多对多**的交互通信; -- UDP的**首部开销小**,只有8个字节,比TCP的20个字节的首部要短。 - -那么,再说一次TCP的特点: - -- **TCP是面向连接的**。(就好像打电话一样,通话前需要先拨号建立连接,通话结束后要挂机释放连接); -- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的(**一对一**); -- TCP**提供可靠交付的服务**。通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达; -- TCP**提供全双工通信**。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双方通信的数据; -- **面向字节流**。TCP中的“流”(stream)指的是流入进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。 - - - -# 补充Mybatis - -## MyBatis与Hibernate有哪些不同? - - 1. Mybatis和hibernate不同,它不完全是一个ORM框架,因为**MyBatis需要程序员自己编写Sql语句**。 - 2. Mybatis直接编写原生态sql,可以严格控制sql执行性能,**灵活度高**,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 - 3. Hibernate对象/关系映射能力强,**数据库无关性好**,对于关系模型要求高的软件,如果用hibernate开发可以**节省很多代码,提高效率**。 - - ## Mybatis的一级、二级缓存 - - - 一级缓存: 基于 **PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session**,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。 - - 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 **Mapper(Namespace),并且可自定义存储源**,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态); - - 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。 - -## #{}和${}的区别是什么? - 1. `#{}` 是预编译处理,`${}`是字符串替换。 - 2. Mybatis在处理`#{}`时,会将sql中的`#{}`替换为?号,调用PreparedStatement的set方法来赋值; - 3. Mybatis在处理`${}`时,就是把`${}`替换成变量的值。 - 4. 使用`#{}`可以有效的防止SQL注入,提高系统安全性。 - -## mybatis是如何防止SQL注入的? - - **sql注入**: - - **SQL注入**,大家都不陌生,是一种常见的攻击方式。**攻击者**在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵**参数检验不足**的应用程序。所以,在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如银行软件),经常使用将**SQL语句**全部替换为**存储过程**这样的方式,来防止SQL注入。这当然是**一种很安全的方式**,但我们平时开发中,可能不需要这种死板的方式。 - - **mybatis是如何做到防止sql注入的** - - MyBatis框架作为一款半自动化的持久层框架,其SQL语句都要我们自己手动编写,这个时候当然需要防止SQL注入。其实,MyBatis的SQL是一个具有“**输入+输出**”的功能,类似于函数的结构,参考上面的两个例子。其中,parameterType表示了输入的参数类型,resultType表示了输出的参数类型。回应上文,如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。上面代码中使用#的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的: - - ```sql - select id, username, password, role from user where username=? and password=? - ``` - - 不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。 - - [底层实现原理]MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。 - - ```java - // 安全,预编译 - String sql = "select id, username, password, role from user where id=?"; //执行sql前会预编译号该条语句 - PreparedStatement pstmt = conn.prepareStatement(sql); - pstmt.setString(1, id); - - // 不安全 - String sql = "select id,username,password,role from user where id=" + id; - //当id参数为"3;drop table user;"时,执行的sql语句如下: - //select id,username,password,role from user where id=3; drop table user; - PreparedStatement pstmt = conn.prepareStatement(sql); - ``` - - **结论**: - - **\#{}**:相当于JDBC中的PreparedStatement - - **${}**:是输出变量的值 - - 简单说,#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。 - - -# Dubbo -## 集群、分布式和微服务的概念 -微服务是一种架构风格,一个大型复杂软件应用由**一个或多个微服务组成**。系统中的**各个微服务可被独立部署,各个微服务之间是松耦合的**。**每个微服务仅关注于完成一件任务并很好地完成该任务**。在所有情况下,每个任务代表着一个小的业务能力。 - -- 分布式将一个大的系统划分为多个业务模块,**业务模块分别部署到不同的机器上**,各个业务模块之间通过接口进行数据交互。区别**分布式的方式是根据不同机器不同业务**。 -- 集群模式是不**同服务器部署同一套服务对外访问,实现服务的负载均衡**。区别集群的方式是根据部署多台服务器业务是否相同。 -- 微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务。微服务与分布式的细微差别是,**微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器**。 - -## 什么是RPC? -RPC(RemoteProcedureCall)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A、B部署在两台不同的机器上,那么服务A如果想要调用服务B中的某个方法该怎么办呢?使用HTTP请求当然可以,但是可能会比较慢而且一些优化做的并不好。RPC的出现就是为了解决这个问题。 -gRPC -[https://blog.csdn.net/fly910905/article/details/104130202](https://blog.csdn.net/fly910905/article/details/104130202) - - -## 为什么要用分布式? -从开发角度来讲**单体应用的代码都集中在一起**,而**分布式系统的代码根据业务被拆分**。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。-系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。把整个系统拆分成不同的服务/系统,然后每个服务/系统单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? - -## Dubbo是什么? - -Dubbo是一款**高性能**、**轻量级**的开源JavaRPC框架,它提供了三大核心能力:**面向接口的远程方法调用**,**智能容错和负载均衡**,以及**服务自动注册和发现**。简单来说Dubbo是一个**分布式服务框架**,致力于提供**高性能和透明化的RPC远程服务调用方案**,以及**SOA服务治理方案。** - -- **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务。 -- **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo可以为我们解决服务之间互相是如何调用的。 -- **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 -- **服务降级**——某个服务挂掉之后调用备用服务。另外,Dubbo除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于SpringCloud在微服务中应用更加广泛,所以,我觉得一般我们提Dubbo的话,大部分是分布式系统的情况。 - -## Dubbo的图解? -![dubbo架构图解](http://media.dreamcat.ink/uPic/dubbo架构图解.png) - -- **Provider:**暴露服务的服务提供方 -- **Consumer:**调用远程服务的服务消费方 -- **Registry:**服务注册与发现的注册中心 -- **Monitor:**统计服务的调用次数和调用时间的监控中心 -- **Container:**服务运行容器 - -**调用关系说明**: - -- 服务容器负责启动,加载,运行服务提供者。 -- 服务提供者在启动时,向注册中心注册自己提供的服务。 -- 服务消费者在启动时,向注册中心订阅自己所需的服务。 -- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 -- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 -- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 - -**各个组件总结**: - -- **注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小** -- **监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示** -- **注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外** -- **注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者** -- **注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表** -- **注册中心和监控中心都是可选的,服务消费者可以直连服务提供者** -- **服务提供者无状态,任意一台宕掉后,不影响使用** -- **服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复** - -## Dubbo和SpringCloud的区别? -- **底层**:`Dubbo`底层是使用Netty的NIO框架,基于TCP协议传输,使用Hession序列化完成RPC通信;`SpringCloud`是基于HTTP协议+REST接口调用远程过程的通信,HTTP请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,不存在代码级别的强依赖。 -- **集成**:springcloud相关组件多,有自己的注册中心网关等,集成方便,Dubbo需要自己额外去集成。-**定位**:Dubbo是SOA时代的产物,它的关注点主要在于**服务的调用,流量分发、流量监控和熔断**。而SpringCloud诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了Spirng、SpringBoot的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、SpirngCloud是一个生态。因此可以大胆地判断,Dubbo未来会在服务治理方面更为出色,而SpringCloud在微服务治理上面无人能敌。 - -## Dubbo的容错机制 -1. 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数 -2. 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 -3. 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 -4. 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 -5. 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 -6. 广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息 - -## Dubbo的限流是怎么实现的? -Dubbo默认时候的是**令牌桶**算法实现限流。在某段时间内,桶里面只能放n个令牌,然后来一个请求就减少一个令牌,如果桶内的令牌没有了,则不能继续执行请求。 - -限流主要是通过TPSLimitFilter实现。 - -## 常见的限流算法有哪些? -- 计数算法 -- 滑动窗口,解决计数算法同一时刻进入很多请求 -- 令牌桶算法 -- 漏桶算法 - -## 什么是dubbo服务降级? -- dubbo在服务调用时,可能由于服务器宕机、网络超时、并发数太高等,导致调用失败。**服务降级**就是指在非业务异常导致的服务不可用时,可以返回默认值,避免影响主业务的处理。 -- dubbo可以通过mock配置实现服务降级。 - -## Dubbo的注册中心挂了,还可以继续通信吗? -可以。因为在开始初试化的时候,消费者会将提供者的地址等信息拉取到**本地缓存**中。 - -## 负载均衡 -个人理解: -> 比如我们的系统中的某个服务的**访问量特别大**,我们将这个服务部署在了**多台服务器**上,当客户端发起请求的时候,**多台服务器都可以处理这个请求**。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。 - -1. RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。 -2. RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。 -3. LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。 -4. ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。 - -[http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html](http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html) - -## 为什么使用zk当dubbo的注册中心 -Zookeeper的数据模型很简单,有一系列被称为ZNode的数据节点组成,与传统的磁盘文件系统不同的是,zk将全量数据存储在内存中,可谓是高性能,而且支持集群,可谓高可用,另外支持事件监听。这些特点决定了zk特别适合作为注册中心(数据发布/订阅)。不过要注意网络闪断引发的节点摘除问题。 - -## 几个服务注册与发现的对比 -[https://zhuanlan.zhihu.com/p/145296163](https://zhuanlan.zhihu.com/p/145296163) - - -## SPI源码(过程) -先说一下Java的SPI机制 -Java的SPI机制利用ServiceLoader的load方法传递个接口,就会得到该接口的所有的实现类 -要在指定的META-INF的services下 -但是有一说一,只能通过iterator来遍历判断想要的实现类 -而Dubbo和Spring的SPI比Java的灵活一些,可以通过key来获取对用的实例 - -直接说Dubbo的SPI源码过程 -先说一下Dubbo的SPI机制,不仅支持有着Java的SPI,还有着AOP的功能,包装了一下,同时有着DI功能,依赖注入 -1. 通过getExtensionLoader得到该接口的load,不过获取之间会对一些type检查,同时有缓存机制。 -2. 然后通过load调用getExtension,也是一系列检查和缓存,最关键的就是createExtension -3. 其中getExtensionClasses,这个方法返回对应name的接口的实例对象,接着来到injectExtension注入属性 -4. 如果有wrapper包装,就是通过接口的实例类有木有构造器,如果有,最后injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));无限遍历AOP,也就是构造器注入,最后返回带包装的接口的实例对象。 -5. 以上是没有讲依赖注入的过程,官网上有。 - -## 服务导出与暴漏(源码流程) -源码省略,针对于面试就说源码流程即可 -总结一波吧: -1. 当我们看源码知道,导出和暴漏在IoC初始化的最后一步的finishRefresh中的ServiceBean中。 -2. 其中在onApplicationEvent执行export->doExport,在doExport中首先检查provider呀,register呀,monitor呀等,最后来到关键的一步doExportUrls(); -3. 在这一步当中,实际上,就是对注册的url和导出的url拼接,并且将导出的url远程注册到注册中心,最后暴漏一下自己的url,具体的话就第四步。 -4. doExportUrlsFor1Protocol其中就是刚才第三步说的那一些,然后后面的逻辑就是exportLocal,默认本地导出,关键是远程导出:proxyFactory.getInvoker,然后得到wrapperInvoker,最后就是这个关键了protocol.export(wrapperInvoker),然后会有个子流程去构造buildInvokerChain,调用链。这个是服务调用链路 -5. 实际上找Protocol.class接口的实例代理类,默认是dubbo协议,因此调用的dubbo的实例代理类的export方法,继续使用dubbo协议的url,一步一步绑定nettyClient客户端,最后导出自己的调用链。 - - -## 服务引入与目录(源码过程) - -肯定是ReferenceBean - -1. 当我们看源码知道,首先进来的ReferenceBean的get方法->ReferenceConfig的init方法内部 -2. checkDefault检查消费端的全局配置,接着通过SPI获取消费服务的实现类,经过一些列检查又进入了HashMap的缓存当中 -3. init方法中的最后一步createProxy中,这个方法就是将要引入订阅注册中心的服务端的目录,首先是refprotocol.refer方法从注册中心引入和订阅,该方法是核心。 -4. 首先通过RegistryProtocol的refer中,如果是zk协议,那么就启动zk客户端去连接,接着进入doRefer方法中,先在注册中心,注册消费端服务,接着开始通过subscribe订阅注册中心的目录,category、providers、configurators和routers,然后进入notify,调用listener.notify(categoryList),通知categoryList -5. 这时候来到了协议Dubbo的refer中,开始构造路由链,首先buildInvokerChain调用链,Dubbo启动的是netty客户端哦,debug时候看出来的,获取的是netty的client,最后构建成功就返回。 -6. 最后将所有的目录添加到cluster中,并返回invoker,其实该invoker是MockClusterInvoker,ref是它的代理实现类最后初始化完毕。 - -总感觉处处invoker(执行)类似于发送请求一样。 - -## 服务调用、服务降级和集群容错 -先说一下invorker,在服务引入那里最终返回的是MockClusterInvoker的代理实现类,意思就是说,首先进入Java的动态代理,InvokerInvocationHandler,然后调用invork,进入MockClusterInvoker,然后调用invoke进入默认的FailoverClusterInvoker的invoker。每个invoker就是InvokerDelegate委托实现类 -1. 根据我上面说的,其实从服务目录获取所有的提供者Invokers,在经过MockClusterInvoker的时候,如果配置了服务降级,服务降级就是通过mock机制而已,那么如果调用失败,先走Mock的服务降级策略,如果没有配置,然后开始初始化负载均衡策略, -2. 就进入了容错策略的Invoker类,然后通过负载均衡选择一个invoker,开始调用过滤链,最后才会执行我们的Dubbo协议上的客户端,应该是netty吧,去执行invoker -3. 服务那边开始被触发事件之后,也会执行自己的过滤链,然后最后执行自己的InvokerDelegate服务实现委托类,将结果先返回给自己,然后在通过负责处理请求的控制器传给消费端。 -4. 以上是一次调用过程粗略的经过。 - - -# RocketMQ -> 项目用到了它来保证事务的最终一致性,当然,也得知道消息队列其他应用场景。 - -![](https://static01.imgkr.com/temp/c93d0564a0724be98a995757c40ddb15.png) - -结合项目分析该分布式事务消息的原理: -> 项目是怎么用的?举个例子,1. 下单的时候,通知绑定座位 2. 支付,通知扣钱 3. 退款 - - -想了想,mq也可以 -1. 先给Brock发送一条消息:我要下单了,注意哈 -2. Brock给本地事务回馈消息:ack,好的,我知道了(半投递状态,消费端看不到) -3. 本地事务开始执行业务逻辑,这里首先(校验场次id的座位是否重复,如果没有,直接执行下单:这两个业务非原子,上个锁,要不然可能会出现同样的座位)。下单成功,则返回commit给Brock。消费者此时就可以看到这条消息了。 -4. 如果下单不成功,则返回rollback,Brock一般三天自动删除该无效的消息,消费者也看不到。 -5. 消费者看到了这条消息,调用绑定座位服务,如果失败了,则重试。(消费端不能失败,要不然不能保持一致,如果还是一直失败,则人工处理。) 注意:幂等性 - - -支付,通知扣钱,更改状态 - -1. 先给Brock发送一条消息:我要扣钱了,注意哈 -2. Brock给本地事务回馈消息:ack,好的,我知道了(半投递状态,消费端看不到) -3. 本地事务校验参数问题,执行commit给Brock -4. 用户服务执行扣钱服务,订单服务执行更改订单状态服务,注意幂等 - -退款:省略 - -## CAP -- C(一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。 -- A(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。 -- P(分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。 - -- CP:对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。 -- AP:对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。 - -## BASE -BASE是BasicallyAvailable(基本可用)、Softstate(软状态)和Eventuallyconsistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展-基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。 -- 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。 -- 最终一致:**最终一致是指经过一段时间后,所有节点数据都将会达到一致**。BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。 - -## 2PC -![](https://img2018.cnblogs.com/blog/1090617/201907/1090617-20190710222443794-591603727.jpg) - ---- -第一阶段: -- 协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。 -- 各个 参与者 节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向 协调者 报告说:“我这边可以处理了/我这边不能处理”。. -- 如果 参与者 成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,如果没有 参与者 成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。 - -图就不放了,很简单 -第二阶段:成功 -- 协调者 向 所有参与者 节点发出Commit请求. -- 参与者 收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。 - -第二阶段:失败 -任何一个 参与者 向 协调者 反馈了 No 响应,或者等待超时之后,协调者尚未收到所有参与者的反馈响应。 -- 协调者 向所有参与者节点发出 RoollBack 请求. -- 参与者 接收到RoollBack请求后,会回滚本地事务。 - -缺点: -- 性能问题:无论是在第一阶段的过程中,还是在第二阶段,所有的参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,事务 协调者 才会通知进行全局提交,参与者 进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。 -- 单节点故障:由于协调者的重要性,一旦 协调者 发生故障。参与者 会一直阻塞下去。尤其在第二阶段,协调者 发生故障,那么所有的 参与者 还都处于锁定事务资源的状态中,而无法继续完成事务操作。(虽然协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) - -2PC出现单点问题的三种情况: -- 协调者正常,参与者宕机:由于 协调者 无法收集到所有 参与者 的反馈,会陷入阻塞情况。解决方案:引入超时机制,如果协调者在超过指定的时间还没有收到参与者的反馈,事务就失败,向所有节点发送终止事务请求。 -- 协调者宕机,参与者正常:无论处于哪个阶段,由于协调者宕机,无法发送提交请求,所有处于执行了操作但是未提交状态的参与者都会陷入阻塞情况.解决方案:引入协调者备份,同时协调者需记录操作日志.当检测到协调者宕机一段时间后,协调者备份取代协调者,并读取操作日志,向所有参与者询问状态。 -- 协调者和参与者都宕机 - - -## 3PC -对2pc的优化 - -- 引入超时机制 -- 在第一阶段和第二阶段中插入一个准备阶段,尝试获取数据库锁。如果可以就yes - -## TCC -Try-Confirm-Cancel -- 先是服务调用链路依次执行 Try 逻辑。 -- 如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务。 -- 如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作。 - -这就是所谓的 TCC 分布式事务。TCC 分布式事务的核心思想,说白了,就是当遇到下面这些情况时: -- 某个服务的数据库宕机了。 -- 某个服务自己挂了。 -- 那个服务的 Redis、Elasticsearch、MQ 等基础设施故障了。 -- 某些资源不足了,比如说库存不够这些。 - - -先来 Try 一下,不要把业务逻辑完成,先试试看,看各个服务能不能基本正常运转,能不能先冻结我需要的资源。 - -如果 Try 都 OK,也就是说,底层的数据库、Redis、Elasticsearch、MQ 都是可以写入数据的,并且你保留好了需要使用的一些资源(比如冻结了一部分库存)。 - -接着,再执行各个服务的 Confirm 逻辑,基本上 Confirm 就可以很大概率保证一个分布式事务的完成了。 - -那如果 Try 阶段某个服务就失败了,比如说底层的数据库挂了,或者 Redis 挂了,等等。 - -此时就自动执行各个服务的 Cancel 逻辑,把之前的 Try 逻辑都回滚,所有服务都不要执行任何设计的业务逻辑。保证大家要么一起成功,要么一起失败。 - -等一等,你有没有想到一个问题?如果有一些意外的情况发生了,比如说订单服务突然挂了,然后再次重启,TCC 分布式事务框架是如何保证之前没执行完的分布式事务继续执行的呢? - -所以,TCC 事务框架都是要记录一些分布式事务的活动日志的,可以在磁盘上的日志文件里记录,也可以在数据库里记录。保存下来分布式事务运行的各个阶段和状态。 - -问题还没完,万一某个服务的 Cancel 或者 Confirm 逻辑执行一直失败怎么办呢? - -那也很简单,TCC 事务框架会通过活动日志记录各个服务的状态。举个例子,比如发现某个服务的 Cancel 或者 Confirm 一直没成功,会不停的重试调用它的 Cancel 或者 Confirm 逻辑,务必要它成功! - -当然了,如果你的代码没有写什么 Bug,有充足的测试,而且 Try 阶段都基本尝试了一下,那么其实一般 Confirm、Cancel 都是可以成功的! - -## 可靠消息最终一致性 -在上面的通用方案设计里,完全依赖可靠消息服务的各种自检机制来确保: - -- 如果上游服务的数据库操作没成功,下游服务是不会收到任何通知。 -- 如果上游服务的数据库操作成功了,**可靠消息服务死活都会确保将一个调用消息投递给下游服务,而且一定会确保下游服务务必成功处理这条消息**。 - -通过这套机制,保证了基于 MQ 的异步调用/通知的服务间的分布式事务保障。其实阿里开源的 RocketMQ,就实现了可靠消息服务的所有功能,核心思想跟上面类似。 - -## 为什么选择RocketMQ作消息队列 -- ActiveMQ 的社区算是比较成熟,但是较目前来说,**ActiveMQ 的性能比较差,而且版本迭代很慢**,不推荐使用。 -- RabbitMQ 在**吞吐量方面虽然稍逊于 Kafka 和 RocketMQ** ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果**业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选**。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 -- RocketMQ 阿里出品,**Java 系开源项目**,源代码我们可以直接阅读,然后可以**定制自己公司的MQ**,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。其次具有分布式事务消息的功能,可以达到消息的最终一致性。 -- kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。**kafka 唯一的一点劣势是有可能消息重复消费**,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 - -## RocketMQ组件 -- Producer:消息发布的角色,主要负责把消息发送到Broker,支持分布式集群方式部署。 -- Consumer:消息消费者的角色,主要负责从Broker订阅消息消费,支持分布式集群方式部署。 -- Broker:消息存储的角色,主要负责消息的存储、投递和查询,以及服务高可用的保证,支持分布式集群方式部署。 -- NameServer:是一个非常简单的Topic路由注册中心,其角色类似于Dubbo中依赖的Zookeeper,支持Broker动态注册和发现。 - - 服务注册:NameServer接收Broker集群注册的信息,保存下来作为路由信息的基本数据,并提供心跳检测检测机制,检查Broker是否存活。 - - 路由信息管理:NameServer保存了Broker集群的路由信息,用于提供给客户端查询Broker的队列信息。Producer和Consumer通过NameServer可以知道Broker集群的理由信息,从而进行消息的投递和消费。 - -## MQ在高并发情况下,假设队列满了如何防止消息丢失? -- 生产者可以采用重试机制。因为消费者会不停的消费消息,可以重试将消息放入队列。 -- 死信队列,可以理解为备胎(推荐) - - 即在消息过期,队列满了,消息被拒绝的时候,都可以扔给死信队列。 - - 如果出现死信队列和普通队列都满的情况,此时考虑消费者消费能力不足,可以对消费者开多线程进行处理。 - -## 谈谈死信队列 -**死信队列用于处理无法被正常消费的消息,即死信消息**。 - -当一条消息初次消费失败,**消息队列 RocketMQ 版会自动进行消息重试**;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 RocketMQ 版不会立刻将消息丢弃,而是将其发送到该**消费者对应的特殊队列中**,该特殊队列称为**死信队列**。 - -**死信消息的特点**: - -- 不会再被消费者正常消费。 -- 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。 - -**死信队列的特点**: - -- 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。 -- 如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 版不会为其创建相应的死信队列。 -- 一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。 - -消息队列 RocketMQ 版控制台提供对死信消息的查询、导出和重发的功能。 - -## 使用异步消息时如何保证数据的一致性 - -- **借助数据库的事务**:这需要在数据库中创建一个**本地消息表**,这样可以通过**一个事务来控制本地业务逻辑更新**和**本地消息表的写入在同一个事务中**,一旦消息落库失败,则直接全部回滚。如果消息落库成功,后续就可以根据情况基于本地数据库中的消息数据对消息进行重投了。关于本地消息表和消息队列中状态如何保持一致,可以采用 2PC 的方式。在发消息之前落库,然后发消息,在得到同步结果或者消息回调的时候更新本地数据库表中消息状态。然后只需要通过**定时轮询**的方式对状态未已记录但是未发送的消息重新投递就行了。但是这种方案有个前提,就是要求消息的消费者**做好幂等控制**,这个其实异步消息的消费者一般都需要考虑的。 -- 除了使用数据库以外,还可以使用 **Redis** 等缓存。这样就是无法利用关系型数据库自带的事务回滚了。 - -## RockMQ不适用Zookeeper作为注册中心的原因,以及自制的NameServer优缺点? -- ZooKeeper 作为支持**顺序一致性**的中间件,在某些情况下,它为了满足一致性,会丢失一定时间内的**可用性**,RocketMQ 需要注册中心只是为了**发现组件地址**,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,这同时也是 **NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致**。 -- 另外,当有新的服务器加入时,**NameServer 并不会立马通知到 Produer**,而是由 **Produer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息**(这种情况是通过 Producer 发送消息时,负载均衡解决) -- 包括组件通信间使用 Netty 的自定义协议 -- 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略) -- 消息过滤器(Producer 发送消息到 Broker,Broker 存储消息信息,Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤) -- Broker 同步双写和异步双写中 Master 和 Slave 的交互 - - -## 实现延迟队列 -rocketmq发送延时消息时先把消息按照延迟时间段发送到指定的队列中(rocketmq把每种延迟时间段的消息都存放到同一个队列中)然后通过一个定时器进行轮训这些队列,查看消息是否到期,如果到期就把这个消息发送到指定topic的队列中,这样的好处是同一队列中的消息延时时间是一致的,还有一个好处是这个队列中的消息时按照消息到期时间进行递增排序的,说的简单直白就是队列中消息越靠前的到期时间越早 - -缺点:定时器采用了timer,timer是单线程运行,如果延迟消息数量很大的情况下,可能单线程处理不过来,造成消息到期后也没有发送出去的情况 - -改进点:可以在每个延迟队列上各采用一个timer,或者使用timer进行扫描,加一个线程池对消息进行处理,这样可以提供效率 - - -## 消息队列如何保证顺序消费 -生产者中把 orderId 进行取模,把相同模的数据放到 messagequeue 里面,消费者消费同一个 messagequeue,只要消费者这边有序消费,那么可以保证数据被顺序消费。 - -RocketMQ:顺序消息一般使用集群模式,是指对消息消费者内的线程池中的线程对消息消费队列只能串行消费。并并发消息消费最本质的区别是消息消费时必须成功锁定消息消费队列,在Broker端会存储消息消费队列的锁占用情况。 -[https://blog.csdn.net/AAA821/article/details/86650471](https://blog.csdn.net/AAA821/article/details/86650471) - -## 消息存储 -### 存储过程 -1. 消息生产者发送消息 -2. MQ收到消息,将消息进行持久化,在存储中新增一条记录 -3. 返回ACK给生产者 -4. MQ push 消息给对应的消费者,然后等待消费者返回ACK -5. 如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,即执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤 -6. MQ删除消息 - -想说一点,activeMQ的存储介质DB,这就影响了存储效率,其他几位MQ采用的文件系统,并且依照顺序写,极大跟随了SSD的步伐。 - -### 零拷贝 -都知道内核和用户态了,不必多说了 -1. 从磁盘复制数据到内核态内存; -2. 从内核态内存复制到用户态内存; -3. 然后从用户态内存复制到网络驱动的内核态内存; -4. 最后是从网络驱动的内核态内存复制到网卡中进行传输。 - -但,通过使用mmap的方式,可以省去向用户态的内存复制,提高速度。这种机制在Java中是通过MappedByteBuffer实现的(零拷贝) - -### 存储结构 -RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成的,消息真正的物理存储文件是CommitLog,ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。 - -- CommitLog:存储消息的元数据 -- ConsumerQueue:存储消息在CommitLog的索引 -- IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程 - -## 刷盘机制 -**同步机制**:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立刻通知刷盘线程刷盘, 然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。 -1. 封装刷盘请求 -2. 提交刷盘请求 -3. 线程阻塞5秒,等待刷盘结束 - -服务那边: -1. 加锁 -2. 遍历requestsRead -3. 刷盘 -4. 唤醒发送消息客户端 -5. 更新刷盘监测点 - - -**异步机制**:在返回写成功状态时,消息**可能**只是被写入了内存的PAGECACHE,写操作的返回快,吞吐量大;当内存里的消息量积累到一定程度时,统一触发写磁盘动作,快速写入。 - -在消息追加到内存后,立即返回给消息发送端。如果开启transientStorePoolEnable,RocketMQ会单独申请一个与目标物理文件(commitLog)同样大小的堆外内存,该堆外内存将使用内存锁定,确保不会被置换到虚拟内存中去,消息首先追加到堆外内存,然后提交到物理文件的内存映射中,然后刷写到磁盘。如果未开启transientStorePoolEnable,消息直接追加到物理文件直接映射文件中,然后刷写到磁盘中。 - -开启transientStorePoolEnable后异步刷盘步骤: - -1. 将消息直接追加到ByteBuffer(堆外内存) -2. CommitRealTimeService线程每隔200ms将ByteBuffer新追加内容提交到MappedByteBuffer中 -3. MappedByteBuffer在内存中追加提交的内容,wrotePosition指针向后移动 -4. commit操作成功返回,将committedPosition位置恢复 -5. FlushRealTimeService线程默认每500ms将MappedByteBuffer中新追加的内存刷写到磁盘 - -## NameServer启动流程 -1. 解析配置文件,填充NameServerConfig、NettyServerConfig属性值,并创建NamesrvController -2. 根据启动属性创建NamesrvController实例,并初始化该实例。NameServerController实例为NameServer核心控制器 - 1. 创建NettyServer网络处理对象 - 2. 开启定时任务:每隔10s扫描一次Broker,移除不活跃的Broker - 3. 开启定时任务:每隔10min打印一次KV配置 -3. 在JVM进程关闭之前,先将线程池关闭,及时释放资源 - -## 路由管理 -### 心跳机制 -- RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。 -- Broker启动时向集群中所有的NameServer发送心跳信息,每隔30s向集群中所有NameServer发送心跳包,NameServer收到心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdataTimeStamp信息,然后NameServer每隔10s扫描brokerLiveTable,如果连续120S没有收到心跳包,NameServer将移除Broker的路由信息同时关闭Socket连接。 - -### 删除路由 -- `Broker`每隔30s向`NameServer`发送一个心跳包,心跳包包含`BrokerId`,`Broker`地址,`Broker`名称,`Broker`所属集群名称、`Broker`关联的`FilterServer`列表。但是如果`Broker`宕机,`NameServer`无法收到心跳包,此时`NameServer`如何来剔除这些失效的`Broker`呢? -- `NameServer`会每隔10s扫描`brokerLiveTable`状态表,如果`BrokerLive`的**lastUpdateTimestamp**的时间戳距当前时间超过120s,则认为`Broker`失效,移除该`Broker`,关闭与`Broker`连接,同时更新`topicQueueTable`、`brokerAddrTable`、`brokerLiveTable`、`filterServerTable`。 - -### 路由发现 -RocketMQ路由发现是非实时的,当Topic路由出现变化后,NameServer不会主动推送给客户端,而是由客户端定时拉取主题最新的路由。 - -其实就是一张图 -![](https://imgkr.cn-bj.ufileos.com/6ac077ee-bc03-4785-9200-426457563858.png) - -## 消息发送流程 -1. 验证消息 -2. 查找路由 - 1. 从缓存中获得主题的路由信息 - 2. 路由信息为空,则从NameServer获取路由 - 3. 如果未找到当前主题的路由信息,则用默认主题继续查找 -3. 选择队列(默认不启用Broker故障延迟机制) - 1. 第一次选择队列 - 1. sendWhichQueue自增 - 2. 对队列大小取模 - 3. 返回对应的队列 - 2. sendWhichQueue - 3. 遍历消息队列集合 - 4. sendWhichQueue自增后取模 - 5. 规避上次Broker队列 -4. 发送消息 - 1. 获得broker网络地址信息 - 2. 没有找到从NameServer更新broker网络地址信息 - 3. 为消息分类唯一ID - 4. 消息大小超过4K,启用消息压缩 - 5. 如果是事务消息,设置消息标记MessageSysFlag.TRANSACTION_PREPARED_TYPE - 6. 如果注册了消息发送钩子函数,在执行消息发送前的增强逻辑 - 7. 同步或异步发送 - 8. 如果有钩子,发送完执行钩子 - -## 消费消息流程 -就不那么详细了 -1. 客户端发起拉取请求 -2. 消息服务端Broker组装信息 -3. 消息拉取客户端处理消息 - -这里有一点: -拉取长轮询分析:源码上实际上还是个监听器 -- RocketMQ未真正实现消息推模式,而是消费者主动向消息服务器拉取消息,RocketMQ推模式是循环向消息服务端发起消息拉取请求,如果消息消费者向RocketMQ拉取消息时,消息未到达消费队列时,如果不启用长轮询机制,则会在服务端等待shortPollingTimeMills时间后(挂起)再去判断消息是否已经到达指定消息队列,如果消息仍未到达则提示拉取消息客户端PULL—NOT—FOUND(消息不存在); -- 如果开启长轮询模式,RocketMQ一方面会每隔5s轮询检查一次消息是否可达,同时一有消息达到后立马通知挂起线程再次验证消息是否是自己感兴趣的消息,如果是则从CommitLog文件中提取消息返回给消息拉取客户端,否则直到挂起超时,超时时间由消息拉取方在消息拉取是封装在请求参数中,PUSH模式为15s,PULL模式通过DefaultMQPullConsumer#setBrokerSuspendMaxTimeMillis设置。RocketMQ通过在Broker客户端配置longPollingEnable为true来开启长轮询模式。 - - -# zookeeper -## 什么是Zookeeper? -ZooKeeper 是一个开源的分布式协调服务 - -## Zookeeper使用场景? -1. 数据发布/订阅、 -2. 负载均衡、 -3. 命名服务、 -4. 分布式协调/通知、 -5. 集群管理、 -6. Master 选举、 -7. 分布式锁和分布式队列等功能。 - -## Zookeeper的特点 -- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 -- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 -- 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 -- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 - -## Zookeeper的原理? -ZAB 协议&Paxos算法 -ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 - -## 选择算法和流程 -目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: - -1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 -2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 -3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 -4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 -5. 服务器5启动,后面的逻辑同服务器4成为follower。 - -[https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) - -## 客户端源码 -- ZooKeeper实例 :客户端入口 -- ClientWatcherManager :客户端Watcher管理器 -- HostProvider:客户端地址列表管理器 -- ClientCnxn:客户端核心线程。包含两个线程,即SendThread和EventThread。前者是一个I/O线程,主要负责ZooKeeper客户端和服务端之间的网络I/O通信,后者是一个事件线程,主要负责对服务端事件进行处理。 -[http://www.guobingwei.tech/articles/2019/03/15/1552608908888.html](http://www.guobingwei.tech/articles/2019/03/15/1552608908888.html) - -## 服务端源码 -[http://www.guobingwei.tech/articles/2019/03/19/1552949363416.html](http://www.guobingwei.tech/articles/2019/03/19/1552949363416.html) -[http://www.guobingwei.tech/articles/2019/03/23/1553295398628.html](http://www.guobingwei.tech/articles/2019/03/23/1553295398628.html) - -## 选举源码 -[http://www.guobingwei.tech/articles/2019/03/29/1553815655905.html](http://www.guobingwei.tech/articles/2019/03/29/1553815655905.html) - -## 常见面试题 -[https://www.cnblogs.com/ibigboy/p/11356221.html](https://www.cnblogs.com/ibigboy/p/11356221.html) - - -# 限流 -## 为什么选择Sentinel? -Sentinel是一个面试分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 - -隔离策略:信号量隔离(并发线程数限流) - -熔断策略: -1. 基于响应时间 -2. 异常比率 -3. 异常数 - -限流:基于QPS限流 - -控制台:查看秒级监控、机器发现等。 - -## 服务限流 -当**系统资源不够,不足以应对大量请求**,对系统按照预设的规则进行流量限制或功能限制 - -## 服务熔断 -当**调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务**,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 - -## 服务降级 -**为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级**,有策略的不处理或用简单的方式处理 - -## 为什么熔断降级 -系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 - -## 和Hystrix对比 -![](https://imgkr.cn-bj.ufileos.com/8f2cf909-4c41-47c3-89d9-d6eb2676a519.png) - -**值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案**虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响**。 - -Sentinel 并发线程数限流不负责创建和管理线程池,而是**简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离**。 - -[官网补充](http://dubbo.apache.org/zh-cn/blog/sentinel-introduction-for-dubbo.html) - - -# 常问的算法 -## paxos算法 -> 要讲这个算法,还要先扯背景:在常见的分布式系统中,总会发生诸如机器宕机或网络异常(等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。 - -> 其实在整个提议和投票过程当中,主要的角色就是“提议者”和“接受者” - -该算法大致流程:其实分为两个阶段 - -1. 因为存在多个“提议者”Proposer,如果都提意见,那么“接受者”Acceptor不就炸掉了嘛?到底接受谁啊?所以,要先明确哪个“提议者”是领袖,最厉害的那个,先把这个给挑出来。尽早的让意见统一,并且早点形成多数派。 -2. 由上阶段选出的意见领袖提出提议,“接受者”反馈意见。如果多数“接受者”接受了一个提议,那么这个提议就通过了。 - -[例子](https://ocavue.com/paxos.html#%E8%8A%82%E7%82%B9%E6%95%85%E9%9A%9C%E7%9A%84%E4%BE%8B%E5%AD%90) - - -## ZAB -- ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。 -- 当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。 -- 当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 - -## zk的leader选举算法和流程 -目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: - -1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 -2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 -3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 -4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 -5. 服务器5启动,后面的逻辑同服务器4成为follower。 - -[https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) - -## raft - -[https://zhuanlan.zhihu.com/p/66441389](https://zhuanlan.zhihu.com/p/66441389) - -## Redlock - -假设有5个实例 - -1. 比如过期时间为TTL:10min -2. 记录当前时间:比如T1 = 12:00 -3. 客户端分别向5个实例获取锁,比如申请锁的时间依次为:12:01...12:05,最后获取实例的锁为T2:12:05(获取锁的超时时间要远远小于过期时间,防止死等。) -4. 如果获取锁的实例大于3个(过半机制),那么就相当于获取到锁了,该锁的真正的有效时间为TTL-(T2-T1) = 5min -5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 - -## 布隆过滤器原理 -- 布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中”。 -- 布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0 -- 为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1” - -总结:当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中。 -[5分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!](https://juejin.im/post/5de1e37c5188256e8e43adfc) - -## JWT -[JWT的优缺点](https://snailclimb.gitee.io/javaguide/#/docs/system-design/authority-certification/JWT-advantages-and-disadvantages) - -## 限流算法 -[https://zhuanlan.zhihu.com/p/95066428](https://zhuanlan.zhihu.com/p/95066428) - -# 项目 -## 微服务在线班车预约平台 - -### 项目描述 -该平台是针对电子科技大学班车预约平台利用最新的技术进行重构。其中主要提供沙河和清水河的班车场次的功能、下单功能、支付功能、退款功能等,并且按照学生需求增加相应的功能,比如未支付订单列表,未支付订单超时自动取消等。 - -### 设计技术 -> 1. 采用Dubbo的架构开发,整个项目分为用户、班车、订单、支付四个服务,达到易维护的效果 -> 2. 基于 JWT 的 SSO 单点登录,并依携带的 Token 可以访问系统中其他服务,采用 Redis 缓存绑定用户,达到用户登录一次处处能访问各个系统。 -> 3. 采用 Redis 的 list 数据结构缓存班车场次列表,并基于 Spring 定时器优化班车场次到点更新班车 状态的业务,最后配合阿里巴巴开源的 Sentinel 中间件进行接口限流达到高并发、高可用的效果。 -> 4. 下单和支付服务均采用基于阿里巴巴开源的 RocketMQ 消息中间件保持数据的最终一致性,并且 采用 Redis 缓存维持 RocketMQ 消息的幂等性,接着采用 RocketMQ 和 Sentinel 进行接口限流维护系统的稳定性,最后采用 Redis 的监听 key 键过期事件保证未支付订单超时自动取消业务,达到高 并发、高可用的效果。 -> 5. 采用 Dubbo 的负载均衡机制将班车服务、订单服务分配到不同的服务器上,达到高性能的效果。 - -#### 显示场次列表的业务逻辑 - -逻辑: -- 找出符合当前天(比如,5.30)(说明,只有当天的车次) -- 找出大于等于当前时间的场次(比如,数据库8点有一场,目前时间为7点,它就符合) -- 找出状态为getBusStatus的场次,一般是还未发车的场次,(比如0,1)(0:沙河,1:清水河) - -#### 判断座位是否已重复业务逻辑 - -- 根据场次id查找当前数据库中的该场次的座位 -- 使用HashSet的集合判断当前座位和数据库中的座位是否重复 - -#### 回退座位的业务逻辑 -- 根据场次id查找当前数据库中的该场次的座位 -- 使用HashSet的集合判断当前座位和数据库中的座位是否重复 -- 重复则移除 - - -### 下单和支付 -#### 下单逻辑 - -- 判断座位,如果重复,直接退出,否则下一步 -- 更新座位,如果没有异常,这是写操作 -- 计算总金额,如果没有异常 -- 添加订单 - -#### 支付逻辑 - -- 先核对支付密码是否正确,若不对,则返回 -- 核对余额是否够,若不够,则返回 -- 更改订单状态(->依支付) - -#### 退款逻辑 - -- 退回金额,读取用户金额,计算金额,更新 -- 退回座位 -- 更改订单状态(->已退款) - - - -### 上线bug - -> 项目上线之后,大概将近一个月,我点击车次列表页面,突然什么都没有了,于是就开始找哪个不顺眼的家伙搞的鬼。 - -#### 起因 - -访问主页[http://47.104.22.225:8080/home](http://47.104.22.225:8080/home) - - -一直加载中,于是我看一下响应信息。 - -#### 查看一下gateway的日志 -- 终端显示不长,于是我将日志用vscode打开看 - -```log -org.apache.dubbo.rpc.RpcException: No provider available from registry 39.108.93.119:2181 for service com.stylefeng.guns.rest.bus.IBusService on consumer 192.168.31.221 use dubbo version 2.7.4.1, please check status of providers(disabled, not registered or in blacklist). -``` - -类似于这样的信息,说我们的bus服务没有注册。 - -> 注意:出事之前,几个服务都在的呀,怎么今天bus突然不在了。于是,我不相信,我就去dubbo后台看了一下... - - -**此时,还真没有bus服务** -![](https://imgkr.cn-bj.ufileos.com/3eb38910-64b5-439c-8437-c155d50857d2.png) - - -**于是乎,俺又不想重新启动服务,毕竟你看** - -- 终端输入`ps -ef | grep guns-bus` -- 我们看到了惊人的一幕 -```shell -pch 2003942 1 0 4月16 ? 06:10:54 java -jar guns-bus-0.0.1.jar -``` - -#### 猜测 - -##### DubboAdmin展示? -好像没问题吧?这样的话,其他三个也应该不存在的啊 - -##### 注册中心,bus节点丢了 - -- 查看日志,当天出现zk出现了大量的超时,原因是当天的zk**主节点**宕机了。 - -##### 找原因 - -**问题是否出现在了dubbo对zk重连恢复数据这块,开始查源码。注册中心源码ZookeeperRegistry。** - -1. 连接注册zk:通过zkclient添加zk状态监听。并且继承了FailbackRegistry各种失败重试。 -2. zk客户端:默认使用CuratorZookeeperClient实现 -3. 重试任务:注册重新失败重连任务FailbackRegistry中的DubboRegistryFailedRetryTimer,默认5秒检查一次是否需要失败恢复 - - -#### 总结一波 -通过三部分的代码我们可以推断,如果zk状态监听与恢复部分出现问题可能会导致数据丢失问题。于是查看相关的api并且尝试查看dubbo社区的问题与bug,果然发现了类似问题的修改与原因分析:[https://github.com/apache/dubbo/pull/5135](https://github.com/apache/dubbo/pull/5135) - -原因: -> 如果ZNode数据已经存在,在会话超时期间,此时我们将重建一个数据节点,这个重复的异常原因可能是由于zk server中老的超时会话依然持有节点导致该节点的delete删除事件延迟,并且zk server还没有来得及去执行删除,可能由这种场景引起。在这个情景下,我们可以本地删除节点后再创建恢复节点数据。 - -其实说白了: -> 如果会话断开连接又重新连接成功。断开连接发出的删除节点事件,**因为延迟原因走在了重新连接恢复节点事件的后面**。**导致重新连接后没能成功恢复节点**。也就是说,即使恢复了,又被删除了,也就是我么见到的,provider有三个节点服务正常,但是zk注册中心中的提供者节点数据丢失,导致出现该节点对其他订阅者不可见的现象 - -```java - protected void createEphemeral(String path, String data) { - byte[] dataBytes = data.getBytes(CHARSET); - - try { - ((ACLBackgroundPathAndBytesable)this.client.create().withMode(CreateMode.EPHEMERAL)).forPath(path, dataBytes); - } catch (NodeExistsException var5) { - logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration, this duplication might be caused by a delete delay from the zk server, which means the old expired session may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, we can just try to delete and create again.", var5); - this.deletePath(path); - this.createEphemeral(path, data); - } catch (Exception var6) { - throw new IllegalStateException(var6.getMessage(), var6); - } - - } -``` - -**人家源码的warn写的是真清楚**... - -#### 定位cpu过高的线程或者位置 - -> 这里要用到几个命令了,比如top,jstack等。 - -##### 查看一波 -- 终端输入`top -H` - -![](https://imgkr.cn-bj.ufileos.com/e69d6335-c260-4d69-9f4d-840b7427cce5.png) -- 类似于长这样,但是有些含义就不解释了,请上互联网 -- 我就挑它吧`1227027` -- 终端输入`top -Hp 1227027` -![](https://imgkr.cn-bj.ufileos.com/d703c8ae-a3a8-4eed-ad57-6c3440dd4ea7.png) -- 这里我也就不介绍了,找一个`1227029` -- `printf "%x\n" 1227029`结果是:`12b915` -- `jstack -l 1227027| grep 0x12b915 -A 10` - -```shell -"DestroyJavaVM" #84 prio=5 os_prio=0 tid=0x00007f69f800b7c0 nid=0x12b915 waiting on condition [0x0000000000000000] - java.lang.Thread.State: RUNNABLE - - Locked ownable synchronizers: - - None - -"BrokerFastFailureScheduledThread1" #83 prio=5 os_prio=0 tid=0x00007f69f8b29050 nid=0x12ba0a runnable [0x00007f68b6bec000] - java.lang.Thread.State: TIMED_WAITING (parking) - at sun.misc.Unsafe.park(Native Method) - - parking to wait for <0x0000000080594a60> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) - at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) -``` -**以上就是举个例子** ... - -## 面向多源藏汉信息采集、融合和分析系统平台 - -### 项目描述 -该平台是面向藏汉多源信息的采集和挖掘,主要提供的功能为自动采集相关多源数据、搜索引擎和数据分析等功能。 - -### 设计技术 -- 采用 Python 开发的 Scrapy 爬虫组件,其利用 URL 设计布隆过滤器,达到了数据去重的效果。 -- 将采集的信息存入 MongoDB,达到了将非关系数据存储和稳定提供给分析平台的效果。 - -### 有可能聊计算机网络的知识 - -### Scrapy是什么? -Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。只需要编写很少的代码就能实现抓取功能,另外由于它底层用了twisted,性能也非常优越。使用Scrapy框架编写的抓取代码,可读性很强,非常利于维护,是现在最流行的抓取框架。 - -- Scrapy Engine(引擎):负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。 -- Scheduler(调度器):它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。 -- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理, -- Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器), -- Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方. -- Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。 -- Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests) - -### 布隆过滤器原理 -- 布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中”。 -- 布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0 -- 为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1” - -总结:当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中。 -[5分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!](https://juejin.im/post/5de1e37c5188256e8e43adfc) - - -### MongoDB是什么? -MongoDB 是一种NoSQL 数据库,存储的数据对象由键值对组成。MongoDB 所有存储在集合中的数据都是 BSON 格式。BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。 - -#### 和Redis的区别是什么? -- MongoDB 更类似 MySQL,支持**字段索引、游标操作**,其优势在于查询功能比较强大,**擅长查询 JSON 数据**,能存储海量数据,但是不支持事务。 - -- Redis 是一个开源(BSD许可)的,**内存中的数据结构存储系统**,它可以用作数据库、缓存和消息中间件。 -- Redis 数据**全部存在内存,定期写入磁盘,当内存不够时,可以选择指定的 LRU 算法删除数据**。 -- Mongodb的所有数据实际上是**存放在硬盘的**,所有要操作的数据通过mmap的方式映射到内存某个区域内。 -- Redis所有数据都是放在内存中的,持久化是使用RDB方式或者aof方式。 - -#### MongoDB为什么采用B树 -- B+树查询时间复杂度固定是logn,B-树查询复杂度最好是 O(1)。 -- B+树更适合外部存储,也就是磁盘存储。由于内节点无 data 域,每个节点能索引的范围更大更精确。 - -在聊mongodb是什么 -MongoDB 是文档型的数据库,是一种 nosql,它使用类 Json 格式保存数据。 -MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,无疑单次查询平均快于Mysql。 - -再说说MySQL -Mysql作为一个关系型数据库,数据的**关联性**是非常强的,**区间访问**是常见的一种情况,B+树由于**数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历**。 - -## 基于 BP 神经网络个性化搜索引擎软件 - -### 项目描述 -该平台是面向某大学的新闻官网提供个性化搜索服务如,用户查询、管理员管理 BP 神经网络算法。 其中管理员管理 BP 神经网络算法是该平台“个性化”的功能体现。 - -### 设计技术 -- 采用 Vue 和 Flask 搭建前后端分离,达到了结构简单、业务分工且易维护等效果。 -- 采用 BP 神经网络搭建文章个性化排序模型,并针对于某大学通过爬虫组件采集文章制作数据集。 -- 采用 Elasticsearch 分布式、开源的搜索数据分析引擎建立模型,达到存储(所有基本类型)、分 析、查询、汇总、聚合等效果。 - - -### 为什么使用Vue? -- 简单 -- 易用 -- 数据双向绑定 -- 渐进式JavaScript -- 可以在谈一谈和react的区别 - -### 为什么使用flask? -- 简单 -- 易用 -- 纯python - -### 为什么使用BP神经网络? -- 它可以通过不断的训练,使得让网络更加智能。 -- 它可以模拟并很友好的支持非线性问题 -- 简单:三层(输入,隐藏,输出) -- 可以举个例子最好。。。 - -### 为什么采用ElasticSearch? -- Elasticsearch **分布式、开源的搜索数据分析引擎**建立模型,达到存储(所有基本类型)、分析、查询、汇总、聚合等效果。 -- **精确匹配和相关性匹配**(比如我搜「莎士比亚」,我要的肯定不只是精精确确包含「莎士比亚」的文稿,我可能还要搜「莎翁」、「Shakespeare」、「哈姆雷特」、「罗密欧和朱丽叶」、「威尼斯的商人」… 又比如我输错了,输成「莎士笔亚」,「相关性匹配」可以智能的帮我优化为「莎士比亚」,返回对应的搜索结果。) -- 搜索和分析,不只是搜索,还有分析 -- Mysql基于B+树索引,来实现快速检索,ES则基于倒排索引,对于文档搜索来说,倒排索引在性能和空间上都有更加明显的优势。 - -### BP和ES怎么结合的? -- 先说BP吧,以文章的标题,内容,词频建立模型 -- 首先ES检索出文章,其次使用BP进行预测文章的权值 -- 最后按照权值进行排序,将结果返回给用户 - -## 基于数据爬虫与图像识别的大学一卡通业务交易平台 - -### 项目描述 -该平台是面向大学的一卡通管理,其中包括用户登陆、查询剩余金额、查询消费记录、查询宿舍电费 和缴纳宿舍电费插 5 个功能。 - -### 设计技术 -- 采用 PyQT5 组件库搭建用户界面,并设计用户登录、查询剩余金额、查询消费记录、查询宿舍电 费和缴纳宿舍电费 5 个功能,达到了跨平台的效果。 -- 采用 CNN 搭建解决学校网站登录验证码的自动识别模型,其中针对于某大学通过爬虫组件不断采 集登录验证码制作数据集,正确率接近为 94%。 - -### 为什么使用CNN? -良好的容错能力,可处理环境信息复杂,背景知识不清楚,推理规则不明确情况下的问题,允许样品有较大的缺损,运行速度快,自适应性能好,具有较高的分辨率。 **它是通过结构重组和减少权值将特征抽取功能融合进多层感知器,省略识别前复杂的图像特征抽取过程**。 - - -# 设计模式 -## 懒汉(双重校验) -```java -class Singleton { - private static volatile Singleton instance = null; - - private Singleton() { - } - - public static Singleton getInstance() { - if (instance == null) { - synchronized (Singleton.class) { - if (instance == null) { - instance = new Singleton(); - } - } - } - return instance; - } -} -``` - -## 工厂 -> 定一个面条抽象类 - -```java -abstract class INoodles { - /** - * 描述每种面条长什么样的... - */ - public abstract void desc(); -} - -``` -> 兰州拉面 - -```java -class LzNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("兰州拉面,成都的好贵 家里的才5-6块钱一碗"); - } -} -``` -> 泡面 - -```java -class PaoNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("泡面可还行..."); - } -} -``` - -> 工厂(面馆) - -```java -class SimpleNoodlesFactory { - public static final int TYPE_LZ = 1; // 兰州拉面 - public static final int TYPE_PAO = 2; // 泡面撒 - // 提供静态方法 - public static INoodles createNoodles(int type) { - switch (type) { - case TYPE_LZ:return new LzNoodles(); - case TYPE_PAO:return new PaoNoodles(); - default:return new ZaNoodles(); - } - } -} -``` - -> 测试 - -```java -public class FactoryMode { - public static void main(String[] args) { - INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_PAO); - noodles.desc(); - } -} -``` - -## 代理 -> 所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。 - -### 静态 -> 主题 - -```java -interface Subject { - void visit(); -} -``` -> 实现Subject的目标对象类 - -```java -class RealSubject implements Subject { - private String name = "dreamcat"; - @Override - public void visit() { - System.out.println(name); - } -} -``` - -> 目标对象的代理类也得实现该接口 - -```java -class ProxySubject implements Subject { - - private Subject subject; - - public ProxySubject(Subject subject) { - this.subject = subject; - } - - @Override - public void visit() { - subject.visit(); - } -} -``` - -### 动态 -```java -class DynamicProxy { - - private Object target; - - DynamicProxy (Object target) { - this.target = target; - } - - public Object getProxyInstance() { - return Proxy.newProxyInstance( - target.getClass().getClassLoader(), - target.getClass().getInterfaces(), - (proxy, method, args) -> { - System.out.println("我是动态代理,兄弟..."); - Object value = method.invoke(target, args); - System.out.println("代理结束了,兄弟..."); - return value; - }); - } - -} - -``` - -### CGlib -```java -class RealSubject2 { - void visit() { - System.out.println("cat visit Dream"); - } -} - -class CgLibProxy implements MethodInterceptor { - private Object target; - - CgLibProxy(Object target) { - this.target = target; - } - - public Object getProxyInstance() { - // 1. 创建一个工具类 - Enhance en = new Enhance(); - // 2. 设置父类 - en.setSuperClass(target.getClass()); - // 3. 设置回调函数 - en.setCallback(this); - // 4. 创建子类对象,代理对象 - return en.create(); - - } - - public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) Throwable{ - System.out.println("Cglib代理..."); - Object value = method.invoke(target, args); - System.out.println("结束"); - return value; - } -} -``` - -> 测试 - -```java -public class ProxyMode { - public static void main(String[] args) { - // 静态代理 - ProxySubject proxySubject = new ProxySubject(new RealSubject()); - proxySubject.visit(); - - // 动态代理 - Subject proxyInstance = (Subject)new DynamicProxy(new RealSubject()).getProxyInstance(); - proxyInstance.visit(); - - // CG - RealSubject2 subject2 = (RealSubject2)new CgLibProxy(new RealSubject2()).getProxyInstance(); - subject2.visit(); - } -} - -``` - -[https://www.cnblogs.com/carpenterlee/p/8241042.html](https://www.cnblogs.com/carpenterlee/p/8241042.html) - - - - -# 大数据和空间限制与系统设计 -## 100亿黑名单URL,每个64B,判断一个URL是否在黑名单中 -[布隆过滤器...](https://juejin.im/post/5c959ff8e51d45509e2ccf84) - -## 2GB内存在20亿整数中找到出现次数最多的数 -[哈希多个文件](https://blog.csdn.net/u013246898/article/details/52033937) - -## 40亿个非负整数中找到没有出现的数 -[分区加位图](https://blog.csdn.net/u010456903/article/details/48806947) - -## 找到100亿个URL中重复的URL/海量搜索词汇,找到最热TOP100词汇的方法 -[哈希分流](https://blog.csdn.net/weixin_41362649/article/details/94601249) - -## 40亿个无符号整数,1GB内存,找到所有出现两次的数/10MB内存,找到40亿整数的中位数 -[位图](https://blog.csdn.net/liyutaogege/article/details/104394790) - -## 设计短域名系统,将长URL转化成短的URL. -利用放号器,初始值为0,对于每一个短链接生成请求,都递增放号器的值,再将此值转换为62进制(a-zA-Z0-9),比如第一次请求时放号器的值为0,对应62进制为a,第二次请求时放号器的值为1,对应62进制为b,第10001次请求时放号器的值为10000,对应62进制为sBc。 -[发号器](https://blog.csdn.net/u010870518/article/details/80026452) - -# 智力题 -- 赛马找最快<腾讯高频> -- 砝码称轻重 -- 绳子两头烧 -- 犯人猜颜色 -- 猴子搬香蕉 -- 高楼扔鸡蛋<谷歌> -- 轮流拿石子<头条> -- 蚂蚁走树枝 -- 海盗分金币<不常见> -- 三个火枪手 -- 囚犯拿豆子 -- 学生猜生日<笔试高频> - -- [分硬币](https://blog.csdn.net/we_are_the_world_123/article/details/78012100) - -[答案解析](https://www.nowcoder.com/discuss/262595) - -# 操作系统 -## 进程和线程的区别 - -- 进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运⾏⼀个程序即是 ⼀个进程从创建,运⾏到消亡的过程。 -- 线程是⼀个⽐进程更⼩的执⾏单位 -- ⼀个进程在其执⾏的过程中可以产⽣多个线程 -- 与进程不同的是同类的多个线程共享进程的堆和⽅法区资源,但每个线程有⾃⼰的程序计数器、 虚拟机栈和本地⽅法栈,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要 ⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程 - -⽐如:当我们启动 main 函数时其实就是启动了⼀个 JVM 的进程,⽽ main 函数所在的线程就是这个进程中的⼀个线程,也称主线程。 - -## 协程? - -协程是一种用户态的轻量级线程, 我们的server的发展如下:IO密集型应用:多进程 -> 多线程 ->事件驱动 ->协程 - -协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,然后去做其他工作,当你的IO解除之后切回原来的状态,恢复先前保存的寄存器上下文和栈。 - -协程能保留上一次调用时的状态(既所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态. 也就是相当于LOL艾克的R。 - -优点: - -- 跨平台 -- 无需线程上下文切换的开销 -- 无需原子操作锁定及同步的开销 -- 方便切换控制流,简化编程模型 -- 高并发+高扩展行+低成本: 一个CPU支持上万的协程都不是问题,所以很适合用于高并发处理 - -缺点: - -- 无法利用多核资源:协程的本质是一个单线程,它不能同时将单个CPU的多个核作用上,协程需要和进程配合才能运行在多CPU上. -- 进行阻塞(Blocking)操作会阻塞到整个程序; 这一点和事件驱动一样,可以使用异步IO操作来解决. - -## 进程之间的通信 - -> 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。 - -1. 管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;(pipe) -2. FIFO命名管道:FIFO是一种文件类型,可以在无关的进程之间交换数据,与无名管道不同,FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。(mkfifo) -3. 消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。 -4. 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生; -5. 信号量:信号量是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。 -6. 共享内存:共享内存指两个或多个进程共享一个给定的存储区,一般配合信号量使用。(mmap) -7. 套接字:这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。(socket) - - -### 进程间五种通信方式的比较 -1. 管道:速度慢,容量有限,只有父子进程能通讯。 -2. FIFO:任何进程间都能通讯,但速度慢。 -3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。 -4. 信号量:不能传递复杂消息,只能用来同步。 -5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。 - -### 为什么共享内存快 -采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 - -内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存 - -### 共享内存的实现(mmap) -mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。 - -### 系统调用mmap共享内存的两种方式 -- 使用普通文件提供的内存映射:适用于任何进程之间; -- 使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间; 由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。 - -## 死锁 -- 互斥 -- 占有等待 -- 不能剥夺 -- 循环等待 - -举例子:还是吃饭(兄妹吃饭哈) -比如有两个菜哈,土豆丝和西红柿鸡蛋 -首先哥哥先把土豆丝端到自己旁边,而妹妹把西红柿鸡蛋端到自己旁 -此时呢?哥哥也想吃妹妹的西红柿鸡蛋,但是妹妹还没放回去,妹妹其实也想吃哥哥的土豆丝,但是哥哥也没放回去,俩人等了老半天,一直在等,等到天黑都没放回去。害 - -解决: -预防死锁、避免死锁、检测死锁、解除死锁 、鸵鸟策略 - -## 用户态和内核态 - -> 用户态就是提供应用程序运行的空间,为了使应用程序访问到内核管理的资源例如CPU,内存,I/O。内核必须提供一组通用的访问接口,这些接口就叫**系统调用。** - -**系统调用**时操作系统的最小功能单位。根据不同的应用场景,不同的Linux发行版本提供的系统调用数量也不尽相同,大致在240-350之间。这些系统调用组成了用户态跟内核态交互的基本接口,**例如:用户态想要申请一块20K大小的动态内存,就需要brk系统调用,将数据段指针向下偏移,如果用户态多处申请20K动态内存,同时又释放呢?这个内存的管理就变得非常的复杂。** - -从用户态到内核态切换可以通过三种方式: - -- 系统调用,这个上面已经讲解过了。其实系统调用本身就是中断,但是软件中断,跟硬中断不同。 -- 异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,就会触发切换。例如:缺页异常。 -- 外设中断:当外设完成用户的请求时,会向CPU发送中断信号。 - -## 操作系统内存管理方式,分页分段以及段页式的优缺点 - -### 分页管理 - -分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页加以编号,从0开始,如第0页、第1页等。相应地,也把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或页框(frame),也同样为它们加以编号,如0#块、1#块等等。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“页内碎片”。 - -**优缺点**:**没有外部碎片,内存利用率高。但各页中内容没有关联,不利于编程和共享**。 - -### 分段管理 - -程序通过分段(segmentation)划分为多个模块,如代码段、数据段、共享段。内存每段的大小都匹配程序段,不会产生内部碎片。 -**优缺点**: 可以针对不同类型的段采取不同的保护。 可以按段为单位来进行共享,包括通过动态链接进行代码共享。 **不会产生内部碎片,但会产生外部碎片,内存利用率比分页低**。 - -### 段页式管理 - -一个进程中所包含的具有独立逻辑功能的程序或数据仍被划分为段,并有各自的段号s。这反映相继承了段式管理的特征。其次,对于段s中的程序或数据,则按照一定的大小将其划分为不同的页。和页式系统一样,最后不足一页的部分仍占一页。这反映了段页式管理中的页式特征。从而,段页式管理时的进程的虚拟地址空间中的虚拟地址由三部分组成:即段号s,页号P和页内相对地址d。虚拟空间的最小单位是页而不是段,从而内存可用区也就被划分成为若干个大小相等的页面,且每段所拥有的程序和数据在内存中可以分开存放。分段的大小也不再受内存可用区的限制。 -**优缺点:**既有具有独立逻辑功能的段,又以大小相同的页为内存分配单位进而不会产生外部碎片。但仍会有内部碎片。 - -[操作系统如管理内存](https://blog.csdn.net/hguisu/article/details/5713164) - -## 页面置换算法有哪些,FIFO为什么不好?如何改进?LRU思想,手写LRU - -> 地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断。当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。常见的置换算法有: - -- 最佳置换算法(OPT):所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。 -- 先进先出置换算法(FIFO):选择换出的页面是最先进入的页面。该算法会将那些经常被访问的页面换出,导致缺页率升高。 -- 第二次机会算法:当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。 -- 最近最久未使用(LRU)算法:为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。 - - -[https://blog.csdn.net/wangsifu2009/article/details/6757352](https://blog.csdn.net/wangsifu2009/article/details/6757352) - -## 虚拟内存 - -虚拟内存允许执行进程不必完全在内存中。虚拟内存的基本思想是: -- 每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。 -- 这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。 -- 当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上。 - -举个小例子: -吃饭。 - -比如你和同学去聚餐,根据菜谱点了一堆的菜,有汤,有肉,有素材等。 大可不必一下子全做完一起送过来(如果桌子不给力,比较小,并且时间成本,饭菜都凉了等),比如,同学想吃肉哈?不想先喝汤,那就把肉类端上来,对吧?等同学又想喝汤了,就把汤做一下端过来。 - -[https://blog.csdn.net/lvyibin890/article/details/82217193](https://blog.csdn.net/lvyibin890/article/details/82217193) - -## 同步和互斥的区别 -### 同步 -同步,又称直接制约关系,是指多个线程(或进程)为了合作完成任务,必须严格按照规定的 某种先后次序来运行。 - -### 互斥 -互斥,又称间接制约关系,是指系统中的某些共享资源,一次只允许一个线程访问。当一个线程正在访问该临界资源时,其它线程必须等待。 - -### 信号量 -信号量的本质是一种数据操作锁、用来负责数据操作过程中的互斥、同步等功能。 -信号量用来管理临界资源的。它本身只是一种外部资源的标识、不具有数据交换功能,而是通过控制其他的通信资源实现进程间通信。 -可以这样理解,信号量就相当于是一个计数器。当有进程对它所管理的资源进行请求时,进程先要读取信号量的值,大于0,资源可以请求,等于0,资源不可以用,这时进程会进入睡眠状态直至资源可用。 - -## 操作系统中进程调度策略有哪几种 -- FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU -- SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度 -- 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化 -- 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 -- 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。 -- 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。 - -[https://blog.csdn.net/justloveyou_/article/details/78304294](https://blog.csdn.net/justloveyou_/article/details/78304294) - - - -# 项目思考 - -> 碍于面试官喜欢出刁难的场景... -1. 如何确保userid是安全的?(分布式唯一ID)? Redis的value不存一些安全信息?不怕被攻击?有意思是别人不管通过什么手段获取到userID,然后拿到token,不断的黑嘛?这哥们更狠,源码被窃取了,知道算法加密了,怎么办? - -这个问题,我就。。。 我自己都没思考过,在这么短的时间内,我果断没有想出来!! 经验不足 - -事后,我思考一下: -如果,我再签证的时候,我可以用版本号? 什么意思? 前端第一次登陆,不仅签证token,还要生成一个唯一id(可以设备唯一id和时间戳)作为版本号。 - -如果还刁难, 就用https的ssl存私密数据!!!! - -如果说客户端一直狂刷,狂调用, 那就同一个设备id进行在一段时间内,限制他访问的次数,拿到userid不断攻击也没用 - -你说源码被窃取,我就~~~~~ 我就撒撒谁啊 - -启动其他备用源码的加密方案, 更换方案 再刁难? 还不行? -那我就折服了!!! 超过了我的经验和知识盲区了。 - -2. 幂等在redis写消费记录,如何保证写过程当中没有出现问题?出现问题怎么办? -这个问题, 一开始想的是失败就重写被, 还能咋地,没敢说出来。。 我再思考思考 ,,, 问的太刁难我了 -那就这样,如果写失败了, 没写进去, 我tm当第二次消费的时候, 肯定缓存数据库是没有的,老子就查一下数据库的状态,确保万无一失是否消费过!!! (整个消费记录表!!!)。 问的这么刁难我, 那我就数据库和Redis一起用。。 - -难不成用自旋锁? - -3. 如果订单过期了那一瞬间,后端的监听事件收到了,还没更改状态,用户开始支付,这个时候怎么办?当用户支付成功了,你把状态又改为失效了。 - -这个瞬间我就想出来了。。。。。。 -瞬间就给你扯分布式锁 - - -# Android - -## Activity生命周期 -Activity 类提供六个核心回调:onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy()。当 Activity 进入新状态时,系统会调用其中每个回调。 - -- onCreate():Activity 会在创建后进入“已创建”状态,调用该方法 -- onStart():当 Activity 进入“已开始”状态时,系统会调用此回调 -- onResume():Activity 会在进入“已恢复”状态时来到前台 -- onPause():系统将此方法视为用户将要离开您的 Activity 的第一个标志(尽管这并不总是意味着 Activity 会被销毁) -- onStop():如果您的 Activity 不再对用户可见,说明其已进入“已停止”状态 -- onDestroy():销毁 Ativity 之前,系统会先调用 onDestroy() - -## Activity启动的四种模式 -1. standard:标准模式:如果在mainfest中不设置就默认standard;standard就是新建一个Activity就在栈中新建一个activity实例;场景:邮件、mainfest中没有配置就默认标准模式 -2. singleTop:栈顶复用模式:与standard相比栈顶复用可以有效减少activity重复创建对资源的消耗,但是这要根据具体情况而定,不能一概而论;场景:登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏 -3. singleTask:栈内单例模式,栈内只有一个activity实例,栈内已存activity实例,在其他activity中start这个activity,Android直接把这个实例上面其他activity实例踢出栈GC掉;场景:程序模块逻辑入口:主页面(Fragment的containerActivity)、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面 -4. singleInstance :堆内单例:整个手机操作系统里面只有一个实例存在就是内存单例;场景:系统Launcher、锁屏键、来电显示等系统应用 diff --git "a/Interview/myself/\344\270\252\344\272\272\345\210\267\347\206\237\351\242\230.md" "b/Interview/myself/\344\270\252\344\272\272\345\210\267\347\206\237\351\242\230.md" deleted file mode 100644 index 54f82f46..00000000 --- "a/Interview/myself/\344\270\252\344\272\272\345\210\267\347\206\237\351\242\230.md" +++ /dev/null @@ -1,3375 +0,0 @@ -> 看了很多面经,也有一些面试常见的题,只是希望多熟练一些,保证自己能在有效的时间写对,这是最关键的。 -> 面试代码不可能太长的,而且都是高频热点。 - -## 链表 - -### 1、[从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T3 { - // 创建list - ArrayList list = new ArrayList<>(); - public ArrayList printListFromTailToHead(ListNode listNode) { - // 判断头节点是否为空 - if (listNode != null) { - // 递归打印 - this.printListFromTailToHead(listNode.next); - list.add(listNode.val); - } - return list; - } -} -``` - -### 2、[删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public ListNode deleteDuplication(ListNode pHead) { - // 1. 边界,需要下一个结点做判断,因此还是需要判断下个结点是否为空 - if (pHead == null || pHead.next == null) - return pHead; - // 取下一个结点 - ListNode next = pHead.next; - // 2. 判断当前结点和下一个结点是否相等 - if (pHead.val == next.val) { - // 3. 如果相等,判断是否一直重复 - while (next != null && pHead.val == next.val) - next = next.next; - return deleteDuplication(next); - } else { - // 4, 否则不重复的话,就递归下一个结点 - pHead.next = deleteDuplication(pHead.next); - return pHead; - } -} -``` - -### 3.1 环形链表 -[https://leetcode-cn.com/problems/linked-list-cycle/](https://leetcode-cn.com/problems/linked-list-cycle/) -```java -public class Solution { - public boolean hasCycle(ListNode head) { - if (head == null) { - return false; - } - ListNode l1 = head, l2 = head.next; - while (l1 != null && l2 != null && l2.next != null) { - if (l1 == l2) { - return true; - } - l1 = l1.next; - l2 = l2.next.next; - } - return false; - } -} -``` - -### 3.2、[链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - - -[https://leetcode-cn.com/problems/linked-list-cycle-ii/submissions/](https://leetcode-cn.com/problems/linked-list-cycle-ii/submissions/) - -```java -// 快慢指针 -public ListNode EntryNodeOfLoop(ListNode pHead) { - // 设计双指针, 1. 边界都要判断下一个结点也是否为空 - if (pHead == null || pHead.next == null) - return null; - ListNode slow = pHead, fast = pHead; - // 2. 快以二倍速前进,慢正常操作 - do { - fast = fast.next.next; - slow = slow.next; - } while (slow != fast); // 3. 相遇点 - // 4. fast从头继续和slow正常走 - fast = pHead; - while (slow != fast) { - slow = slow.next; - fast = fast.next; - } - // 5. 返回slow,毕竟二者走在一块 - return slow; -} -``` - -### 4、[反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/) - -```java -public ListNode reverseList(ListNode head) { - // 总感觉,本质还是两个指针,pre不过初始化为null,cur为head当前结点 - ListNode pre = null; - ListNode cur = head; - while (cur != null) { - // 1. 获取当前节点的下一个节点,方便cur下移动 - ListNode nextTemp = cur.next; - // 2. 当前节点的下个节点指向前一个节点 (反转,那肯定cur的next指向pre咯) - cur.next = pre; - // 3. pre移动下一个结点,那肯定是cur咯 - pre = cur; - // 4. cur移动下一个结点, 那肯定是nextTemp咯 - cur = nextTemp; - } - return pre; -} -``` - -```java -public class T15 { - public ListNode ReverseList(ListNode head) { - // 判断 - if (head == null) return null; - return reverse(null, head); - } - private ListNode reverse(ListNode pre, ListNode cur) { - // 递归结束判断 - if (cur == null) return pre; - // 1. 依然第一个方法遍历依然,得到cur的next,递归用 - ListNode next = cur.next; - // 2. 反转操作 - cur.next = pre; - return reverse(cur, next); - } -} -``` - -### 5、[链表中倒数第k个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) - -```java -public ListNode FindKthToTail(ListNode head, int k) { - // 还是双指针 - // 1. 边界判断 - if (head == null) - return null; - ListNode p1 = head; - // 2. 先让p1移动k步 - while (p1 != null && k-- > 0) - p1 = p1.next; - // 3. 这一步防止k大于head的长度 - if (k > 0) - return null; - ListNode p2 = head; - // 4. 二者正常走,不过p1肯定先到末尾 - while (p1 != null) { - p1 = p1.next; - p2 = p2.next; - } - // 5. 返回p2 - return p2; -} -``` - -### 6、[合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=%2Fta%2) - -[https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/) -```java -public class T16 { - public ListNode Merge(ListNode list1,ListNode list2) { - // 1. 如果list1为空,返回list2 - if (list1 == null) return list2; - // 2. 如果list2为空,返回list1 - if (list2 == null) return list1; - // 3. 如果list1.val < list2.val,则list1.next连接下一个比较值(递归比较) - if (list1.val < list2.val) { - list1.next = Merge(list1.next, list2); - return list1; - } else { - // 4. 否则,list2.next 连接下一个比较值(递归比较) - list2.next = Merge(list1, list2.next); - return list2; - } - } -} -``` - -### 7、[两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/intersection-of-two-linked-lists/](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/) - -```java -// 还是双指针 -public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - ListNode l1 = pHead1, l2 = pHead2; - // 1. 循环条件 - while (l1 != l2) { - // 2. 走完l1,从头走head2 - l1 = (l1 == null) ? pHead2 : l1.next; - // 3. 走完l2,从头走head1 - l2 = (l2 == null) ? pHead1 : l2.next; - } - // 4. 返回l1 - return l1; -} -``` - -### 8. 两数相加(2819) -[https://leetcode-cn.com/problems/add-two-numbers/](https://leetcode-cn.com/problems/add-two-numbers/) - -```java -class Solution { - public ListNode addTwoNumbers(ListNode l1, ListNode l2) { - // 边界判断:3个 - // 1. l1和l2同时为空 - if (l1 == null && l2 == null) return null; - // 2. l1为空,返回l2 - if (l1 == null) return l2; - // 3. l2为空,返回l1 - if (l2 == null) return l1; - // 三指针 - // 1. p1 - ListNode p1 = l1; - // 2. p2 - ListNode p2 = l2; - // 3. p3 特殊:返回最值链表 - ListNode l3 = new ListNode(-1); - ListNode p3 = l3; - // 4. 注意:进位 - int carried = 0; - // 5. 循环条件,任意一个不为空即可 - while (p1 != null || p2 != null) { - // p1不为空,获取p1val,否则0 - int a = p1 != null ? p1.val : 0; - // p2不为空,获取p2val,否则0 - int b = p2 != null ? p2.val : 0; - // 关键一步,(a+b+carried) % 10 个位 - p3.next = new ListNode((a + b + carried) % 10); - // 加完,记得进位呀(a + b + carried) / 10 - carried = (a + b + carried) / 10; - // 三个指针开始移动 - p3 = p3.next; - p1 = p1 != null ? p1.next : null; - p2 = p2 != null ? p2.next : null; - } - // 循环完之后,判断进位是否0,如果不是,new一个结点为1,是的话,就null - p3.next = carried != 0 ? new ListNode(1) : null; - // 返回l3下一个结点 - return l3.next; - } -} -``` - -### 9. 合并K个排序链表(924) -[https://leetcode-cn.com/problems/merge-k-sorted-lists/](https://leetcode-cn.com/problems/merge-k-sorted-lists/) - -最小堆 -```java -class Solution { - public ListNode mergeKLists(ListNode[] lists) { - // 边界判断 - if (lists == null || lists.length == 0) return null; - // 2. 堆,大顶堆 - PriorityQueue queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val); - // 3. 返回结点 - ListNode dummy = new ListNode(0); - // 4. 临时p - ListNode p = dummy; - // 5. 遍历k个lists,一个一个添加到queue - for (ListNode node : lists) { - if (node != null) queue.add(node); - } - // 6. 循环堆不为空 - while (!queue.isEmpty()) { - // p的下个结点指向 取出堆中最大的链表结点 - p.next = queue.poll(); - // 移动 - p = p.next; - // 如果下一个不为空,继续添加刚才堆中最大的下一个结点 - if (p.next != null) queue.add(p.next); - } - // 7. 返回下一个结点 - return dummy.next; - } -} -``` - -```java -class Solution { - public ListNode mergeKLists(ListNode[] lists) { - // 归并 - if (lists == null || lists.length == 0) return null; - return merge(lists, 0, lists.length - 1); - } - - private ListNode merge(ListNode[] lists, int left, int right) { - // 归并排序一样 - if (left == right) return lists[left]; - int mid = left + (right - left) / 2; - ListNode l1 = merge(lists, left, mid); - ListNode l2 = merge(lists, mid + 1, right); - return mergeTwoLists(l1, l2); - } - // 不过,最后归并的时候用的是合并两个排序的链表 - private ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1,l2.next); - return l2; - } - } -} -``` - -### 10. 两两交换链表中的节点(947) -[https://leetcode-cn.com/problems/swap-nodes-in-pairs/](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) - -```java -class Solution { - // 两两交换,那么双指针 - public ListNode swapPairs(ListNode head) { - // 创建一个返回结点 - ListNode node = new ListNode(-1); - // 该结点的next指向head - node.next = head; - // 在创建一个pre,移动到node - ListNode pre = node; - // 循环遍历 - // pre的next才是head,因此判断next next - while (pre.next != null && pre.next.next != null) { - // 获取l1, l2 - ListNode l1 = pre.next, l2 = pre.next.next; - // - ListNode next = l2.next; - l1.next = next; - l2.next = l1; - pre.next = l2; - // pre移动一步 - pre = l1; - } - return node.next; - } -} -``` - -### 11. 链表的中间结点(853) -[https://leetcode-cn.com/problems/middle-of-the-linked-list/](https://leetcode-cn.com/problems/middle-of-the-linked-list/) - -```java -// 双指针(快慢) -class Solution { - public ListNode middleNode(ListNode head) { - ListNode p = head, q = head; - while (q != null && q.next != null) { - q = q.next.next; - p = p.next; - } - return p; - } -} -``` - -### 12. 删除排序链表中的重复元素(603) -[https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) - -```java -// 和那个有点区别 -class Solution { - public ListNode deleteDuplicates(ListNode head) { - if (head == null || head.next == null) return head; - head.next = deleteDuplicates(head.next); - return head.val == head.next.val ? head.next : head; - } -} -``` - -### 13. 回文链表(624) -[https://leetcode-cn.com/problems/palindrome-linked-list/](https://leetcode-cn.com/problems/palindrome-linked-list/) - -这题考察了很多链表的题,挺综合额 -```java -class Solution { - public boolean isPalindrome(ListNode head) { - if(head == null || head.next == null) return true; - // 找中点 - ListNode slow = head, fast = head.next; - while(fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - // 奇偶情况 - if(fast != null) slow = slow.next; - // cut - cut(head, slow); - // 比较 - return isEqual(head, reverse(slow)); - - } - - // 切 - public void cut (ListNode head, ListNode cutNode) { - ListNode node = head; - // 循环遍历找到和cutNode相等的结点的前一个结点 - while(node.next != cutNode) { - node = node.next; - } - // 然后直接null - node.next = null; - } - - // 反转链表派上用场 - public ListNode reverse(ListNode head) { - ListNode pre = null; - ListNode cur = head; - while(cur != null) { - ListNode nextNode = cur.next; - cur.next = pre; - pre = cur; - cur = nextNode; - } - return pre; - } - - - public boolean isEqual(ListNode l1, ListNode l2) { - // 二者都不为空才行 - while(l1 != null && l2 != null) { - // 二者值不相等直接false - if(l1.val != l2.val) return false; - // 移动 - l1 = l1.next; - l2 = l2.next; - } - // 比如就true - return true; - } -} -``` - -### 14. 奇偶链表(317) -[https://leetcode-cn.com/problems/odd-even-linked-list/](https://leetcode-cn.com/problems/odd-even-linked-list/) - -```java -class Solution { - public ListNode oddEvenList(ListNode head) { - if (head == null) return head; - // 临时变量 奇头 偶头下 偶头部 - ListNode odd = head, even = head.next, evenHead = even; - // 循环遍历偶和偶下不为空 - while (even != null && even.next != null) { - // 奇 -> 奇下下 - odd.next = odd.next.next; - // 奇移动一步 - odd = odd.next; - // 偶 -> 偶下下 - even.next = even.next.next; - // 偶移动 - even = even.next; - } - // 奇 -> 偶头 - odd.next = evenHead; - // 返回head - return head; - } -} -``` - -### 15. K 个一组翻转链表(字节爱考) -[https://leetcode-cn.com/problems/reverse-nodes-in-k-group/](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/) - -```java -class Solution { - public ListNode reverseKGroup(ListNode head, int k) { - ListNode dummy = new ListNode(0), pre = dummy, curr = head, next; - // 临时赋值 - dummy.next = head; - // 算长度 - int len = 0; - while (head != null) { - len++; - head = head.next; - } - // 从头 - head = dummy.next; - // 两重for遍历 - for (int i = 0; i < len / k; i++) { - for (int j = 0; j < k - 1; j++) { - // 谜一般的操作 - // 临时next - next = curr.next; - // 我指向你 - curr.next = next.next; - // 你指向他 - next.next = pre.next; - // 他指向临时 - pre.next = next; - } - pre = curr; // 移动 - curr = pre.next; // 移动 - } - return dummy.next; - } -} -``` - -### 16. 奇数位升序偶数位降序的链表 - -```java -public static ListNode oddEvenLinkedList(ListNode head) { - // 将偶数链表拆分出来 - ListNode evenHead = getEvenList(head); - // 逆序偶数链表 - ListNode reEvenHead = reverseList(evenHead); - // 归并奇偶链表 - ListNode mHead = mergeList(head, reEvenHead); - return mHead; -} - -public static ListNode getEvenList(ListNode head) { - ListNode cur = head; - ListNode next = null; - ListNode evenHead = head.next; - while (cur != null && cur.next != null) { - // 获取next - next = cur.next; - // cur奇数,-> next.next (奇数) - cur.next = next.next; - // 移动 - cur = cur.next; - - // 开始偶数 - next.next = cur.next; - // 移动 - next = next.next; - } - return evenHead; -} - -public static ListNode reverseList(ListNode head) { - ListNode pre = null; - ListNode cur = head; - while (cur != null) { - ListNode next = cur.next; - cur.next = pre; - pre = cur; - cur = next; - } - return pre; -} - -public static ListNode mergeList(ListNode l1, ListNode l2){ - // 我用递归 - if (l1 == null) - return l2; - if (l2 == null) - return l1; - if (l1.val < l2.val) { - l1.next = mergeList(l1.next, l2); - return l1; - } else { - l2.next = mergeList(l1, l2.next); - return l2; - } -} -``` - -### 17. 分隔链表 -[https://leetcode-cn.com/problems/partition-list/](https://leetcode-cn.com/problems/partition-list/) -```java -class Solution { - public ListNode partition(ListNode head, int x) { - ListNode dummy1 = new ListNode(0); - ListNode dummy2 = new ListNode(0); - ListNode node1 = dummy1, node2 = dummy2; - while (head != null){ - if (head.val < x){ - node1.next = head; - // 你左脚走 - head = head.next; - // 1也左脚走 - node1 = node1.next; - // 直接割,我不需要 - node1.next = null; - } else { - node2.next = head; - head = head.next; - node2 = node2.next; - node2.next = null; - } - } - node1.next = dummy2.next; - return dummy1.next; - } -} -``` - -### xx、[复杂链表的复制](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -> 这个题面试没见着,但是先放这里把 - -```java -public class T25 { - public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) return null; - // 第一步:先复制一遍next - RandomListNode node = pHead; - while (node != null) { - RandomListNode copyNode = new RandomListNode(node.label); - copyNode.next = node.next; - node.next = copyNode; - node = copyNode.next; - } - // 第二步:再复制一遍random - node = pHead; - while (node != null) { - node.next.random = node.random == null ? null : node.random.next; - node = node.next.next; - } - // 第三步:切开 - node = pHead; - RandomListNode pCloneHead = pHead.next; - while (node != null) { - RandomListNode copyNode = node.next; - node.next = copyNode.next; - copyNode.next = copyNode.next == null ? null : copyNode.next.next; - node = node.next; - } - return pCloneHead; - } -} -``` - -## 树 -### 1、[重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/) - -```java -class Solution { - public TreeNode buildTree(int[] preorder, int[] inorder) { - int n = preorder.length; - if (n == 0) - return null; - int rootVal = preorder[0], rootIndex = 0; - // 找中序根的索引 - for (int i = 0; i < n; i++) { - if (inorder[i] == rootVal) { - rootIndex = i; - break; - } - } - TreeNode root = new TreeNode(rootVal); - // 注意边界 - // left - root.left = buildTree( - Arrays.copyOfRange(preorder, 1, 1 + rootIndex), - Arrays.copyOfRange(inorder, 0, rootIndex)); - // right - root.right = buildTree( - Arrays.copyOfRange(preorder, 1 + rootIndex, n), - Arrays.copyOfRange(inorder, rootIndex + 1, n)); - - return root; - } -} -``` - -### 2、[二叉树的下一个结点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -// 中序 -public class T57 { - public TreeLinkNode GetNext(TreeLinkNode pNode) { - if (null == pNode) { - return null; - } - // 两种情况 - if (null != pNode.right) { - TreeLinkNode node = pNode.right; - while (null != node.left) { - node = node.left; - } - return node; - } - while (null != pNode.next) { - TreeLinkNode parent = pNode.next; - if (parent.left == pNode) { - return parent; - } - pNode = pNode.next; - } - return null; - } -} -``` - -### 3、[树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/) - -```java -public class T17 { - public boolean HasSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null || root2 == null) - return false; - return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); - } - - private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) { - if (root2 == null) - return true; - if (root1 == null) - return false; - if (root1.val != root2.val) - return false; - return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right); - } - -} -``` - -### 3、[二叉树的镜像](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/) - -```java -public class T18 { - public void Mirror(TreeNode root) { - // 判断 - if (root == null) return; - swap(root); - Mirror(root.left); - Mirror(root.right); - - } - - private void swap(TreeNode root) { - TreeNode t = root.left; - root.left = root.right; - root.right = t; - } -} -``` - -```java -class Solution { - public TreeNode mirrorTree(TreeNode root) { - if (root == null) return null; - Queue queue = new LinkedList<>(); - queue.add(root); - TreeNode cur; - while (!queue.isEmpty()) { - cur = queue.poll(); - if(cur.left != null) queue.add(cur.left); - if (cur.right != null) queue.add(cur.right); - swap(cur); - } - return root; - } - - private void swap(TreeNode root) { - TreeNode t = root.left; - root.left = root.right; - root.right = t; - } -} -``` - -### 4、[对称的二叉树](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/) - -```java -public class T58 { - boolean isSymmetrical(TreeNode pRoot) { - if (null == pRoot) { - return true; - } - return comRoot(pRoot.left, pRoot.right); - } - - private boolean comRoot(TreeNode left, TreeNode right) { - if (left == null && right == null) { - return true; - } - if (left == null || right == null) { - return false; - } - if (left.val != right.val) { - return false; - } - // 左右对比 - return comRoot(left.right, right.left) && comRoot(left.left, right.right); - } -} -``` - -### 5.1、[从上往下打印二叉树](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) - -```java -public class T22 { - // 层序遍历 - public ArrayList PrintFromTopToBottom(TreeNode root) { - ArrayList list = new ArrayList<>(); - // 需要用到队列 - LinkedList queue = new LinkedList<>(); - queue.offer(root); // 第一次先加根入队 - while (!queue.isEmpty()) { - int cnt = queue.size(); - // 如果队列不为空的话, 队列出一个元素 - while(cnt-- > 0) { - TreeNode t = queue.poll(); - if (t == null) continue; - list.add(t.val); - queue.add(t.left); - queue.add(t.right); - } - } - return list; - } -} -``` - -### 5.2、[把二叉树打印多行](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T60 { - - ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - while (!queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (list.size() != 0) ret.add(list); - } - return ret; - } -} -``` - -### 5.3、[按之字形顺序打印二叉树](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T59 { - public ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - boolean reverse = false; - while (! queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (reverse) Collections.reverse(list); - reverse = !reverse; - if (list.size() != 0) ret.add(list); - } - reutrn ret; -} -``` - -### 5.4 二叉树的右视图 -[https://leetcode-cn.com/problems/binary-tree-right-side-view/](https://leetcode-cn.com/problems/binary-tree-right-side-view/) - -层序遍历,只保留最后一个结点的值 -```java -class Solution { - public List rightSideView(TreeNode root) { - List ret = new ArrayList<>(); - if (root == null) return ret; - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()) { - int size = queue.size(); - while (size-- > 0) { - TreeNode t = queue.poll(); - if (t.left != null) queue.add(t.left); - if (t.right != null) queue.add(t.right); - if (size == 0) ret.add(t.val); - } - } - return ret; - } -} -``` - -### 5.5 二叉树的左视图 - -```java -class Solution { - public List rightSideView(TreeNode root) { - List ret = new ArrayList<>(); - if (root == null) return ret; - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()){ - int size = queue.size(); - int tmp = size - 1; - while (size-- > 0){ - TreeNode t = queue.poll(); - if(t.left != null) queue.add(t.left); - if(t.right != null) queue.add(t.right); - if(tmp == size) ret.add(t.val); - } - } - return ret; - } -} -``` - -### 6、[二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) - - -```java -public class T23 { - public boolean VerifySquenceOfBST(int [] sequence) { - if (sequence == null || sequence.length == 0) return false; - return isBST(sequence, 0, sequence.length - 1); - } - private boolean isBST(int[] sequence, int first, int last) { - if (last - first <= 1) { - return true; - } - int rootVal = sequence[last]; - int cutIndex = first; - while (cutIndex < last && sequence[curIndex] <= rootVal) { // 二叉搜索树特征 - cutIndex++; - } - for (int i = cutIndedx; i < last; i++) { - if (sequence[i] < rootVal) return false; - } - return isBST(sequence, first, cutIndex - 1) && isBST(sequence, cutIndex, last - 1); - } -} -``` - -### 7、[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/) - -```java -public class T24 { - - private ArrayList> ret = new ArrayList<>(); - - public ArrayList> FindPath(TreeNode root, int target) { - backtracking(root, target, new ArrayList<>()); - return ret; - } - - private void backtracking(TreeNode node, int target, ArrayList path) { - if (node == null) - return; - path.add(node.val); - target -= node.val; - if (target == 0 && node.left == null && node.right == null) { - ret.add(new ArrayList<>(path)); - } else { - backtracking(node.left, target, path); - backtracking(node.right, target, path); - } - path.remove(path.size() - 1); - } -} -``` - -### 8、[二叉搜索树的第k个结点](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) - -```java -// 定义第k个结点的返回 -private TreeNode ret; -// 定义次数 -private int cnt = 0; - -public TreeNode KthNode(TreeNode pRoot, int k) { - // 中序遍历 - inOrder(pRoot, k); - return ret; -} - -private void inOrder(TreeNode root, int k) { - // 递归结束条件 - if (root == null || cnt >= k) - return; - // 左 - inOrder(root.left, k); - // 加一次 - cnt++; - // 如果正好等于k,则给ret - if (cnt == k) - ret = root; - // 递归右 - inOrder(root.right, k); -} - -``` - -### 9.1、[二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T38 { - public int TreeDepth(TreeNode root) { - // 递归取左和右的最大高度 + 1 - return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); - } -} - -// 迭代 -class Solution { - public int maxDepth(TreeNode root) { - if (root == null) return 0; - int depth = 0; - Queue> queue = new LinkedList<>(); - queue.add(new Pair(root, 1)); - while (!queue.isEmpty()){ - Pair cur = queue.poll(); - root = cur.getKey(); - int curDepth = cur.getValue(); - if (root != null) { - depth = Math.max(depth, curDepth); - queue.add(new Pair(root.left, curDepth + 1)); - queue.add(new Pair(root.right, curDepth + 1)); - } - } - return depth; - } -} -``` -### 9.2 二叉树的直径 -[https://leetcode-cn.com/problems/diameter-of-binary-tree/](https://leetcode-cn.com/problems/diameter-of-binary-tree/) -```java -class Solution { - // 定义最大高度 - private int max = 0; - public int diameterOfBinaryTree(TreeNode root) { - // 递归 - Depth(root); - return max; - } - - private int Depth(TreeNode root) { - // 递归结束条件 - if (root == null) return 0; - // 递归左的高度 - int l = Depth(root.left); - // 递归右的高度 - int r = Depth(root.right); - // 每次保持最大高度 - max = Math.max(max, l + r); - // 返回左和右的最大高度加1 - return Math.max(l, r) + 1; - } -} -``` - -### 10、[平衡二叉树](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/) - -```java -// 定义一个平衡标记,默认平衡 -private boolean isBalanced = true; - -public boolean IsBalanced_Solution(TreeNode root) { - // 递归 - height(root); - return isBalanced; -} - -private int height(TreeNode root) { - // 递归结束条件 - if (root == null || !isBalanced) - return 0; - // 递归左高度 - int left = height(root.left); - // 递归右高度 - int right = height(root.right); - // 绝对值是否大于1 - if (Math.abs(left - right) > 1) - isBalanced = false; - // 返回左和右的最大高度加1 - return 1 + Math.max(left, right); -} -``` - -### 11.1 非递归前序 -[https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/) - -```java -public List preorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - // 用栈的思想 - Stack stack = new Stack<>(); - // 我们知道,前序:根左右 - // 添加根 - stack.push(root); - while (!stack.isEmpty()) { - // 弹根 - TreeNode node = stack.pop(); - // 判断是否为空 - if (node == null) continue; - // 不为空,加val加入列表 - ret.add(node.val); - // 先添加右,后左,这样下次就能先弹左 - stack.push(node.right); - stack.push(node.left); - } - return ret; -} - -``` - -### 11.2 后序 -[https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/) - -```java -public List postorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - // 还是栈思想,后序:左右根,倒过来:根右左:那么就是根前序的差不多 - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - if (node == null) continue; - ret.add(node.val); - // 这里先添加左,保证弹出的是右 - stack.push(node.left); - stack.push(node.right); - } - // 翻转就是后序 - Collections.reverse(ret); - return ret; -} -``` - -### 11.3 中序 -[https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/) - -```java -public List inorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - if (root == null) return ret; - // 还是栈:中序:左根右 - Stack stack = new Stack<>(); - // 虚拟结点 - TreeNode cur = root; - while (cur != null || !stack.isEmpty()) { - while (cur != null) { - // 一直左 - stack.push(cur); - cur = cur.left; - } - // 保证弹出的左 - TreeNode node = stack.pop(); - ret.add(node.val); - // 开始移动到右 - cur = node.right; - } - return ret; -} - -``` - -### 12 二叉树的最近公共祖先 -[https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) -```java -class Solution { - public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if (root == null) - return root; - // 如果根等于p或者q,直接返回根 - if (root == p || root == q) - return root; - // 递归左和pq比 - TreeNode left = lowestCommonAncestor(root.left, p, q); - // 递归右和pq比 - TreeNode right = lowestCommonAncestor(root.right, p, q); - // 同时不为空,则为根 - if (left != null && right != null) - return root; - // 左不空,则左 - else if (left != null) - return left; - // 右不空,则右 - else if (right != null) - return right; - return null; - } -} -``` - -### 13. 合并二叉树 -[https://leetcode-cn.com/problems/merge-two-binary-trees/](https://leetcode-cn.com/problems/merge-two-binary-trees/) - -```java -class Solution { - public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) - return null; - if (t1 == null) - return t2; - if (t2 == null) - return t1; - TreeNode root = new TreeNode(t1.val + t2.val); - root.left = mergeTrees(t1.left, t2.left); - root.right = mergeTrees(t1.right, t2.right); - return root; - } -} - - public TreeNode mergeTrees2(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) - return null; - if (t1 == null) - return t2; - if (t2 == null) - return t1; - // 先合并根节点 - t1.val += t2.val; - // 再递归合并左右子树 - t1.left = mergeTrees(t1.left, t2.left); - t1.right = mergeTrees(t1.right, t2.right); - return t1; - } -``` - -```java -class Solution { - public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { - Queue queue = new LinkedList<>(); - queue.offer(t1); - queue.offer(t2); - while(!queue.isEmpty()){ - TreeNode node1 = queue.poll(); - TreeNode node2 = queue.poll(); - //合并两个值 - node1.val += node2.val; - //左子树都不为空 - if(node1.left != null && node2.left!=null){ - queue.offer(node1.left); - queue.offer(node2.left); - } - if(node1.left == null) - node1.left = node2.left; - //右子树都不为空 - if(node1.right != null && node2.right != null){ - queue.offer(node1.right); - queue.offer(node2.right); - } - if(node1.right == null) - node1.right = node2.right; - } - return t1; - } -} -``` - -### 14. 不同的二叉搜索树 - -[https://leetcode-cn.com/problems/unique-binary-search-trees/](https://leetcode-cn.com/problems/unique-binary-search-trees/) - -动态规划 - -假设n个节点存在二叉排序树的个数是G(n),令f(i)为以i为根的二叉搜索树的个数 - -即有:G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n) - -n为根节点,当i为根节点时,其左子树节点个数为[1,2,3,...,i-1],右子树节点个数为[i+1,i+2,...n],所以当i为根节点时,其左子树节点个数为i-1个,右子树节点为n-i,即f(i) = G(i-1)*G(n-i), - -上面两式可得:G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0) - -```java -class Solution { - public int numTrees(int n) { - int[] dp = new int[n + 1]; - dp[0] = 1; - dp[1] = 1; - for (int i = 2; i <=n; i++){ - for (int j = 1; j <= i; j++){ - dp[i] += dp[j - 1] * dp[i - j]; - } - } - return dp[n]; - } -} -``` - -## dfs||回溯 -### 1.1、[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/) - -```java -private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; -private int rows; -private int cols; - -public boolean hasPath(char[] array, int rows, int cols, char[] str) { - if (rows == 0 || cols == 0) return false; - this.rows = rows; - this.cols = cols; - boolean[][] marked = new boolean[rows][cols]; - char[][] matrix = buildMatrix(array); - for (int i = 0; i < rows; i++) - for (int j = 0; j < cols; j++) - if (backtracking(matrix, str, marked, 0, i, j)) - return true; - - return false; -} - -private boolean backtracking(char[][] matrix, char[] str, - boolean[][] marked, int pathLen, int r, int c) { - // 如果长度满足,则为true:true的条件 - if (pathLen == str.length) return true; - // 如果任意满足,则false:false的条件 - if (r < 0 || r >= rows || c < 0 || c >= cols - || matrix[r][c] != str[pathLen] || marked[r][c]) { - - return false; - } - // 我这个元素只能拿一次,递归的时候,你不能拿了 - marked[r][c] = true; - for (int[] n : next) - if (backtracking(matrix, str, marked, pathLen + 1, r + n[0], c + n[1])) - return true; - // 递归结束,该元素为false,意味着,可以拿了,回溯嘛,就像线程切换一样 - marked[r][c] = false; - return false; -} - -private char[][] buildMatrix(char[] array) { - char[][] matrix = new char[rows][cols]; - for (int r = 0, idx = 0; r < rows; r++) - for (int c = 0; c < cols; c++) - matrix[r][c] = array[idx++]; - return matrix; -} -``` - - -### 1.2. 单词搜索(420) -[https://leetcode-cn.com/problems/word-search/](https://leetcode-cn.com/problems/word-search/) -```java -class Solution { - private final static int[][] direction = {{1,0},{-1,0},{0,1},{0,-1}}; - private int m; - private int n; - public boolean exist(char[][] board, String word) { - if (word == null || word.length() == 0) return true; - if (board == null || board.length == 0 || board[0].length == 0) return false; - m = board.length; - n = board[0].length; - boolean[][] hasVisited = new boolean[m][n]; - for (int r = 0; r < m; r++) { - for (int c = 0; c < n; c++) { - if (backtracking(0, r, c, hasVisited, board, word)) { - return true; - } - } - } - return false; - } - private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) { - // 符合条件 - if (curLen == word.length()) return true; - // 不符合条件 - if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(curLen) || visited[r][c]) return false; - // 表面元素已用过 - visited[r][c] = true; - for (int[] d : direction) { - if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) return true; - } - // 可以重新使用 - visited[r][c] = false; - return false; - } -} -``` - - - -### 2.1、[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -[https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/) - -```java -private ArrayList ret = new ArrayList<>(); - -public ArrayList Permutation(String str) { - if (str.length() == 0) - return ret; - char[] chars = str.toCharArray(); - // 排序,过滤重复 - Arrays.sort(chars); - backtracking(chars, new boolean[chars.length], new StringBuilder()); - return ret; -} - -private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) { - // 满足条件 - if (s.length() == chars.length) { - ret.add(s.toString()); - return; - } - // 遍历 - for (int i = 0; i < chars.length; i++) { - // 我已经拿过了,不能在拿了。 - if (hasUsed[i]) - continue; - // 避免重复,实际上优化! 注意后面那个条件,上一个元素没用过 - if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) /* 保证不重复 */ - continue; - // 标记只能取一次 - hasUsed[i] = true; - s.append(chars[i]); - backtracking(chars, hasUsed, s); - s.deleteCharAt(s.length() - 1); - hasUsed[i] = false; - } -} - -``` - -### 2.2. 全排列(985) -[https://leetcode-cn.com/problems/permutations/](https://leetcode-cn.com/problems/permutations/) - -```java -class Solution { - public List> permute(int[] nums) { - List> permutes = new ArrayList<>(); - List permuteList = new ArrayList<>(); - boolean[] hasVisited = new boolean[nums.length]; - backtracking(permuteList, permutes, hasVisited, nums); - return permutes; - } - private void backtracking(List permuteList, List> permutes, boolean[] visited, final int[] nums) { - // 满足条件 - if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); // 重新构造一个List - return; - } - // 遍历 - for (int i = 0; i < visited.length; i++) { - // 已经拿过了,不能再拿了 - if (visited[i]) - continue; - // 标记 - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - // 回溯 - permuteList.remove(permuteList.size() - 1); - visited[i] = false; - } - - } -} -``` - -### 2.3. 全排列 II(429) -[https://leetcode-cn.com/problems/permutations-ii/](https://leetcode-cn.com/problems/permutations-ii/) - -```java -class Solution { - public List> permuteUnique(int[] nums) { - List> permutes = new ArrayList<>(); - List permuteList = new ArrayList<>(); - Arrays.sort(nums); // 排序,为了避免重复 - boolean[] hasVisited = new boolean[nums.length]; - backtracking(permuteList, permutes, hasVisited, nums); - return permutes; - } - private void backtracking(List permuteList, List> permutes, boolean[] visited, final int[] nums) { - // 满足条件 - if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); - return; - } - // 遍历 - for (int i = 0; i < visited.length; i++) { - // 避免重复 - if (i != 0 && nums[i] == nums[i -1] && !visited[i - 1]) { - continue; // 防止重复 - } - // 表明已经拿了,退出 - if (visited[i]) - continue; - // 标记,只能拿一次 - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - // 回溯 - permuteList.remove(permuteList.size() - 1); - visited[i] = false; - } - } -} -``` - -### 2.4. 组合总和(582) -[https://leetcode-cn.com/problems/combination-sum/](https://leetcode-cn.com/problems/combination-sum/) - -```java -class Solution { - public List> combinationSum(int[] candidates, int target) { - List> combinations = new ArrayList<>(); - backtracking(new ArrayList<>(), combinations, 0, target, candidates); - return combinations; - } - - private void backtracking(List tempCombination, List> combinations, - int start, int target, final int[] candidates) { - // target为0,则满足 - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); - return; - } - // 遍历从start开始 - for (int i = start; i < candidates.length; i++) { - // 注意这个骚条件,满足才行 - if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - backtracking(tempCombination, combinations, i, target - candidates[i], candidates); - // 回溯 - tempCombination.remove(tempCombination.size() - 1); - } - } - } -} -``` - - -### 2.5. 组合总和 II(401) -[https://leetcode-cn.com/problems/combination-sum-ii/](https://leetcode-cn.com/problems/combination-sum-ii/) - -```java -class Solution { - public List> combinationSum2(int[] candidates, int target) { - List> combinations = new ArrayList<>(); - Arrays.sort(candidates); // 为了避免重复 - backtracking(new ArrayList<>(), combinations, new boolean[candidates.length], 0, target, candidates); - return combinations; - } - - private void backtracking(List tempCombination, List> combinations, - boolean[] hasVisited, int start, int target, final int[] candidates) { - - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); - return; - } - for (int i = start; i < candidates.length; i++) { - if(hasVisited[i]) - continue; - // 一样的道理 - if (i != 0 && candidates[i] == candidates[i - 1] && !hasVisited[i - 1]) { - continue; - } - if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - // 只能拿一次 - hasVisited[i] = true; - backtracking(tempCombination, combinations, hasVisited, i, target - candidates[i], candidates); - hasVisited[i] = false; - tempCombination.remove(tempCombination.size() - 1); - } - } - } - -} -``` - - -### 3.1. 子集(633) -[https://leetcode-cn.com/problems/subsets/](https://leetcode-cn.com/problems/subsets/) -```java -class Solution { - public List> subsets(int[] nums) { - List> subsets = new ArrayList<>(); - List tempSubset = new ArrayList<>(); - for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小 - } - return subsets; - } - - private void backtracking(int start, List tempSubset, List> subsets, - final int size, final int[] nums) { - - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); - return; - } - for (int i = start; i < nums.length; i++) { - tempSubset.add(nums[i]); - backtracking(i + 1, tempSubset, subsets, size, nums); - tempSubset.remove(tempSubset.size() - 1); - } - } - -} -``` - -### 3.2. 子集 II(304) -[https://leetcode-cn.com/problems/subsets-ii/](https://leetcode-cn.com/problems/subsets-ii/) - -```java -class Solution { - public List> subsetsWithDup(int[] nums) { - Arrays.sort(nums); // 注意 - List> subsets = new ArrayList<>(); - List tempSubset = new ArrayList<>(); - boolean[] hasVisited = new boolean[nums.length]; - for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小 - } - return subsets; - } - - private void backtracking(int start, List tempSubset, List> subsets, boolean[] hasVisited, - final int size, final int[] nums) { - - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); - return; - } - for (int i = start; i < nums.length; i++) { - // 注意 - if (i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]) { - continue; - } - tempSubset.add(nums[i]); - hasVisited[i] = true; - backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums); - hasVisited[i] = false; - tempSubset.remove(tempSubset.size() - 1); - } - } - -} -``` - - - - - -### 4.1. 岛屿数量(853) -[https://leetcode-cn.com/problems/number-of-islands/](https://leetcode-cn.com/problems/number-of-islands/) -```java -class Solution { - // 像这种二维, 定义四个全局方向 - private int m, n; - private int[][] direaction = {{0,1},{0,-1},{1,0},{-1,0}}; - public int numIslands(char[][] grid) { - if (grid == null || grid.length == 0) return 0; - m = grid.length; - n = grid[0].length; - int islandsNum = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - // 不等于0,才能dfs - if (grid[i][j] != '0') { - dfs(grid, i, j); - // 成功一次,加一次 - islandsNum++; - } - } - } - return islandsNum; - } - - private void dfs(char[][] grid, int i, int j) { - // 失败条件 - if (i < 0 || i >= m || j < 0 || j >=n || grid[i][j] == '0') { - return; - } - // 标记,已走过 - grid[i][j] = '0'; - for (int[] d : direaction) { - dfs(grid, i + d[0], j + d[1]); - } - } -} -``` - - - -### 4.2. 岛屿的最大面积(648) -[https://leetcode-cn.com/problems/max-area-of-island/](https://leetcode-cn.com/problems/max-area-of-island/) - -```java -class Solution { - private int m, n; - private int[][] direaction = {{0,1},{0,-1},{1,0},{-1,0}}; - public int maxAreaOfIsland(int[][] grid) { - if(grid == null || grid.length == 0) return 0; - m = grid.length; - n = grid[0].length; - int maxArea = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - // 这里可以加个条件,不等于0进来 - // 每次取最大面积 - maxArea = Math.max(maxArea, dfs(grid, i, j)); - } - } - return maxArea; - } - private int dfs(int[][] grid, int r, int c) { - // 失败条件 - if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) { - return 0; - } - // 标记走过 - grid[r][c] = 0; - // 开始dfs - int area = 1; - for (int[] d : direaction) { - area += dfs(grid, r + d[0], c + d[1]); - } - return area; - } -} -``` - -### 5. 电话号码的字母组合(1085) -[https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) - -```java -class Solution { - private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; - public List letterCombinations(String digits) { - List combinnations = new ArrayList<>(); - if (digits == null || digits.length() == 0) return combinnations; - doCombination(new StringBuilder(), combinnations, digits); - return combinnations; - } - - private void doCombination(StringBuilder prefix, List combinnations, final String digits) { - if (prefix.length() == digits.length()) { - combinnations.add(prefix.toString()); - return; - } - int curDigits = digits.charAt(prefix.length()) - '0'; - String letters = KEYS[curDigits]; - for (char c : letters.toCharArray()) { - prefix.append(c); - doCombination(prefix, combinnations, digits); - prefix.deleteCharAt(prefix.length() - 1); - } - } -} -``` - - -### 6. 被围绕的区域(328) -[https://leetcode-cn.com/problems/surrounded-regions/](https://leetcode-cn.com/problems/surrounded-regions/) - -```java -class Solution { - private int[][] direction = {{0,1},{0,-1},{1,0},{-1,0}}; - private int m, n; - public void solve(char[][] board) { - if (board == null || board.length == 0) return; - m = board.length; - n = board[0].length; - // 边缘两列 - for (int i = 0; i < m; i++) { - dfs(board, i, 0); - dfs(board, i, n - 1); - } - // 上下两行 - for (int i = 0; i < n; i++) { - dfs(board, 0, i); - dfs(board, m - 1, i); - } - - // 再走全部走一遍 - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - // 遇见T标记O - if (board[i][j] == 'T') { - board[i][j] = 'O'; - // 遇见O标记X - } else if (board[i][j] == 'O') { - board[i][j] = 'X'; - } - } - } - } - - private void dfs(char[][] board, int r, int c) { - if(r<0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') { - return; - } - board[r][c] = 'T'; - for (int[] d : direction) { - dfs(board, r + d[0], c + d[1]); - } - } -} -``` - -### 7. 求 [1,n] 这 n 个数字的排列组合有多少个 -条件:相邻的两个数字的绝对值不能等于1. -例如: -4 -[2, 4, 1, 3] -[3, 1, 4, 2] - -```java -private static List> ret = new ArrayList<>(); -private static int n = 0; -public static void main(String[] args){ - Scanner sc = new Scanner(System.in); - n = sc.nextInt(); - boolean[] marked = new boolean[n + 1]; - dfs(0, marked, new ArrayList<>()); - for (List list : ret) { - System.out.println(list.toString()); - } -} - -private static void dfs(int x, boolean[] marked, ArrayList list) { - if (list.size() == n) { - ret.add(new ArrayList<>(list)); - return; - } - // 开始遍历 - for (int i = 1; i <= n; i++) { - // 关键是这个条件 - if (!marked[i] && (list.isEmpty() || Math.abs(list.get(list.size() - 1) - i) != 1)){ - list.add(i); - marked[i] = true; - dfs(x+1, marked, list); - list.remove(list.size() - 1); - marked[i] = false; - } - } -} -``` - -## 栈||队列 -### 1、[用两个栈实现一个队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T5 { - // 双栈实现 - Stack in = new Stack<>(); - Stack out = new Stack<>(); - - public void push (int node) { - // 添加value - in.push(node); - } - // 主要逻辑在pop上 - public int pop() { - // 判断stack2是否为空 - if (out.isEmpty()) { - // 如果为空 - while (!in.isEmpty()) { - // 并且stack1不为空,然后将栈1所有的元素重新弹出去添加到栈2 - // 这样的话,用栈2弹,就是FIFO的队列了 - out.push(stack1.pop()); - } - } - return out.pop(); - } -} -``` - -### 2、[包含min函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -```java -public class T20 { - // 双栈 - private Stack dataStack = new Stack<>(); - private Stack minStack = new Stack<>(); - - public void push(int node) { - dataStack.push(node);// dataStack添加元素 - // 主要逻辑在这,比大小 - minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node)); - } - - public void pop() { - dataStack.pop(); - // 辅助栈也得弹,因为每次push, 辅助栈也在push - minStack.pop(); - } - // 栈顶,没啥可说的 - public int top() { - return dataStack.peek(); - } - // 最小值,辅助栈弹就完事了 - public int min() { - return minStack.peek(); - } -} -``` - -### 3. 用队列实现栈(4169) -[https://leetcode-cn.com/problems/implement-stack-using-queues/](https://leetcode-cn.com/problems/implement-stack-using-queues/) - -```java -class MyStack { - private Queue queue; - /** Initialize your data structure here. */ - public MyStack() { - queue = new LinkedList<>(); - } - - /** Push element x onto stack. */ - public void push(int x) { - queue.add(x); - int cnt = queue.size(); - // 主要是这个while,元素倒过来 - while (cnt-- > 1) { - queue.add(queue.poll()); - } - } - - /** Removes the element on top of the stack and returns that element. */ - public int pop() { - return queue.remove(); - } - - /** Get the top element. */ - public int top() { - return queue.peek(); - } - - /** Returns whether the stack is empty. */ - public boolean empty() { - return queue.isEmpty(); - } -} -``` - -### 4. 用数组实现栈 - -```java -public class MyStack { - int[] data; // 数组 - int size; // 长度 - int top; // 栈顶的位置 - - public MyStack(int size) { - this.size = size; - data = new int[size]; - top = -1; - } - - public boolean isEmpty() { - return top == -1; - } - - public boolean isFull() { - return (top+1) == size; - } - - public boolean push(int data) { - if (isFull()) { - System.out.println("the stack is full!"); - return false; - } else { - this.data[++top] = data; - } - } - - public int pop() throws Exception { - if (isEmpty()) { - throw new Exception("the stack is empty!"); - } else { - return this.data[top--]; - } - } - - public int peek() { - return this.data[top]; - } -} -``` - -## 排序||top - -### 1、[最小的k个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class Solution { - public ArrayList GetLeastNumbers_Solution(int [] input, int k) { - ArrayList list = new ArrayList<>(); - if (input == null || input.length == 0 || k > input.length) return list; - Arrays.sort(input); - // 犯规就犯规 - for (int i = 0; i < k; i++) { - list.add(input[i]); - } - return list; - } -} -``` - -### 2. 数组中的第K个最大元素(855) -[https://leetcode-cn.com/problems/kth-largest-element-in-an-array/](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) - -排序 :时间复杂度 O(NlogN),空间复杂度 O(1) -```java -public int findKthLargest(int[] nums, int k) { - Arrays.sort(nums); - return nums[nums.length - k]; -} -``` - -堆 :时间复杂度 O(NlogK),空间复杂度 O(K)。 -```java -public int findKthLargest(int[] nums, int k) { - PriorityQueue pq = new PriorityQueue<>(); // 小顶堆 - for (int val : nums) { - pq.add(val); - if (pq.size() > k) // 维护堆的大小为 K - pq.poll(); - } - return pq.peek(); -} -``` - -快排 -```java -public int findKthLargest(int[] nums, int k) { - // 注意k - k = nums.length - k; - int l = 0, h = nums.length - 1; - while (l < h) { - int j = partition(nums, l, h); - if (j == k) { - break; - } else if (j < k) { - l = j + 1; - } else { - h = j - 1; - } - } - return nums[k]; -} - -private int partition(int[] a, int l, int h) { - int pivot = l; - int index = pivot + 1; - for (int i = index; i <= h; i++) { - if (a[i] < a[pivot]) - swap(a, i, index++); - } - swap(a, pivot, index - 1); - return index - 1; -} - -private void swap(int[] a, int i, int j) { - int t = a[i]; - a[i] = a[j]; - a[j] = t; -} -``` - -### 3. 寻找两个正序数组的中位数 -[https://leetcode-cn.com/problems/median-of-two-sorted-arrays/](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/) - -// 归并 - -```java -class Solution { - public double findMedianSortedArrays(int[] nums1, int[] nums2) { - int[] temp = new int[nums1.length + nums2.length]; - // 归并,三个指针,走起 - int i = 0; - int j = 0; - int t = 0; - while (i < nums1.length && j < nums2.length) { - temp[t++] = nums1[i] <= nums2[j] ? nums1[i++] : nums2[j++]; - } - - while (i < nums1.length) { - temp[t++] = nums1[i++]; - } - - while (j < nums2.length) { - temp[t++] = nums2[j++]; - } - // 加起来,取中位数 - double b = (temp[(temp.length - 1) / 2] + temp[temp.length / 2]) * 1.0 / 2; - return b; - } -} -``` - -### 4. 根据字符出现频率排序 - -```java -// 注意list的sort -class Solution { - public String frequencySort(String s) { - if (s == null || s.length() == 0) - return s; - // 先map统计 - Map map = new HashMap<>(); - for (int i = 0; i < s.length(); i++){ - char c = s.charAt(i); - map.put(c, map.getOrDefault(c, 0) + 1); - } - // 然后map.entrySet传进来 - List> list = new ArrayList<>(map.entrySet()); - // 按照value排序 - Collections.sort(list, (o1, o2) -> { - return o2.getValue() - o1.getValue(); - }); - // list - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : list){ - int cnt = entry.getValue(); - char key = entry.getKey(); - while (cnt-- > 0) - sb.append(key); - } - return sb.toString(); - } -} -``` - -### 148. 排序链表 -[https://leetcode-cn.com/problems/sort-list/](https://leetcode-cn.com/problems/sort-list/) - -归并排序 -1. 切 -2. 递归 -3. 合并 -```java -class Solution { - public ListNode sortList(ListNode head) { - return head == null ? null : mergeSort(head); - } - private ListNode mergeSort(ListNode head) { - if (head.next == null) - return head; - ListNode p = head, q = head, pre = null; - while (q != null && q.next != null){ - pre = p; - p = p.next; - q = q.next.next; - } - pre.next = null; - ListNode l = mergeSort(head); - ListNode r = mergeSort(p); - return merge(l, r); - } - - private ListNode merge(ListNode l1, ListNode l2){ - if (l1 == null) - return l2; - if (l2 == null) - return l1; - if (l1.val < l2.val){ - l1.next = merge(l1.next, l2); - return l1; - } else { - l2.next = merge(l1, l2.next); - return l2; - } - } -} -``` - -## 字符串 -### 1. 字符串相加 -[https://leetcode-cn.com/problems/add-strings/](https://leetcode-cn.com/problems/add-strings/) - -```java -class Solution { - public String addStrings(String num1, String num2) { - StringBuilder str = new StringBuilder(); - // 三个变量 carry i j:倒着来 - int carry = 0, i = num1.length() - 1, j = num2.length() - 1; - // while循环条件 注意|| - while (carry == 1 || i >= 0 || j >= 0) { - // 注意"0" - int x = i < 0 ? 0 : num1.charAt(i--) - '0'; - int y = j < 0 ? 0 : num2.charAt(j--) - '0'; - // 老生长谈了 - // 加的时候 - str.append((x + y + carry) % 10); - // 注意进位 - carry = (x + y + carry) / 10; - } - // 别忘了反转 - // 反转 - return str.reverse().toString(); - } -} -``` - -### 2. 反转字符串(660) -[https://leetcode-cn.com/problems/reverse-string/](https://leetcode-cn.com/problems/reverse-string/) - -```java -// 利用while反转交换 -class Solution { - public void reverseString(char[] s) { - int p1 = 0, p2 = s.length - 1; - while(p1 < p2){ - swap(s, p1++, p2--); - } - } - public void swap(char[] s, int i, int j) { - char temp = s[i]; - s[i] = s[j]; - s[j] = temp; - } -} -``` - -### 3. 无重复字符的最长子串(2862) -[https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) - -```java -class Solution { - public int lengthOfLongestSubstring(String s) { - int n = s.length(), ans = 0; - // map 加双指针。map来保留索引,类似于滑动窗 - Map map = new HashMap<>(); - for (int i = 0, j = 0; j < n; j++) { - if (map.containsKey(s.charAt(j))) { - i = Math.max(map.get(s.charAt(j)), i); - } - ans = Math.max(ans, j - i + 1); - map.put(s.charAt(j), j + 1); - } - return ans; - } -} -``` -### 4.1、[左旋转字符串](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public String LeftRotateString(String str, int n) { - if (n >= str.length()) - return str; - char[] chars = str.toCharArray(); - // 分三步反转 - // 1. n之前反转 - reverse(chars, 0, n - 1); - // 2. n之后反转 - reverse(chars, n, chars.length - 1); - // 3. 全部反转 - reverse(chars, 0, chars.length - 1); - return new String(chars); -} - -private void reverse(char[] chars, int i, int j) { - while (i < j) - swap(chars, i++, j--); -} - -private void swap(char[] chars, int i, int j) { - char t = chars[i]; - chars[i] = chars[j]; - chars[j] = t; -} - -``` - -### 4.2、[翻转单词顺序列](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。 - -```java -public String ReverseSentence(String str) { - int n = str.length(); - char[] chars = str.toCharArray(); - int i = 0, j = 0; - // 双指针,滑窗,,注意边界。 - while (j <= n) { - // 关键是这个判断边界 - if (j == n || chars[j] == ' ') { - // 反转 - reverse(chars, i, j - 1); - // 下个单词的索引开头 - i = j + 1; - } - // 继续走 - j++; - } - // 全反转 - reverse(chars, 0, n - 1); - return new String(chars); -} - -private void reverse(char[] c, int i, int j) { - while (i < j) - swap(c, i++, j--); -} - -private void swap(char[] c, int i, int j) { - char t = c[i]; - c[i] = c[j]; - c[j] = t; -} -``` - -### 5、[把字符串转成整数](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T49 { - public int StrToInt(String str) { - if (str == null || str.length() == 0) - return 0; - // 注意第一个字符是否是- - boolean isNegative = str.charAt(0) == '-'; - int ret = 0; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - // 跳过第一个字符 - if (i == 0 && (c == '+' || c == '-')) /* 符号判定 */ - continue; - // 防止非法输入 - if (c < '0' || c > '9') /* 非法输入 */ - return 0; - // 正常操作,注意“0” - ret = ret * 10 + (c - '0'); - } - return isNegative ? -ret : ret; - } -} -``` - - -## 数组 -### 1.1、[旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -// 变种二分 -public int minNumberInRotateArray(int[] nums) { - if (nums.length == 0) - return 0; - int l = 0, h = nums.length - 1; - // 注意条件 - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] <= nums[h]) - h = m; - else - l = m + 1; - } - // 注意返回值 - return nums[l]; -} -``` - -### 1.2 搜索旋转排序数组 -[https://leetcode-cn.com/problems/search-in-rotated-sorted-array/](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) -思路:如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了 -```java -class Solution { - public int search(int[] nums, int target) { - int len = nums.length; - int left = 0, right = len - 1; - while (left <= right) { - int mid = (left + right) / 2; - if (nums[mid] == target) - return mid; - else if(nums[mid] < nums[right]) { - // 注意边界 - if (nums[mid] < target && target <= nums[right]) - left = mid + 1; - else - right = mid - 1; - } else { - // 注意边界 - if (nums[left] <= target && target < nums[mid]) - right = mid - 1; - else - left = mid + 1; - } - } - return -1; - } -} -``` - -### 2. 两数之和(4897) -[https://leetcode-cn.com/problems/two-sum/](https://leetcode-cn.com/problems/two-sum/) -```java -// 双指针 -class Solution { - public int[] twoSum(int[] nums, int target) { - int p1 = 0, p2 = nums.length - 1; - while (p1 < p2) { - int sum = nums[p1] + nums[p2]; - if (sum < target) p1++; - else if (sum > target) p2--; - else return new int[] {p1, p2}; - } - return new int[]{}; - } -} -``` - -### 3. 三数之和 -[https://leetcode-cn.com/problems/3sum/](https://leetcode-cn.com/problems/3sum/) -```java -排序过后的双指针,注意重复 -class Solution { - public List> threeSum(int[] nums) { - // 排序 - Arrays.sort(nums); - List> ls = new ArrayList<>(); - for (int i = 0; i < nums.length - 2; i++) { - // 判断是否元素大于0,大于0,没必要操作了 - if (nums[i] > 0) - break; - // 判断是否重复 - if (i > 0 && nums[i] == nums[i - 1]) - continue; - // 双指针操作 - int l = i + 1, r = nums.length - 1; - while (l < r) { - if (nums[l] + nums[r] < -nums[i]) l++; - else if (nums[l] + nums[r] > -nums[i]) r--; - else { - // 相等了哈 - ls.add(Arrays.asList(nums[i], nums[l], nums[r])); - // 防止重复 - while (l < r && nums[l] == nums[l + 1]) l++; - while (l < r && nums[r] == nums[r - 1]) r--; - l++; - r--; - } - } - } - return ls; - } -} -``` - -### 4. 二分查找 -[https://leetcode-cn.com/problems/binary-search/](https://leetcode-cn.com/problems/binary-search/) -```java -class Solution { - public int search(int[] nums, int target) { - if (nums == null || nums.length == 0) return -1; - int l = 0, h = nums.length - 1; - while (l <= h) { - int m = l + (h - l) / 2; - if (nums[m] == target) return m; - else if (nums[m] < target) l = m + 1; - else h = m - 1; - } - return -1; - } -} -``` - -### 5、[顺时针打印矩阵](https:/www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -跟lc的螺旋矩阵一样 -```java -public class T19 { - public ArrayList printMatrix(int [][] matrix) { - ArrayList list = new ArrayList<>(); - int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; - while(r1 <= r2 && c1 <= c2) { - for (int i = c1; i <= c2; i++) { - list.add(matrix[r1][i]); - } - for (int i = r1 + 1; i <= r2; i++) { - list.add(matrix[i][c2]); - } - // 注意边界 - if (r1 != r2) { - for (int i = c2 - 1; i >= c1; i--) { - list.add(matrix[r2][i]); - } - } - // 注意边界 - if (c1 != c2) { - for (int i = r2 - 1; i >= r1; i--) { - list.add(matrix[i][c1]); - } - } - r1++; r2--; c1++; c2--; - } - return list; - } -} -``` -### 5. 旋转数组 -[https://leetcode-cn.com/problems/rotate-array/](https://leetcode-cn.com/problems/rotate-array/) -```java -class Solution { - public void rotate(int[] nums, int k) { - int n = nums.length; - // 注意这个k - k %= n; - reverse(nums, 0, n - 1); - reverse(nums, 0, k - 1); - reverse(nums, k, n - 1); - } - private void reverse(int[] nums, int start, int end) { - while(start < end) { - int temp = nums[start]; - nums[start++] = nums[end]; - nums[end--] = temp; - } - } -} -``` - -### 6. 缺失的第一个正数 -[https://leetcode-cn.com/problems/first-missing-positive/](https://leetcode-cn.com/problems/first-missing-positive/) -采用排序的犯规操作 -```java -class Solution { - public int firstMissingPositive(int[] nums) { - int ans = 1; - // 犯规操作 - Arrays.sort(nums); - for (int i = 0; i < nums.length; i++) { - if (nums[i] > ans) break; - if (nums[i] == ans) ans++; - } - return ans; - } -} -``` - -## dp||贪心 - -### 1.1、[斐波那契数列](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T7 { - public int Fibonacci(int n) { - // 条件 - if (n <= 1) return n; - // 可以用自底向上的方法 - int pre2 = 0, pre1 = 1; - int f = 0; - for (int i = 2; i <= n; i++) { - f = pre2 + pre1; // 如果动态规划,这个就是dp的公式 - pre2 = pre1; - pre1 = f; - } - return f; - } -} -``` - -### 1.2、[跳台阶](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -```java -public class T8 { - public int JumpFloor(int target) { - // 条件 - if (target <= 2) return target; - // 自底向上的方法 - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; // 一样的道理, 和上面那道题的初始值不一样 - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} -``` - -### 1.3、[矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T10 { - public int RectCover(int target) { - // 条件 - if (target <= 2) return target; - // 自底向上 - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; // 同理呀 - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} -``` - -### 1.4、[变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public int JumpFloorII(int target) { - int[] dp = new int[target]; - Arrays.fill(dp, 1); - // 注意起始位置 - for (int i = 1; i < target; i++) - // 开始跳 - for (int j = 0; j < i; j++) - // 注意dp[i] 累计dp[j] - dp[i] += dp[j]; - return dp[target - 1]; -} -``` - - - -### 2. 最大子序和(1385) -[https://leetcode-cn.com/problems/maximum-subarray/](https://leetcode-cn.com/problems/maximum-subarray/) -```java -class Solution { - public int maxSubArray(int[] nums) { - if (nums == null || nums.length == 0) return 0; - // 注意两个变量的初始化 - int preSum = nums[0]; - int maxSum = preSum; - // 注意从1开始 - for (int i = 1; i < nums.length; i++) { - // 注意这个条件 - preSum = preSum > 0 ? preSum + nums[i] : nums[i]; - maxSum = Math.max(maxSum, preSum); - } - return maxSum; - } -} -``` - -### 3.1、[股票的最大利润](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/description/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china) - -```java -class Solution { - public int maxProfit(int[] prices) { - if (prices == null || prices.length == 0) return -1; - int min = prices[0]; - int max = 0; - // 从1开始 - for (int i = 1; i < prices.length; i++) { - // 注意保持最小 - min = prices[i] < min ? prices[i] : min; - max = Math.max(max, prices[i] - min); - } - return max; - } -} -``` -### 3.2 买卖股票的最佳时机 II -[https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) -```java -class Solution { - public int maxProfit(int[] prices) { - // 贪心:只要我当前数比前一个数大, 就xxx - int profit = 0; - // 从1开始,因为下面的if - for (int i = 1; i < prices.length; i++) { - if (prices[i] > prices[i - 1]) - profit += prices[i]- prices[i - 1]; - } - return profit; - } -} -``` -### 3.3 买卖股票的最佳时机含手续费 -状态机 -```java -class Solution { - public int maxProfit(int[] prices, int fee) { - if (prices == null || prices.length == 0) - return 0; - int buy = Integer.MIN_VALUE; // 购买股票后的收益,开始购买第一支股票后肯定是负数 - int sell = 0; // 售卖第一次股票后 - for (int i = 0; i < prices.length; i++){ - buy = Math.max(buy, sell - prices[i]); - sell = Math.max(sell, buy + prices[i] - fee); //手续费在交易完成时一次性扣除 - } - return sell; - } -} -``` - -### 4. 打家劫舍 -[https://leetcode-cn.com/problems/house-robber/description/](https://leetcode-cn.com/problems/house-robber/description/) -```java -class Solution { - public int rob(int[] nums) { - int pre2 = 0, pre1 = 0; - for (int i = 0; i < nums.length; i++) { - // 注意这个状态转移,毕竟题目是隔着偷 - int cur = Math.max(pre2 + nums[i], pre1); - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - -### 5. 打家劫舍 II -[https://leetcode-cn.com/problems/house-robber-ii/description/](https://leetcode-cn.com/problems/house-robber-ii/description/) -```java -class Solution { - public int rob(int[] nums) { - if (nums == null || nums.length == 0) return 0; - int n = nums.length; - if (n == 1) return nums[0]; - // 注意0-n-2 个 1 -n-1 - return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1)); - } - - private int rob(int[] nums, int first, int last) { - int pre2 = 0, pre1 = 0; - for (int i = first; i <= last; i++) { - int cur = Math.max(pre1, pre2 + nums[i]); - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - - -### 6、[剪绳子](https://www.nowcoder.com/practice/57d85990ba5b440ab888fc72b0751bf8?tpId=13&tqId=33257&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) -```java -// 动态规划 -public int integerBreak(int n) { - int[] dp = new int[n + 1]; - dp[1] = 1; - // 一厘米,没法切,所以从2 - for (int i = 2; i <= n; i++) - // 切从1cm开始 - for (int j = 1; j < i; j++) - // 注意这个状态转移 - dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j))); - return dp[n]; -} -``` -### 7、[礼物的最大值](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab) - -```java -public int getMost(int[][] values) { - if (values == null || values.length == 0 || values[0].length == 0) - return 0; - int n = values[0].length; - int[] dp = new int[n]; - for (int[] value : values) { - dp[0] += value[0]; - for (int i = 1; i < n; i++) - dp[i] = Math.max(dp[i], dp[i - 1]) + value[i]; - } - return dp[n - 1]; -} -``` - -### 8. 最小路径和 -[https://leetcode-cn.com/problems/minimum-path-sum/description/](https://leetcode-cn.com/problems/minimum-path-sum/description/) -```java -class Solution { - public int minPathSum(int[][] grid) { - if (grid.length == 0 || grid[0].length == 0) return 0; - int m = grid.length, n = grid[0].length; - // 优化过后的dp - int[] dp = new int[n]; - for (int i = 0; i < m; i ++) { - for (int j = 0; j < n; j++) { - if (j == 0) // 注意 - dp[j] = dp[j]; - else if (i == 0) // 注意 - dp[j] = dp[j - 1]; - else // 注意 - dp[j] = Math.min(dp[j], dp[j - 1]); - // 别忘了 - dp[j] += grid[i][j]; - } - } - return dp[n-1]; - } -} -``` - -```java -class Solution { - public int minPathSum(int[][] grid) { - if (grid == null || grid.length == 0 || grid[0].length == 0) - return 0; - int m = grid.length; - int n = grid[0].length; - int[][] dp = new int[m][n]; - dp[0][0] = grid[0][0]; - // 第一列 - for (int i = 1; i < m; i++){ - dp[i][0] = dp[i - 1][0] + grid[i][0]; - } - // 第一行 - for (int j = 1; j < n; j++){ - dp[0][j] = dp[0][j - 1] + grid[0][j]; - } - - for (int i = 1; i < m; i++) { - for (int j = 1; j < n; j++){ - dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; - } - } - return dp[m - 1][n - 1]; - - } -} -``` - -### 9. 不同路径 -[https://leetcode-cn.com/problems/unique-paths/description/](https://leetcode-cn.com/problems/unique-paths/description/) - -```java -class Solution { - public int uniquePaths(int m, int n) { - // 优化过后了 - int[] dp = new int[n]; - // 注意 - Arrays.fill(dp, 1); - // 注意起始位置 - for (int i = 1; i < m; i++) { - for (int j = 1; j < n; j++) { - // 累加 - dp[j] += dp[j - 1]; - } - } - return dp[n -1]; - } -} -``` - -```java -class Solution { - public int uniquePaths(int m, int n) { - int[][] dp = new int[m][n]; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (i == 0 || j == 0) - dp[i][j] = 1; - else { - dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; - } - } - } - return dp[m - 1][n - 1]; - } -} -``` - -### 9.2 不同路径 II -[https://leetcode-cn.com/problems/unique-paths-ii/](https://leetcode-cn.com/problems/unique-paths-ii/) - -```java -class Solution { - public int uniquePathsWithObstacles(int[][] obstacleGrid) { - int m = obstacleGrid.length; - int n = obstacleGrid[0].length; - // 因为if - int[] dp = new int[n + 1]; - dp[1] = 1; // 注意初始值 - // 起始位置 - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= n; j++) { - // 别忘了条件 - if (obstacleGrid[i - 1][j - 1] == 1) - dp[j] = 0; - else - dp[j] += dp[j - 1]; - } - } - return dp[n]; - } -} -``` - -```java -class Solution { - public int uniquePathsWithObstacles(int[][] obstacleGrid) { - int m = obstacleGrid.length; - int n = obstacleGrid[0].length; - int [][] dp = new int[m+1][n+1]; - // 第一行 和 其他行的区别在于没有来自上边的路径 但是 起点到起点 算一条路径 所以这样初始化 - dp[0][1] = 1; - for(int i = 1; i <= m; i++) { - for(int j = 1; j <= n; j++) { - if(obstacleGrid[i-1][j-1] == 1) { - // 障碍 不可达 路径数量为0 - dp[i][j] = 0; - } - else { - // 左 + 上 - dp[i][j] = dp[i-1][j] + dp[i][j-1]; - } - } - } - return dp[m][n]; - } -} -``` - -```java -class Solution { - public int uniquePathsWithObstacles(int[][] obstacleGrid) { - int m = obstacleGrid.length; - int n = obstacleGrid[0].length; - int[][] dp = new int[m][n]; - dp[0][0] = 1; - for (int i = 0; i < m; i++){ - for (int j = 0; j < n; j++){ - if (obstacleGrid[i][j] == 1) - continue; - if (i == 0 && j == 0) - continue; - if(i == 0) - dp[i][j] = dp[i][j - 1]; - else if (j == 0) - dp[i][j] = dp[i - 1][j]; - else dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; - } - } - return dp[m - 1][n - 1]; - } -} -``` - -### 9.3 最大正方形 -[https://leetcode-cn.com/problems/maximal-square/](https://leetcode-cn.com/problems/maximal-square/) -```java -class Solution { - public int maximalSquare(char[][] matrix) { - /** - dp[i][j]表示以第i行第j列为右下角所能构成的最大正方形边长, 则递推式为: - dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]); - **/ - if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0; - int m = matrix.length; - int n = matrix[0].length; - int max = 0; - int[][] dp = new int[m + 1][n + 1]; - - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= n; j++){ - if (matrix[i-1][j-1] == '1') { - // 左, 右,左上 - dp[i][j] = 1 + Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])); - max = Math.max(max, dp[i][j]); - } - } - } - return max * max; - } -} -``` - -### 10. 解码方法 -[https://leetcode-cn.com/problems/decode-ways/description/](https://leetcode-cn.com/problems/decode-ways/description/) -```java -class Solution { - public int numDecodings(String s) { - if (s == null || s.length() == 0) { - return 0; - } - int n = s.length(); - int[] dp = new int[n + 1]; - dp[0] = 1; // 初始值 - // 注意第一个元素是0? - dp[1] = s.charAt(0) == '0' ? 0 : 1; - // 注意起始位置, - for (int i = 2; i <= n; i++) { - // substring 用的很骚 - int one = Integer.valueOf(s.substring(i - 1, i)); - if (one != 0) { - dp[i] += dp[i - 1]; - } - // 注意这个判断 - if (s.charAt(i - 2) == '0') continue; - int two = Integer.valueOf(s.substring(i - 2, i)); - if (two <= 26) { - dp[i] += dp[i - 2]; - } - } - return dp[n]; - } -} -``` - -### 11. 最长上升子序列 -[https://leetcode-cn.com/problems/longest-increasing-subsequence/description/](https://leetcode-cn.com/problems/longest-increasing-subsequence/description/) -```java -class Solution { - public int lengthOfLIS(int[] nums) { - if (nums.length == 0) return 0; - int[] dp = new int[nums.length]; - // 注意这个初始化 - Arrays.fill(dp, 1); - for (int i = 0; i < nums.length; i++) { - for (int j = 0; j < i; j++) { - if (nums[i] > nums[j]) { - // 注意if - dp[i] = Math.max(dp[i], dp[j] + 1); // 关键这里, - } - } - } - // 找最大 - return Arrays.stream(dp).max().orElse(0); - } -} -``` - -### 12. 最长公共子序列 -[https://leetcode-cn.com/problems/longest-common-subsequence/](https://leetcode-cn.com/problems/longest-common-subsequence/) -```java -class Solution { - public int longestCommonSubsequence(String text1, String text2) { - int n1 = text1.length(), n2 = text2.length(); - int[][] dp = new int[n1 + 1][n2 + 1]; - for (int i = 1; i <= n1; i++) { - for (int j = 1; j <= n2; j++) { - if (text1.charAt(i - 1) == text2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1] + 1; - } else { - dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); - } - } - } - return dp[n1][n2]; - } -} -``` - -### 13.1 0/1背包 -[https://www.nowcoder.com/questionTerminal/7e157ce9a8c249daa3ddafad322dbf1e?answerType=1&f=discussion](https://www.nowcoder.com/questionTerminal/7e157ce9a8c249daa3ddafad322dbf1e?answerType=1&f=discussion) - -```java -import java.util.*; -public class Main{ - public static void main(String[] args){ - Scanner sc = new Scanner(System.in); - int n = sc.nextInt(); - int W = sc.nextInt(); - int[] ws = new int[n]; - int[] vals = new int[n]; - for (int i = 0; i < n; i++){ - ws[i] = sc.nextInt(); - } - for (int i = 0; i < n; i++){ - vals[i] = sc.nextInt(); - } - int[] dp = new int[W + 1]; - for (int i = 1; i <= n; i++){ - int w = ws[i - 1], v = vals[i - 1]; - for (int j = W; j >=0; j--){ //倒着遍历 - if (j >= w){ - dp[j] = Math.max(dp[j], dp[j - w] + v); - } - } - } - System.out.println(dp[W]); - } -} -``` - -### 13.2 1和0 -[https://leetcode-cn.com/problems/ones-and-zeroes/](https://leetcode-cn.com/problems/ones-and-zeroes/) -这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。 -```java -class Solution { - public int findMaxForm(String[] strs, int m, int n) { - if (strs == null || strs.length == 0) - return -1; - // 俩包, 0 1 - int[][] dp = new int[m + 1][n + 1]; - for (String s : strs){ - int zeros = 0, ones = 0; - for (char c : s.toCharArray()){ - if (c == '0') - zeros++; // 统计数量 - else - ones++; // 统计数量 - } - // 开始dp - for (int i = m; i >= zeros; i--){ - for (int j = n; j >= ones; j--){ - // 优化过后的dp - dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1); - } - } - } - return dp[m][n]; - } -} -``` - -### 13.3 零钱兑换 -[https://leetcode-cn.com/problems/coin-change/description/](https://leetcode-cn.com/problems/coin-change/description/) -完全背包问题 -```java -class Solution { - public int coinChange(int[] coins, int amount) { - if (amount == 0) return 0; - int[] dp = new int[amount + 1]; - for (int coin : coins) { - for (int i = coin; i <= amount; i++) { //将逆序遍历改为正序遍历 - // 三种情况 - if (i == coin) { - dp[i] = 1; - } else if (dp[i] == 0 && dp[i - coin] != 0) { - dp[i] = dp[i - coin] + 1; - - } else if (dp[i - coin] != 0) { - dp[i] = Math.min(dp[i], dp[i - coin] + 1); - } - } - } - return dp[amount] == 0 ? -1 : dp[amount]; - } - -} -``` - -### 13.4 找零钱的硬币数组合 -[https://leetcode-cn.com/problems/coin-change-2/description/](https://leetcode-cn.com/problems/coin-change-2/description/) -```java -class Solution { - public int change(int amount, int[] coins) { - if (coins == null) return 0; - int[] dp = new int[amount + 1]; - dp[0] = 1; - for (int coin : coins) { - for (int i = coin; i <= amount; i++) { - dp[i] += dp[i - coin]; - } - } - return dp[amount]; - } -} -``` - -### 13.5. 单词拆分 -[https://leetcode-cn.com/problems/word-break/description/](https://leetcode-cn.com/problems/word-break/description/) -求解顺序的完全背包问题 -```java -class Solution { - public boolean wordBreak(String s, List wordDict) { - int n = s.length(); - boolean[] dp = new boolean[n + 1]; - dp[0] = true; - for (int i = 1; i <= n; i++) { - for (String word: wordDict) { - // 对物品的迭代应该放在最里层 - int len = word.length(); - if (len <= i && word.equals(s.substring(i - len , i))) { - dp[i] = dp[i] || dp[i - len]; - } - } - } - return dp[n]; - } -} -``` - -### 15. 编辑距离 -[https://leetcode-cn.com/problems/edit-distance/description/](https://leetcode-cn.com/problems/edit-distance/description/) - -```java -class Solution { - public int minDistance(String word1, String word2) { - if (word1 == null || word2 == null) { - return 0; - } - int m = word1.length(), n = word2.length(); - int[][] dp = new int[m + 1][n + 1]; - for (int i = 1; i <= m; i++) { - dp[i][0] = i; - } - for (int i = 1; i <= n; i++) { - dp[0][i] = i; - } - // 这dp - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= n; j++) { - if (word1.charAt(i - 1) == word2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1; - } - } - } - return dp[m][n]; - } - -} -``` - -## 其他 -### 1. x 的平方根 -[https://leetcode-cn.com/problems/sqrtx/](https://leetcode-cn.com/problems/sqrtx/) - -```java -class Solution { - public int mySqrt(int x) { - if (x <= 1) return x; - int l = 1, h = x; - while (l <= h) { - int mid = l + (h - l) / 2; - int sqrt = x / mid; - if (sqrt == mid) return mid; - else if (mid > sqrt) h = mid - 1; - else l = mid + 1; - } - return h; - } -} -``` - -### 2. 移动零 -[https://leetcode-cn.com/problems/move-zeroes/](https://leetcode-cn.com/problems/move-zeroes/) - -```java -class Solution { - public void moveZeroes(int[] nums) { - int idx = 0; - for (int num : nums) { - if (num != 0) nums[idx++] = num; - } - while (idx < nums.length) { - nums[idx++] =0; - } - } -} -``` - -### 3. 接雨水(字节爱出) -[https://leetcode-cn.com/problems/trapping-rain-water/](https://leetcode-cn.com/problems/trapping-rain-water/) -```java -class Solution { - public int trap(int[] height) { - int min = 0, max = 0; - int l = 0, r = height.length - 1; - int res = 0; - while(l < r) { - min = height[height[l] < height[r] ? l++ : r--]; - max = Math.max(max, min); - res += max - min; - } - return res; - } -} -``` - -### 4. 下一个排列 -[https://leetcode-cn.com/problems/next-permutation/](https://leetcode-cn.com/problems/next-permutation/) - -```java - //源于离散数学及其应用的算法:(以3 4 5 2 1 为例) - //从后往前寻找第一次出现的正序对:(找到 4,5) - //之后因为从5 开始都是逆序,所以把他们反转就是正序:3 4 1 2 5 - //之后4 的位置应该是:在它之后的,比他大的最小值(5) - //交换这两个值:得到 3 5 1 2 4 - // 对于初始即为逆序的序列,将在反转步骤直接完成 -class Solution { - public void nextPermutation(int[] nums) { - int len = nums.length; - if (len < 2) return; - int i = len - 1; - while (i > 0 && nums[i - 1] >= nums[i]) - i--; // 从后向前找第一个正序,这里最后i指向的是逆序起始位置 - reverse(nums, i, len - 1); // 翻转后面的逆序区域,使其变为正序 - if (i == 0) return; - int j = i - 1; - while(i < len && nums[j] >= nums[i]) - i++; // 找到第一个比nums[j]大的元素,交换即可 - // 交换 - swap(nums, i, j); - } - private void reverse(int[] nums, int i, int j) { - while (i < j) { - swap(nums, i++, j--); - } - } - - private void swap(int[] nums, int i, int j){ - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; - } -} -``` - -### 5. [孩子们的游戏](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。 - -```java -public int LastRemaining_Solution(int n, int m) { - if (n == 0) /* 特殊输入的处理 */ - return -1; - if (n == 1) /* 递归返回条件 */ - return 0; - return (LastRemaining_Solution(n - 1, m) + m) % n; -} -``` - -### 165. 比较版本号 - -[https://leetcode-cn.com/problems/compare-version-numbers/](https://leetcode-cn.com/problems/compare-version-numbers/) - -```java -class Solution { - public int compareVersion(String version1, String version2) { - String[] a1 = version1.split("\\."); - String[] a2 = version2.split("\\."); - - for (int n = 0; n < Math.max(a1.length, a2.length); n++) { - int i = n < a1.length ? Integer.valueOf(a1[n]) : 0 ; - int j = n < a2.length ? Integer.valueOf(a2[n]) : 0 ; - if (i < j) - return -1; - else if (i > j) - return 1; - return 0; - } - } -} -``` - -### 209. 长度最小的子数组 -```java -class Solution { - public int minSubArrayLen(int s, int[] nums) { - // 滑动窗口 - int i = 0; - int sum = 0; - int len = 0; - for (int j = 0; j < nums.length; j++){ - sum += nums[j]; - // 注意这个骚条件 - while (sum >= s){ - len = len == 0 ? j - i + 1 : Math.min(len, j - i + 1); - // 滑动 - sum -= nums[i++]; - } - } - return len; - } -} -``` \ No newline at end of file diff --git "a/Interview/myself/\345\211\221\346\214\207offer.md" "b/Interview/myself/\345\211\221\346\214\207offer.md" deleted file mode 100644 index 33e898c5..00000000 --- "a/Interview/myself/\345\211\221\346\214\207offer.md" +++ /dev/null @@ -1,3147 +0,0 @@ -## 3、[数组中重复的数字](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 - -```html -Input: -{2, 3, 1, 0, 2, 5} - -Output: -2 -``` - -### 解题思路 - -要求时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。 - -对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。本题要求找出重复的数字,因此在调整过程中,如果第 i 位置上已经有一个值为 i 的元素,就可以知道 i 值重复。 - -以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复: - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/643b6f18-f933-4ac5-aa7a-e304dbd7fe49.gif) - -```java -public boolean duplicate(int[] nums, int length, int[] duplication) { - if (nums == null || length <= 0) - return false; - for (int i = 0; i < length; i++) { - while (nums[i] != i) { // 该value和索引不相等,则i,nums[i]的索引的value互相交换 - if (nums[i] == nums[nums[i]]) { // 如果value和索引相等了,那么重复了 - duplication[0] = nums[i]; - return true; - } - swap(nums, i, nums[i]); - } - } - return false; -} - -private void swap(int[] nums, int i, int j) { - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; -} - -``` - - - -```java -public class T50 { - public boolean duplicate(int numbers[],int length,int [] duplication) { - // 边界 - if (numbers == null || numbers.length == 0) { - return false; - } - // 排序 - Arrays.sort(numbers); - int flag = 0; - for (int i = 0; i < length - 1; i++) { - if (numbers[i] == numbers[i + 1]) { - duplication[0] = numbers[i]; - flag = 1; - break; - } - } - return flag == 1 ? true : false; - } -} -``` - -## 4、[二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - -### 题目描述 - -给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。 - -```html -Consider the following matrix: -[ - [1, 4, 7, 11, 15], - [2, 5, 8, 12, 19], - [3, 6, 9, 16, 22], - [10, 13, 14, 17, 24], - [18, 21, 23, 26, 30] -] - -Given target = 5, return true. -Given target = 20, return false. - -``` - -### 解题思路 - -要求时间复杂度 O(M + N),空间复杂度 O(1)。其中 M 为行数,N 为 列数。 - -该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/35a8c711-0dc0-4613-95f3-be96c6c6e104.gif) - -```java -public boolean Find(int target, int[][] matrix) { - if (matrix == null || matrix.length == 0 || matrix[0].length == 0) - return false; - int rows = matrix.length, cols = matrix[0].length; - int r = 0, c = cols - 1; // 从右上角开始 - while (r <= rows - 1 && c >= 0) { - if (target == matrix[r][c]) // 如果相等,直接返回 - return true; - else if (target > matrix[r][c]) // 如果小于target, r++; - r++; - else - c--; // 如果大于,c--; - } - return false; -} - -``` - - - -```java -public class T1 { - public boolean Find(int target, int [][] array) { - // 研究数组的特性,比如: - // 3 4 5 - // 4 8 6 - // 5 9 10 - int col = 0; - for (int i = array.length - 1; i >= 0; i--) { - // 最后一行开始,按列遍历和target比较: - // 如果= 0 && P2 > P1) { - char c = str.charAt(P1--); - if (c == ' ') { - str.setCharAt(P2--, '0'); // 倒着替换 - str.setCharAt(P2--, '2'); - str.setCharAt(P2--, '%'); - } else { - str.setCharAt(P2--, c); - } - } - return str.toString(); -} -``` - - - -```java -public class T2 { - public String replaceSpace(StringBuffer str) { - // 检测空格数目 - int spaceNum = 0; - // 第一遍循环,检测空格的数目 - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == ' ') spaceNum++; - } - // 创建新数组 ,也可以StringBuilder - char[] ans = new char[str.length() + 2 * spaceNum]; - int p1 = ans.length - 1; - // 倒着遍历,一个一个添加 - for (int i = str.length() - 1; i >= 0; i++) { - if (str.charAt(i) == ' ') { - ans[p1--] = '0'; - ans[p1--] = '2'; - ans[p1--] = '%'; - } else { - ans[p1--] = str.charAt(i); - } - } - return new String(ans); - } -} -``` - -## 6、[从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -从尾到头反过来打印出每个结点的值。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/f5792051-d9b2-4ca4-a234-a4a2de3d5a57.png) - -### 解题思路 - -要逆序打印链表 1->2->3(3,2,1),可以先逆序打印链表 2->3(3,2),最后再打印第一个节点 1。而链表 2->3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。 - -```java -public class T3 { - // 创建list - ArrayList list = new ArrayList<>(); - public ArrayList printListFromTailToHead(ListNode listNode) { - // 判断头节点是否为空 - if (listNode != null) { - // 递归打印 - this.printListFromTailToHead(listNode.next); - list.add(listNode.val); - } - return list; - } -} -``` - -## 7、[重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191102210342488.png) - -### 解题思路 - -前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。然后分别对左右子树递归地求解。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/60c4a44c-7829-4242-b3a1-26c3b513aaf0.gif) - - - -```java -class Solution { - public TreeNode buildTree(int[] preorder, int[] inorder) { - int n = preorder.length; - if (n == 0) - return null; - int rootVal = preorder[0], rootIndex = 0; - for (int i = 0; i < n; i++) { - if (inorder[i] == rootVal) { - rootIndex = i; - break; - } - } - TreeNode root = new TreeNode(rootVal); - root.left = buildTree(Arrays.copyOfRange(preorder, 1, 1 + rootIndex), Arrays.copyOfRange(inorder, 0, rootIndex)); - root.right = buildTree(Arrays.copyOfRange(preorder, 1 + rootIndex, n), Arrays.copyOfRange(inorder, rootIndex + 1, n)); - - return root; - } -} -``` - -## 8、[二叉树的下一个结点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回 。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 - -```java -public class TreeLinkNode { - - int val; - TreeLinkNode left = null; - TreeLinkNode right = null; - TreeLinkNode next = null; // 指向父结点的指针 - - TreeLinkNode(int val) { - this.val = val; - } -} -``` - -### 解题思路 - -我们先来回顾一下中序遍历的过程:先遍历树的左子树,再遍历根节点,最后再遍历右子树。所以最左节点是中序遍历的第一个节点。 - -```java -void traverse(TreeNode root) { - if (root == null) return; - traverse(root.left); - visit(root); - traverse(root.right); -} -``` - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ad5cc8fc-d59b-45ce-8899-63a18320d97e.gif) - -① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点; - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7008dc2b-6f13-4174-a516-28b2d75b0152.gif) - -② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/094e3ac8-e080-4e94-9f0a-64c25abc695e.gif) - - - -```java -public class T57 { - public TreeLinkNode GetNext(TreeLinkNode pNode) { - if (null == pNode) { - return null; - } - if (null != pNode.right) { - TreeLinkNode node = pNode.right; - while (null != node.left) { - node = node.left; - } - return node; - } - while (null != pNode.next) { - TreeLinkNode parent = pNode.next; - if (parent.left == pNode) { - return parent; - } - pNode = pNode.next; - } - return null; - } -} -``` - - -## 9、[用两个栈实现一个队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 - -### 解题思路 - -in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/3ea280b5-be7d-471b-ac76-ff020384357c.gif) - -```java -public class T5 { - // 举个例子: - // 1,2,3,4,5依次push - // stack1:5,4,3,2,1 栈顶是5 - // stack2:1,2,3,4,5 栈顶是1 - // 这样就是队列的先进先出了 - Stack in = new Stack<>(); - - Stack out = new Stack<>(); - - public void push (int node) { - // 添加value - in.push(node); - } - - public int pop() { - // 判断stack2是否为空 - if (out.isEmpty()) { - // 如果为空 - while (!in.isEmpty()) { - // 并且stack1不为空,然后将栈1所有的元素重新弹出去添加到栈2 - // 这样的话,用栈2弹,就是FIFO的队列了 - out.push(stack1.pop()); - } - } - return out.pop(); - } -} -``` - -## 10.1、[斐波那契数列](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -求斐波那契数列的第 n 项,n <= 39。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/45be9587-6069-4ab7-b9ac-840db1a53744.jpg) - -### 解题思路 - -如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c13e2a3d-b01c-4a08-a69b-db2c4e821e09.png) - -递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。 - -```java -public int Fibonacci(int n) { - if (n <= 1) - return n; - int[] dp = new int[n + 1]; - dp[1] = 1; - for (int i = 2; i <= n; i++) - dp[i] = dp[i - 1] + dp[i - 2]; - return dp[n]; -} - -``` - -考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1) - -```java -public class T7 { - public int Fibonacci(int n) { - // 条件 - if (n <= 1) return n; - // 可以用自底向上的方法 - int pre2 = 0, pre1 = 1; - int f = 0; - for (int i = 2; i <= n; i++) { - f = pre2 + pre1; // 如果动态规划,这个就是dp的公式 - pre2 = pre1; - pre1 = f; - } - return f; - } -} -``` -## 10.2、[跳台阶](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 - -### 解题思路 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/508c6e52-9f93-44ed-b6b9-e69050e14807.jpg) - -```java -public class T8 { - public int JumpFloor(int target) { - // 条件 - if (target <= 2) return target; - // 自底向上的方法 - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; // 一样的道理, 和上面那道题的初始值不一样 - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} -``` - -## 10.3、[矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法? - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b903fda8-07d0-46a7-91a7-e803892895cf.gif) - -### 解题思路 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/508c6e52-9f93-44ed-b6b9-e69050e14807.jpg) - - - -```java -public class T10 { - public int RectCover(int target) { - // 条件 - if (target <= 2) return target; - // 自底向上 - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; // 同理呀 - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} -``` - - -## 10.4、[变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cd411a94-3786-4c94-9e08-f28320e010d5.png) - -### 解题思路 - -```java -public int JumpFloorII(int target) { - int[] dp = new int[target]; - Arrays.fill(dp, 1); - for (int i = 1; i < target; i++) - for (int j = 0; j < i; j++) - dp[i] += dp[j]; - return dp[target - 1]; -} -``` - - - -```java -public class T9 { - public int JumpFloorII(int target) { - // 公式 2的target-1次方 - return 1 << (target - 1); - } -} -``` - - -## 11、[旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0038204c-4b8a-42a5-921d-080f6674f989.png) - -```java -public int minNumberInRotateArray(int[] nums) { - if (nums.length == 0) - return 0; - int l = 0, h = nums.length - 1; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] <= nums[h]) - h = m; - else - l = m + 1; - } - return nums[l]; -} -``` - - - -```java -public class T6 { - // 这道题也可以倒着遍历 - public int minNumberInRotateArray(int [] array) { - // 判断条件 - if (array.length == 0) return 0; - if (array.length == 1) return array[0]; - - int a = array[0]; - // 根据数组的特征,一开始递增,突然变小,于是,那个突然变小的那个元素就是最小数字 - for (int i = 1; i < array.length; i++) { - if (a > array[i]) { - // array[i] < a,则代表最小 - return array[i]; - } else { // 否则 a - a = array[i]; - } - } - return 0; - } -} -``` - -## 12、[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 - -例如下面的矩阵包含了一条 bfce 路径。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1db1c7ea-0443-478b-8df9-7e33b1336cc4.png) - -### 解题思路 - -使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。例如下图示例中,从 f 开始,下一步有 4 种搜索可能,如果先搜索 b,需要将 b 标记为已经使用,防止重复使用。在这一次搜索结束之后,需要将 b 的已经使用状态清除,并搜索 c。 - -```java -private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; -private int rows; -private int cols; - -public boolean hasPath(char[][] matrix, int rows, int cols, char[] str) { - if (rows == 0 || cols == 0) return false; - this.rows = rows; - this.cols = cols; - boolean[][] marked = new boolean[rows][cols]; - for (int i = 0; i < rows; i++) - for (int j = 0; j < cols; j++) - if (backtracking(matrix, str, marked, 0, i, j)) - return true; - - return false; -} - -private boolean backtracking(char[][] matrix, char[] str, - boolean[][] marked, int pathLen, int r, int c) { - - if (pathLen == str.length) return true; - if (r < 0 || r >= rows || c < 0 || c >= cols - || matrix[r][c] != str[pathLen] || marked[r][c]) { - - return false; - } - marked[r][c] = true; - for (int[] n : next) - if (backtracking(matrix, str, marked, pathLen + 1, r + n[0], c + n[1])) - return true; - marked[r][c] = false; - return false; -} - -``` - -```java -public class T65 { - public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { - if (null == matrix || matrix.length == 0 || null == str || str.length == 0 || matrix.length != rows * cols || rows <= 0 || cols <= 0){ - return false; - } - boolean[] visited = new boolean[rows * cols]; - int[] pathLength = {0}; - for (int i = 0; i <= rows - 1; i++){ - for (int j = 0; j <= cols - 1; j++){ - if (hasPathCore(matrix, rows, cols, str, i, j, visited, pathLength)) { - return true; - } - } - } - return false; - } - - public boolean hasPathCore(char[] matrix, int rows, int cols, char[] str,int row,int col, boolean[] visited,int[] pathLength) { - boolean flag = false; - - if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row * cols + col] && matrix[row * cols + col] == str[pathLength[0]]) { - pathLength[0]++; - visited[row * cols + col] = true; - if (pathLength[0] == str.length) { - return true; - } - flag = hasPathCore(matrix, rows, cols, str, row, col + 1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row + 1, col, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row, col - 1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row - 1, col, visited, pathLength); - if (!flag) { - pathLength[0]--; - visited[row * cols + col] = false; - } - } - return flag; - } -} -``` - -## 13、[机器人的运动范围](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 - -例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? - -### 解题思路 - -使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。而普通的深度优先搜索并不需要使用这些局部状态,虽然还是有可能设置一些全局状态。 - -```java -public class T66 { - public int movingCount(int threshold, int rows, int cols) { - boolean[][] visited = new boolean[rows][cols]; // 设置状态 - return countingStep(threshold,rows,cols,0,0,visited); - } - - public int countingStep(int limit, int rows, int cols, int r, int c, boolean[][] visited) { - if (r < 0 || r >= rows || - c < 0 || c >= cols || - visited[r][c] || - bitSum(r) + bitSum(c) > limit) return 0; - visited[r][c] = true; - return countingStep(limit,rows,cols,r - 1,c,visited) + countingStep(limit,rows,cols,r,c - 1,visited) + countingStep(limit,rows,cols,r+1,c,visited) + countingStep(limit,rows,cols,r,c+1,visited) + 1; - } - - public int bitSum(int t) { - int count = 0; - while (t != 0) { - count += t % 10; - t /= 10; - } - return count; - } -} - -``` - -## 14、[剪绳子](https://www.nowcoder.com/practice/57d85990ba5b440ab888fc72b0751bf8?tpId=13&tqId=33257&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -把一根绳子剪成多段,并且使得每段的长度乘积最大。 - -```html -n = 2 -return 1 (2 = 1 + 1) - -n = 10 -return 36 (10 = 3 + 3 + 4) - -``` - -### 解题思路 - -```java -// 动态规划 -public int integerBreak(int n) { - int[] dp = new int[n + 1]; - dp[1] = 1; - for (int i = 2; i <= n; i++) - for (int j = 1; j < i; j++) - dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j))); - return dp[n]; -} - -``` - -```java -public class T67 { - // 动态规划 - public int cutRope(int target) { - if (target < 2) return 0; - if (target == 2) return 1; - if (target == 3) return 2; - int[] products = new int[target + 1]; - products[0] = 0; - products[1] = 1; // 长度为2... - products[2] = 2; // 长度为3... - products[3] = 3; // 长度为4... - int max = 0; - for (int i = 4; i <= target; i++) { - max = 0; - for (int j = 1; j <= i / 2; j++) { - int product = products[j] * products[i - j]; - max = max > product ? max : product; - products[i] = max; - } - } - max = products[target]; - return max; - } -} -``` - - -## 15、[二进制1的个数](https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个整数,输出该数二进制表示中 1 的个数。 - -### 解题思路 - -(n-1) & n :该位运算去除 n 的位级表示中最低的那一位。 - -```java -public class T11 { - public int NumberOf1(int n) { - int count = 0; - while ( n!= 0) { - count++; - // (n-1) & n 注意这个。。 - n = (n - 1) & n; - } - return count; - } -} -``` - -## 16、[数值的整数次方](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent,求 base 的 exponent 次方。 - -### 解题思路 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/48b1d459-8832-4e92-938a-728aae730739.jpg) - -```java -public double Power(double base, int exponent) { - if (exponent == 0) - return 1; - if (exponent == 1) - return base; - boolean isNegative = false; - if (exponent < 0) { - exponent = -exponent; - isNegative = true; - } - double pow = Power(base * base, exponent / 2); - if (exponent % 2 != 0) - pow = pow * base; - return isNegative ? 1 / pow : pow; -} - -``` - -```java -public class T12 { - public double Power(double base, int exponent) { - // 还是先判断特殊情况,是0?还是>0,还是<0? - if (exponent == 0) return 1; - double ans = 1; - boolean flag = false; // 判断倒数 - // 如果小于0,取绝对值 - if (exponent < 0) { - flag = true; - exponent = -exponent; - } - for (int i = 1; i <= exponent; i++) { - ans *= base; - } - // 如果小于0,不仅取绝对值,还要最终求倒数 - if (flag) { - ans = 1 / ans; - } - return ans; - } -} -``` - - -## 18、[删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/17e301df-52e8-4886-b593-841a16d13e44.png) - -```java -public ListNode deleteDuplication(ListNode pHead) { - if (pHead == null || pHead.next == null) - return pHead; - ListNode next = pHead.next; - if (pHead.val == next.val) { - while (next != null && pHead.val == next.val) - next = next.next; - return deleteDuplication(next); - } else { - pHead.next = deleteDuplication(pHead.next); - return pHead; - } -} - -``` - -```java -public class T56 { - public ListNode deleteDuplication(ListNode pHead) { - // 只有0个或1个节点,则返回。 - if (null == pHead || pHead.next == null) { - return pHead; - } - // 当前节点是重复节点 - if (pHead.val == pHead.next.val) { - ListNode pNode = pHead.next; - while (pNode != null && pHead.val == pNode.val) { - pNode = pNode.next; // 是不是一直重复, so while - } - return deleteDuplication(pNode); // 递归继续 - } else { - // 当前节点不是重复节点 - // 保留当前节点,从下一个节点开始递归 - pHead.next = deleteDuplication(pHead.next); - return pHead; - } - } -} -``` - -## 19、[正则表达式匹配](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -请实现一个函数用来匹配包括 '.' 和 '*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '*' 表示它前面的字符可以出现任意次(包含 0 次)。 - -在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab*ac*a" 匹配,但是与 "aa.a" 和 "ab*a" 均不匹配。 - -```java -public class T52 { - public boolean match(char[] str, char[] pattern) { - if (str == null || pattern == null) { - return false; - } - int strIndex = 0; - int patternIndex = 0; - return matchCore(str, strIndex, pattern, patternIndex); - } - - public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) { - // 有效性校验:str到尾, pattern到尾,匹配成功 - if (strIndex == str.length && patternIndex == pattern.length) { - return true; - } - // pattern 先到尾,匹配失败 - if (strIndex != str.length && patternIndex == pattern.length) { - return false; - } - // 模式第二个是*,且字符串第一个根模式第一个匹配,分三种匹配模式; - // 如果不匹配,模式后移两 - if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') { - if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) { - return matchCore(str, strIndex, pattern, patternIndex + 2) || matchCore(str, strIndex + 1, pattern, patternIndex + 2) || matchCore(str, strIndex + 1, pattern, patternIndex); - } else { - return matchCore(str, strIndex, pattern, patternIndex + 2); - } - } - // 模式第二个不是*,且字符串第一个根模式第一个匹配,则都后移一位,否则直接返回false - if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])|| (pattern[patternIndex] == '.' && strIndex != str.length)) { - return matchCore(str, strIndex + 1, pattern, patternIndex + 1); - } - return false; - } -} - -``` - -## 20、[表示数值的字符串](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -```html -true - -"+100" -"5e2" -"-123" -"3.1416" -"-1E-16" -``` - -```html -false - -"12e" -"1a3.14" -"1.2.3" -"+-5" -"12e+4.3" -``` - -### 解题思路 - -```html -[] : 字符集合 -() : 分组 -? : 重复 0 ~ 1 次 -+ : 重复 1 ~ n 次 -* : 重复 0 ~ n 次 -. : 任意字符 -\\. : 转义后的 . -\\d : 数字 -``` - - -```java -public boolean isNumeric(char[] str) { - if (str == null || str.length == 0) - return false; - return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); -} - -``` - -```java -public class T53 { - public boolean isNumeric(char[] str) { - String s = String.valueOf(str); - // 正则大法好? - return s.matches("[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]+)?"); - } -} -``` - - -## 21、[调整数组顺序使奇数位于偶数前面](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -需要保证奇数和奇数,偶数和偶数之间的相对位置不变,这和书本不太一样。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/d03a2efa-ef19-4c96-97e8-ff61df8061d3.png) - -### 解题思路 - -方法一:创建一个新数组,时间复杂度 O(N),空间复杂度 O(N)。 - -```java -public void reOrderArray(int[] nums) { - // 奇数个数 - int oddCnt = 0; - for (int x : nums) - if (!isEven(x)) - oddCnt++; - int[] copy = nums.clone(); - int i = 0, j = oddCnt; - for (int num : copy) { - if (num % 2 == 1) - nums[i++] = num; - else - nums[j++] = num; - } -} - -private boolean isEven(int x) { - return x % 2 == 0; -} -``` - -方法二:冒泡 - -```java -public class T13 { - public void reOrderArray(int [] array) { - // 边界判断 - if (array == null || array.length == 0) return; - for (int i = 0; i < array.length; i++) { - // 循环n次 - for (int j = 0; j < array.length - 1 - i; j++) { - // 每次循环,找到当前元素为偶数,下一个元素为奇数,则交换 - if ((array[j] & 0x1) == 0 && (array[j + 1] & 0x1) == 1) { - swap(array, j, j + 1); - } - } - } - } - - /** - * 数据交换 - * @param arr - * @param x - * @param y - */ - private void swap(int[] arr, int x, int y) { - int temp = arr[x]; - arr[x] = arr[y]; - arr[y] = temp; - } -} -``` - -## 22、[链表中倒数第k个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 解题思路 - -设链表的长度为 N。设置两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到第 N - K 个节点处,该位置就是倒数第 K 个节点。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/6b504f1f-bf76-4aab-a146-a9c7a58c2029.png) - -```java -public ListNode FindKthToTail(ListNode head, int k) { - if (head == null) - return null; - ListNode P1 = head; - while (P1 != null && k-- > 0) - P1 = P1.next; - if (k > 0) - return null; - ListNode P2 = head; - while (P1 != null) { - P1 = P1.next; - P2 = P2.next; - } - return P2; -} -``` - - - -```java -public class T14 { - /** - * 栈 - * @param head - * @param k - * @return - */ - public ListNode FindKthToTail2(ListNode head,int k) { - // 边界判断 - if (head == null || k <= 0) return null; - Stack stack = new Stack<>(); - // 遍历将元素压栈 - while (head != null) { - stack.push(head); - head = head.next; - } - // 弹栈k次 - int temp = 0; - while (!stack.empty()) { - ListNode listNode = stack.pop(); - temp++; - if (temp == k) { - return listNode; - } - } - return null; - } -} -``` - -## 23、[链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -一个链表中包含环,请找出该链表的环的入口结点。要求不能使用额外的空间。 - -### 解题思路 - -使用双指针,一个快指针 fast 每次移动两个节点,一个慢指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/bb7fc182-98c2-4860-8ea3-630e27a5f29f.png) - -```java -public ListNode EntryNodeOfLoop(ListNode pHead) { - if (pHead == null || pHead.next == null) - return null; - ListNode slow = pHead, fast = pHead; - do { - fast = fast.next.next; - slow = slow.next; - } while (slow != fast); // 相遇点 - fast = pHead; - while (slow != fast) { // fast从头走,slow还从相遇点走 - slow = slow.next; - fast = fast.next; - } - return slow; -} -``` - - - -```java -public class T55 { - // 哈希 - public ListNode EntryNodeOfLoop(ListNode pHead) { - if (null == pHead) { - return null; - } - HashMap map = new HashMap<>(); - map.put(pHead, 1); - while (null != pHead.next) { - // 入口节点肯定会被map包含 - if (map.containsKey(pHead.next)) { - return pHead.next; - } - map.put(pHead.next, 1); - pHead = pHead.next; - } - return null; - } -} -``` - - - - -## 24、[反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public ListNode reverseList(ListNode head) { - ListNode pre = null; // 当前节点之前的节点 null - ListNode cur = head; - while (cur != null) { - ListNode nextTemp = cur.next; // 获取当前节点的下一个节点 - cur.next = pre; // 当前节点的下个节点指向前一个节点 - // 尾递归其实省了下面这两步 - pre = cur; // 将前一个节点指针移动到当前指针 - cur = nextTemp; // 当当前节点移动到下一个节点 - } - return pre; -} -``` - -```java -public class T15 { - public ListNode ReverseList(ListNode head) { - // 判断 - if (head == null) return null; - return reverse(null, head); - } - - /** - * 尾递归 - * @param pre - * @param cur - * @return - */ - private ListNode reverse(ListNode pre, ListNode cur) { - // 递归边界值判断 - if (cur == null) return pre; - // next节点指向cur.next - ListNode next = cur.next; - // cur.next 连接pre - cur.next = pre; - return reverse(cur, next); - } -} -``` - -## 25、[合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c094d2bc-ec75-444b-af77-d369dfb6b3b4.png) - -### 解题思路 - -```java -public class T16 { - public ListNode Merge(ListNode list1,ListNode list2) { - // 边界值判断 - // 如果list1为空,返回list2 - if (list1 == null) return list2; - // 如果list2为空,返回list1 - if (list2 == null) return list1; - // 如果list1.val < list2.val,则list1.next连接下一个比较值(递归比较) - if (list1.val < list2.val) { - list1.next = Merge(list1.next, list2); - return list1; - } else { - // 否则,list2.next 连接下一个比较值(递归比较) - list2.next = Merge(list1, list2.next); - return list2; - } - } -} -``` -## 26、[树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/84a5b15a-86c5-4d8e-9439-d9fd5a4699a1.jpg) - -### 解题思路 - -```java -public class T17 { - public boolean HasSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null || root2 == null) - return false; - return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); - } - - private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) { - if (root2 == null) - return true; - if (root1 == null) - return false; - if (root1.val != root2.val) - return false; - return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right); - } - -} -``` - -## 27、[二叉树的镜像](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0c12221f-729e-4c22-b0ba-0dfc909f8adf.jpg) - -### 解题思路 - -```java -public class T18 { - public void Mirror(TreeNode root) { - // 判断 - if (root == null) return; - swap(root); - Mirror(root.left); - Mirror(root.right); - - } - - private void swap(TreeNode root) { - TreeNode t = root.left; - root.left = root.right; - root.right = t; - } -} -``` - - - - -## 28、[对称的二叉树](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0c12221f-729e-4c22-b0ba-0dfc909f8adf.jpg) - -### 解题思路 - -```java -public class T58 { - boolean isSymmetrical(TreeNode pRoot) { - if (null == pRoot) { - return true; - } - return comRoot(pRoot.left, pRoot.right); - } - - private boolean comRoot(TreeNode left, TreeNode right) { - if (left == null && right == null) { - return true; - } - if (left == null || right == null) { - return false; - } - if (left.val != right.val) { - return false; - } - // 左右对比 - return comRoot(left.right, right.left) && comRoot(left.left, right.right); - } -} -``` - -## 29、[顺时针打印矩阵](https:/www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -下图的矩阵顺时针打印结果为:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/48517227-324c-4664-bd26-a2d2cffe2bfe.png) - -### 解题思路 - -```java -public class T19 { - public ArrayList printMatrix(int [][] matrix) { - ArrayList list = new ArrayList<>(); - int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; - while(r1 <= r2 && c1 <= c2) { - for (int i = c1; i <= c2; i++) { - list.add(matrix[r1][i]); - } - for (int i = r1 + 1; i <= r2; i++) { - list.add(matrix[i][c2]); - } - if (r1 != r2) { - for (int i = c2 - 1; i >= c1; i--) { - list.add(matrix[r2][i]); - } - } - if (c1 != c2) { - for (int i = r2 - 1; i >= r1; i--) { - list.add(matrix[i][c1]); - } - } - r1++; r2--; c1++; c2--; - } - return list; - } -} -``` - -## 30、[包含min函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数。 - -### 解题思路 - -```java -public class T20 { - - private Stack dataStack = new Stack<>(); - private Stack minStack = new Stack<>(); - - public void push(int node) { - dataStack.push(node);// dataStack添加元素 - minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node)); - } - - public void pop() { - dataStack.pop(); - // 辅助栈也得弹,因为每次push, 辅助栈也在push - minStack.pop(); - } - // 栈顶,没啥可说的 - public int top() { - return dataStack.peek(); - } - // 最小值,辅助栈弹就完事了 - public int min() { - return minStack.peek(); - } -} -``` - -## 31、[栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。 - -例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 - -### 解题思路 - -```java -public class T21 { - public boolean IsPopOrder(int [] pushA,int [] popA) { - if (pushA == null || popA == null) return false; - int p = 0; - Stack stack = new Stack<>(); - for (int i = 0; i < pushA.length; i++) { - // 遍历压栈 - stack.push(pushA[i]); - // 每压一次, 就要将栈顶的元素和弹出序列判断是否相等 - // 如果相等,栈顶元素弹出,p++,继续while, - while (!stack.isEmpty() && stack.peek() == popA[p]) { - stack.pop(); - p++; - } - } - // 如果最后栈为空了, 说明压入序列和弹出序列一致 - return stack.isEmpty(); - } -} -``` - -## 32.1、[从上往下打印二叉树](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -从上往下打印出二叉树的每个节点,同层节点从左至右打印。 - -例如,以下二叉树层次遍历的结果为:1,2,3,4,5,6,7 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/d5e838cf-d8a2-49af-90df-1b2a714ee676.jpg) - -### 解题思路 - -使用队列来进行层次遍历。 - -不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 - -```java -public class T22 { - // 层序遍历 - public ArrayList PrintFromTopToBottom(TreeNode root) { - ArrayList list = new ArrayList<>(); - // 需要用到队列 - LinkedList queue = new LinkedList<>(); - queue.offer(root); // 第一次先加根入队 - while (!queue.isEmpty()) { - int cnt = queue.size(); - // 如果队列不为空的话, 队列出一个元素 - while(cnt-- > 0) { - TreeNode t = queue.poll(); - if (t == null) continue; - list.add(t.val); - queue.add(t.left); - queue.add(t.right); - } - } - return list; - } -} -``` - -## 31.2、[把二叉树打印多行](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -和22题:从上往下打印二叉树 差不多 - -```java -public class T60 { - - ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - while (!queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (list.size() != 0) ret.add(list); - } - return ret; - } -} -``` - -## 32.3、[按之字形顺序打印二叉树](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 - -### 解题思路 - -```java -public class T59 { - public ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - boolean reverse = false; - while (! queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (reverse) Collections.reverse(list); - reverse = ! reverse; - if (list.size() != 0) ret.add(list); - } - reutrn ret; -} -``` - - - -## 33、[二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。 - -例如,下图是后序遍历序列 1,3,2 所对应的二叉搜索树 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/13454fa1-23a8-4578-9663-2b13a6af564a.jpg) - -### 解题思路 - -```java -public class T23 { - public boolean VerifySquenceOfBST(int [] sequence) { - if (sequence == null || sequence.length == 0) return false; - return isBST(sequence, 0, sequence.length - 1); - } - private boolean isBST(int[] sequence, int first, int last) { - if (last - first <= 1) { - return true; - } - int rootVal = sequence[last]; - int cutIndex = first; - while (cutIndex < last && sequence[curIndex] <= rootVal) { // 二叉搜索树特征 - cutIndex++; - } - for (int i = cutIndedx; i < last; i++) { - if (sequence[i] < rootVal) return false; - } - return isBST(sequence, first, cutIndex - 1) && isBST(sequence, cutIndex, last - 1); - } -} -``` - -## 34、[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 - -下图的二叉树有两条和为 22 的路径:10, 5, 7 和 10, 12 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ed77b0e6-38d9-4a34-844f-724f3ffa2c12.jpg) - -### 解题思路 - -```java -public class T24 { - - private ArrayList> ret = new ArrayList<>(); - - public ArrayList> FindPath(TreeNode root, int target) { - backtracking(root, target, new ArrayList<>()); - return ret; - } - - private void backtracking(TreeNode node, int target, ArrayList path) { - if (node == null) - return; - path.add(node.val); - target -= node.val; - if (target == 0 && node.left == null && node.right == null) { - ret.add(new ArrayList<>(path)); - } else { - backtracking(node.left, target, path); - backtracking(node.right, target, path); - } - path.remove(path.size() - 1); - } -} -``` - -## 35、[复杂链表的复制](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。 - -```html -public class RandomListNode { - int label; - RandomListNode next = null; - RandomListNode random = null; - - RandomListNode(int label) { - this.label = label; - } -} - -``` - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66a01953-5303-43b1-8646-0c77b825e980.png) - -### 解题思路 - -第一步,在每个节点的后面插入复制的节点。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/dfd5d3f8-673c-486b-8ecf-d2082107b67b.png) - -第二步,对复制节点的 random 链接进行赋值。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cafbfeb8-7dfe-4c0a-a3c9-750eeb824068.png) - -第三步,拆分。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e151b5df-5390-4365-b66e-b130cd253c12.png) - -```java -public class T25 { - public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) return null; - // 第一步:先复制一遍next - RandomListNode node = pHead; - while (node != null) { - RandomListNode copyNode = new RandomListNode(node.label); - copyNode.next = node.next; - node.next = copyNode; - node = copyNode.next; - } - // 第二步:再复制一遍random - node = pHead; - while (node != null) { - node.next.random = node.random == null ? null : node.random.next; - node = node.next.next; - } - // 第三步:切开 - node = pHead; - RandomListNode pCloneHead = pHead.next; - while (node != null) { - RandomListNode copyNode = node.next; - node.next = copyNode.next; - copyNode.next = copyNode.next == null ? null : copyNode.next.next; - node = node.next; - } - return pCloneHead; - } -} -``` - -## 36、[二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/05a08f2e-9914-4a77-92ef-aebeaecf4f66.jpg) - -### 解题思路 - -```java -public class T26 { - private TreeNode pre = null; - private TreeNode head = null; - - public TreeNode Convert(TreeNode root) { - inOrder(root); - return head; - } - - private void inOrder(TreeNode node) { - if (node == null) return; - inOrder(node.left); - node.left = pre; - if (pre != null) pre.right = node; - pre = node; - if (head == null) { - head = node; - } - inOrder(node.right); - } -} -``` - -## 36、[序列化二叉树](https://github.com/DreamCats/leetniu/blob/master/nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -public class T61 { - String Serialize(TreeNode root) { - if (null == root) { - return ""; - } - StringBuffer sb = new StringBuffer(); - Serialize2(root, sb); - return sb.toString(); - } - - void Serialize2(TreeNode root, StringBuffer sb) { - if (null == root) { - sb.append("#,"); - return; - } - sb.append(root.val); - sb.append(","); - Serialize2(root.left, sb); - Serialize2(root.right, sb); - } - int index = -1; - - TreeNode Deserialize(String str) { - if (str.length() == 0) { - return null; - } - String[] strings = str.split(","); - return Deserialize2(strings); - } - TreeNode Deserialize2(String[] strings) { - index++; - if (!strings[index].equals("#")) { - TreeNode root = new TreeNode(0); - root.val = Integer.parseInt(strings[index]); - root.left = Deserialize2(strings); - root.right = Deserialize2(strings); - return root; - } - return null; - } -} - -``` - -## 38、[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 - -```java -private ArrayList ret = new ArrayList<>(); - -public ArrayList Permutation(String str) { - if (str.length() == 0) - return ret; - char[] chars = str.toCharArray(); - Arrays.sort(chars); - backtracking(chars, new boolean[chars.length], new StringBuilder()); - return ret; -} - -private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) { - if (s.length() == chars.length) { - ret.add(s.toString()); - return; - } - for (int i = 0; i < chars.length; i++) { - if (hasUsed[i]) - continue; - if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) /* 保证不重复 */ - continue; - hasUsed[i] = true; - s.append(chars[i]); - backtracking(chars, hasUsed, s); - s.deleteCharAt(s.length() - 1); - hasUsed[i] = false; - } -} - -``` - -### 解题思路 - -```java -public class T27 { - public ArrayList Permutation(String str) { - ArrayList result = new ArrayList<>(); - if(str == null || str.length() == 0) { - return result; - } - char[] chars = str.toCharArray(); - TreeSet temp = new TreeSet<>(); - Permutation(chars, 0, temp); - result.addAll(temp); - return result; - } - - private void Permutation(char[] chars, int begin, TreeSet result) { - if (chars == null || chars.length == 0 || begin < 0 || begin > chars.length - 1) { - return; - } - if (begin == chars.length - 1) { - result.add(String.valueOf(chars)); - } else { - for (int i = begin; i <= chars.length - 1; i++) { - swap(chars, begin, i); - Permutation(chars, begin + 1, result); - swap(chars, begin, i); - } - } - } - private void swap(char[] x, int a, int b) { - char t = x[a]; - x[a] = x[b]; - x[b] = t; - } -} -``` - -## 39、[数组中出现次数超过一半的数字](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 解题思路 - -```java -public class T28 { - public int MoreThanHalfNum_Solution(int [] array) { - // 哈希的方法 - HashMap map = new HashMap<>(); - // 遍历一次每个元素的个数 - for (int i = 0; i < array.length; i++) { - if (map.containsKey(array[i])) { - map.put(array[i], map.get(array[i]) + 1); - } else { - map.put(array[i], 1); - } - } - int length = array.length >> 1; - // 查找哪个数的次数超过一半 - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() > length) { - return entry.getKey(); - } - } - return 0; - } -} -``` - -## 40、[最小的k个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 解题思路 - -```java -public class T29 { - public ArrayList GetLeastNumbers_Solution(int [] input, int k) { - ArrayList integers = new ArrayList<>(); - // 边界条件 - if (k > input.length){ - return integers; - } - // 先排序。。。 这里直接用Arrays.sort排序 - for (int i = 1; i < input.length; i++) { - for (int j = 0; j < input.length - i; j++) { - if (input[j] > input[j + 1]) { - swap(input, j, j + 1); - } - } - } - // 然后取k个数 - for (int i = 0; i < k; i++) { - integers.add(input[i]); - } - return integers; - } - private void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} -``` - -## 41.1、[数据流中的中位数](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 - -### 解题思路 - -```java -/* 大顶堆,存储左半边元素 */ -private PriorityQueue left = new PriorityQueue<>((o1, o2) -> o2 - o1); -/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */ -private PriorityQueue right = new PriorityQueue<>(); -/* 当前数据流读入的元素个数 */ -private int N = 0; - -public void Insert(Integer val) { - /* 插入要保证两个堆存于平衡状态 */ - if (N % 2 == 0) { - /* N 为偶数的情况下插入到右半边。 - * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大, - * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 */ - left.add(val); - right.add(left.poll()); - } else { - right.add(val); - left.add(right.poll()); - } - N++; -} - -public Double GetMedian() { - if (N % 2 == 0) - return (left.peek() + right.peek()) / 2.0; - else - return (double) right.peek(); -} - -``` - - - -```java -public class T63 { - LinkedList list = new LinkedList<>(); - public void Insert(Integer num) { - if (list.size() == 0 || num < list.getFirst()) { - list.addFirst(num); - } else { - boolean insertFlag = false; - for(Integer e : list) { - if (num < e) { - int index = list.indexOf(e); - list.add(index, num); - insertFlag = true; - break; - } - } - if (!insertFlag) { - list.addLast(num); - } - } - } - - public Double GetMedian() { - if (list.size() == 0) { - return null; - } - if(list.size() % 2 == 0) { - int i = list.size() / 2; - Double a = Double.valueOf(list.get(i - 1) + list.get(i)); - return a / 2; - } - list.get(0); - return Double.valueOf(list.get(list.size() / 2)); - } -} -``` -## 41.2、[字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 - -### 解题思路 - -```java -private int[] cnts = new int[256]; -private Queue queue = new LinkedList<>(); - -public void Insert(char ch) { - cnts[ch]++; - queue.add(ch); - while (!queue.isEmpty() && cnts[queue.peek()] > 1) - queue.poll(); -} - -public char FirstAppearingOnce() { - return queue.isEmpty() ? '#' : queue.peek(); -} - -``` - - - -```java -public class T54 { - - int count[] = new int[256]; - int index = 1; - - public void Insert(char ch) - { - if (count[ch] == 0) { - count[ch] = index++; - } else { - count[ch] = -1; - } - } - - public char FirstAppearingOnce() - { - int temp = Integer.MAX_VALUE; - char ch = '#'; - for (int i = 0; i < count.length; i++) { - if (count[i] != 0 && count[i] != -1 && count[i] < temp) { - temp = count[i]; - ch = (char)i; - } - } - return ch; - } -} -``` - - - - -## 42、[连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。 - -### 解题思路 - -```java -public int FindGreatestSumOfSubArray(int[] nums) { - if (nums == null || nums.length == 0) - return 0; - int greatestSum = Integer.MIN_VALUE; - int sum = 0; - for (int val : nums) { - sum = sum <= 0 ? val : sum + val; - greatestSum = Math.max(greatestSum, sum); - } - return greatestSum; -} - -``` - -```java -public class T30 { - public int FindGreatestSumOfSubArray(int[] array) { - // 动态规划完事 - if (array == null || array.length == 0) return 0; - int res = array[0]; // 记录当前所有子数组的和的最大值 - int max = array[0]; // 记录包含arr[i]的连续子数组的最大值 - for (int i = 1; i < array.length; i++) { - max = Math.max(max + array[i], array[i]); // 动态规划公式 - res = Math.max(max, res); - } - return res; - } -} -``` -## 43、[整数中1出现的次数](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 解题思路 - -```java -public class T31 { - public int NumberOf1Between1AndN_Solution(int n) { - int count = 0; - // 1~n中 遍历呗 - for (int i = 1; i <= n; i++) { - int num = i; - while(num != 0) { - // num%10:其实就是个数 是否为1 是的话count++ - if (num % 10 == 1) { - count++; - } - // num = num / 10 - num /= 10; - } - } - return count; - } -} -``` - - -## 45、[把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。 - -### 解题思路 - -可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。 - -```java -public String PrintMinNumber(int[] numbers) { - if (numbers == null || numbers.length == 0) - return ""; - int n = numbers.length; - String[] nums = new String[n]; - for (int i = 0; i < n; i++) - nums[i] = numbers[i] + ""; - Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1)); - String ret = ""; - for (String str : nums) - ret += str; - return ret; -} - -``` - - - -```java -public class T32 { - public String PrintMinNumber(int [] numbers) { - if (numbers == null || numbers.length == 0) return ""; - int len = numbers.length; - String[] str = new String[len]; - StringBuffer sb = new StringBuffer(); - // 遍历numbers转成字符串数组 - for (int i = 0; i < len; i++) { - str[i] = String.valueOf(numbers[i]); - } - // 然后排序,重写Comparator - Arrays.sort(str, new Comparator() { - @Override - public int compare(String o1, String o2) { - String c1 = o1 + o2; - String c2 = o2 + o1; - return c1.compareTo(c2); - } - }); - for (int i = 0; i < len; i++) { - sb.append(str[i]); - } - return sb.toString(); - } -} -``` - -## 46、[礼物的最大值](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab) - -### 题目描述 - -在一个 m*n 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘 - -```html -1 10 3 8 -12 2 9 6 -5 7 4 11 -3 7 16 5 -``` - -礼物的最大价值为 1+12+5+7+7+16+5=53。 - -### 解题思路 - -应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。 - -```java -public int getMost(int[][] values) { - if (values == null || values.length == 0 || values[0].length == 0) - return 0; - int n = values[0].length; - int[] dp = new int[n]; - for (int[] value : values) { - dp[0] += value[0]; - for (int i = 1; i < n; i++) - dp[i] = Math.max(dp[i], dp[i - 1]) + value[i]; - } - return dp[n - 1]; -} -``` - -## 48、[最长不含重复字符的子字符串](#) - -### 题目描述 - -输入一个字符串(只包含 a~z 的字符),求其最长不含重复字符的子字符串的长度。例如对于 arabcacfr,最长不含重复字符的子字符串为 acfr,长度为 4。 - -### 解题思路 - -```java -public class LongestSubstring { - public int lengthOfLongestSubstring(String s) { - int n = s.length(), ans = 0; - HashMap map = new HashMap<>(); - // abcabc - for (int i = 0, j = 0; j < n; j++) { - if (map.containsKey(s.charAt(j))) { - i = Math.max(map.get(s.charAt(j)), i); // 求重复的字符串的索引,为了ans - } - ans = Math.max(ans, j - i + 1); // 求长度 - map.put(s.charAt(j), j + 1); // 也会同样覆盖重复的索引 - } - return ans; - } -} - -``` - - -## 49、[丑数](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 - -### 解题思路 - - - -```java -public class T33 { - public int GetUglyNumber_Solution(int index) { - if (index <= 0) return 0; - int[] ans = new int[index]; - int count = 0; - int i2 = 0, i3 = 0, i5 = 0; - ans[0] = 1; - int temp = 0; - while (count < index - 1) { - // 先求i3 * 3 和 i5 * 5 的最小值,然后再求i2 * 2的最小值 - temp = min(ans[i2] * 2, min(ans[i3] * 3, ans[i5] * 5)); - if (temp == ans[i2] * 2) i2++; - if (temp == ans[i3] * 3) i3++; - if (temp == ans[i5] * 5) i5++; - ans[++count] = temp; - } - return ans[index - 1]; - } - private int min(int a, int b) { - return (a > b) ? b : a; - } -} -``` - -## 50、[第一个只出现一次的字符](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -在一个字符串中找到第一个只出现一次的字符,并返回它的位置。 - -```html -Input: abacc -Output: b - -``` - -### 解题思路 - -最直观的解法是使用 HashMap 对出现次数进行统计,但是考虑到要统计的字符范围有限,因此可以使用整型数组代替 HashMap,从而将空间复杂度由 O(N) 降低为 O(1)。 - -```java -public int FirstNotRepeatingChar(String str) { - int[] cnts = new int[256]; - for (int i = 0; i < str.length(); i++) - cnts[str.charAt(i)]++; - for (int i = 0; i < str.length(); i++) - if (cnts[str.charAt(i)] == 1) - return i; - return -1; -} - -``` - - - -```java -public class T34 { - // 哈希方法 - public int FirstNotRepeatingChar(String str) { - if (str == null || str.length() == 0) return -1; - HashMap map = new HashMap<>(); - // 遍历计数 - for (int i = 0; i < str.length(); i++) { - if (map.containsKey(str.charAt(i))) { - map.put(str.charAt(i), map.get(str.charAt(i) + 1)); - } else { - map.put(str.charAt(i), 1); - } - } - // 如果等于1则返回 - for (int i = 0; i < str.length(); i++) { - if (map.get(str.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} -``` -## 51、[数组中的逆序对](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 - -```java -public class T35 { - - private Integer count = 0; - - public int InversePairs(int [] array) { - if (array == null || array.length == 0) return 0; - mergeSort(array, 0, array.length - 1); - return (count % 1000000007); - } - // 归并排序 - private void mergeSort(int[] array, int left, int right) { - if (left < right) { - int mid = (left + right) >> 1; - mergeSort(array, left ,mid); - mergeSort(array, mid + 1, right); - merge(array, left, mid, right); - } - - } - - private void merge(int[] array, int left, int mid, int right) { - int[] help = new int[right - left + 1]; - int i = 0; - int p1 = left; - int p2 = mid + 1; - while(p1 <= mid && p2 <= right) { - if(array[p1] > array[p2]) { - help[i++] = array[p2++]; - count += mid - p1 + 1; - } else { - help[i++] = array[p1++]; - } - } - - while(p1 <= mid) { - help[i++] = array[p1++]; - } - - while(p2 <= right) { - help[i++] = array[p2++]; - } - - for(int j = 0; j < help.length; j++) { - array[left + j] = help[j]; - } - } -} -``` - -## 52、[两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5f1cb999-cb9a-4f6c-a0af-d90377295ab8.png) - -### 解题思路 - -设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 - -当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 - -```java -public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - ListNode l1 = pHead1, l2 = pHead2; - while (l1 != l2) { - l1 = (l1 == null) ? pHead2 : l1.next; - l2 = (l2 == null) ? pHead1 : l2.next; - } - return l1; -} -``` - - - -```java -public class T36 { - - public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - // 哈希方法 - // 边界判断 - if (pHead1 == null || pHead2 == null) return null; - ListNode cur1 = pHead1; - ListNode cur2 = pHead2; - HashMap map = new HashMap<>(); - // 遍历第一个链表 - while (cur1 != null) { - map.put(cur1, 1); - cur1 = cur1.next; - } - // 遍历判断map查询第二个链表的节点 - while (cur2 != null) { - if (map.containsKey(cur2)) { - return cur2; - } - cur2 = cur2.next; - } - return null; - } -} -``` -## 53、[数字在排序数组中出现的次数](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -```html -Input: -nums = 1, 2, 3, 3, 3, 3, 4, 6 -K = 3 - -Output: -4 -``` - -### 解题思路 - -```java -public int GetNumberOfK(int[] nums, int K) { - int first = binarySearch(nums, K); - int last = binarySearch(nums, K + 1); - return (first == nums.length || nums[first] != K) ? 0 : last - first; -} - -private int binarySearch(int[] nums, int K) { - int l = 0, h = nums.length; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] >= K) - h = m; - else - l = m + 1; - } - return l; -} -``` - - - -```java -public class T37 { - public int GetNumberOfK(int [] array , int k) { - int count = 0; - // 遍历数组 - for (int i : array) { - if (i == k) count++; - } - return count; - } -} -``` - -## 54、[二叉搜索树的第k个结点](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -```java -private TreeNode ret; -private int cnt = 0; - -public TreeNode KthNode(TreeNode pRoot, int k) { - inOrder(pRoot, k); - return ret; -} - -private void inOrder(TreeNode root, int k) { - if (root == null || cnt >= k) - return; - inOrder(root.left, k); - cnt++; - if (cnt == k) - ret = root; - inOrder(root.right, k); -} - -``` - -```java -public class T62 { - int index = 0; - TreeNode KthNode(TreeNode pRoot, int k) { - if (null != pRoot) { - TreeNode node = KthNode(pRoot.left, k); - if (null != node) { - return node; - } - index++; - if (index == k) { - return pRoot; - } - node = KthNode(pRoot.right, k); - if (null != node) { - return node; - } - } - return null; - } -} -``` -## 55.1、[二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 解题思路 - -```java -public class T38 { - public int TreeDepth(TreeNode root) { - return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); - } -} -``` - -## 55.2、[平衡二叉树](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -平衡二叉树左右子树高度差不超过 1。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/af1d1166-63af-47b6-9aa3-2bf2bd37bd03.jpg) - -```java -private boolean isBalanced = true; - -public boolean IsBalanced_Solution(TreeNode root) { - height(root); - return isBalanced; -} - -private int height(TreeNode root) { - if (root == null || !isBalanced) - return 0; - int left = height(root.left); - int right = height(root.right); - if (Math.abs(left - right) > 1) - isBalanced = false; - return 1 + Math.max(left, right); -} -``` - -```java -public class T39 { - public boolean IsBalanced_Solution(TreeNode root) { - if (root == null) { - return true; - } - // 平衡二叉树的条件 - return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 && IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right); - } - // 最大深度 - private int maxDepth(TreeNode root) { - if(root == null) return 0; - return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); - } -} -``` - -## 56、[数组中只出现一次的数字](https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&tqId=11193&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 - -### 解题思路 - -两个不相等的元素在位级表示上必定会有一位存在不同,将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 - -diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 - -```java -public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) { - int diff = 0; - for (int num : nums) - diff ^= num; - diff &= -diff; - for (int num : nums) { - if ((num & diff) == 0) - num1[0] ^= num; - else - num2[0] ^= num; - } -} -``` - - - -```java -public class T40 { - public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { - if (array == null || array.length <= 1) { - num1[0] = num2[0] = 0; - return; - } - HashMap map = new HashMap<>(); - // 哈希计数 - for (int i = 0; i < array.length; i++) { - if (map.containsKey(array[i])) { - map.put(array[i], 2); - } else { - map.put(array[i], 1); - } - } - StringBuffer sb = new StringBuffer(); - // Sb存只出现一次的数字 - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 1) { - sb.append(entry.getKey()); - sb.append(","); - } - } - String[] strings = sb.toString().split(","); - num1[0] = Integer.valueOf(strings[0]); - num2[0] = Integer.valueOf(strings[1]); - } -} -``` - -## 57.1、[和为S的连续正数序列](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输出所有和为 S 的连续正数序列。 - -例如和为 100 的连续序列有: - -```html -[9, 10, 11, 12, 13, 14, 15, 16] -[18, 19, 20, 21, 22]。 -``` - -### 解题思路 - -```java -public class T41 { - public ArrayList > FindContinuousSequence(int sum) { - ArrayList> arrayLists = new ArrayList<>(); - int phigh = 2; - int plow = 1; - // 双指针 快慢指针 - while(phigh > plow) { - // - int cur = (phigh + plow) * (phigh - plow + 1) / 2; // 特殊的计算方法 - if (cur < sum) { - phigh++; - } - if (cur > sum) { - plow++; - } - if (cur == sum) { - ArrayList arrayList = new ArrayList<>(); - for (int i = plow; i <= phigh; i++) { - arrayList.add(i); - } - arrayLists.add(arrayList); - plow++; - } - } - return arrayLists; - } -} -``` - -## 57.2、[和为S的两个数字](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -输入一个递增排序的数组和一个数字 S,在数组中查找两个数,使得他们的和正好是 S。如果有多对数字的和等于 S,输出两个数的乘积最小的。 - -### 解题思路 - -使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 - -- 如果两个指针指向元素的和 sum == target,那么得到要求的结果; -- 如果 sum > target,移动较大的元素,使 sum 变小一些; -- 如果 sum < target,移动较小的元素,使 sum 变大一些。 - -```java -public ArrayList FindNumbersWithSum(int[] array, int sum) { - int i = 0, j = array.length - 1; - while (i < j) { - int cur = array[i] + array[j]; - if (cur == sum) - return new ArrayList<>(Arrays.asList(array[i], array[j])); - if (cur < sum) - i++; - else - j--; - } - return new ArrayList<>(); -} -``` - - - -```java -public class T42 { - - public ArrayList FindNumbersWithSum(int [] array, int sum) { - int start = 0, end = array.length - 1; - // list存两数字 - ArrayList list = new ArrayList<>(); - // 类似于二分 - while (start < end) { - int count = array[start] + array[end]; - if (count < sum) { - start++; - } - if (count == sum) { - list.add(array[start]); - list.add(array[end]); - return list; - } - if (count > sum) { - end--; - } - } - return list; - } -} -``` - -## 58.1、[左旋转字符串](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -```html -Input: -S="abcXYZdef" -K=3 - -Output: -"XYZdefabc" -``` - -### 解题思路 - -```java -public String LeftRotateString(String str, int n) { - if (n >= str.length()) - return str; - char[] chars = str.toCharArray(); - reverse(chars, 0, n - 1); - reverse(chars, n, chars.length - 1); - reverse(chars, 0, chars.length - 1); - return new String(chars); -} - -private void reverse(char[] chars, int i, int j) { - while (i < j) - swap(chars, i++, j--); -} - -private void swap(char[] chars, int i, int j) { - char t = chars[i]; - chars[i] = chars[j]; - chars[j] = t; -} - -``` - -```java -public class T43 { - public String LeftRotateString(String str,int n) { - if (str.length() == 0) return str; - // 用str的substring的api - for (int i = 0; i < n; i++) { - char c = str.charAt(0); - str = str.substring(1).concat(String.valueOf(c)); - } - return str; - } -} -``` - -## 58.2、[翻转单词顺序列](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -```html -Input: -"I am a student." - -Output: -"student. a am I" -``` - -### 解题思路 - -```java -public class T44 { - - public String ReverseSentence(String str) { - if (str == null) return null; - // 边界判断 - if (str.trim().equals("")) return str; - // 切割 - String[] strings = str.split(" "); - StringBuffer sb = new StringBuffer(); - // 遍历 - for (int i = strings.length - 1; i >= 0; i--) { - sb.append(strings[i]).append(" "); - } - return sb.substring(0, sb.length() - 1); - } -} -``` - -## 59、[滑动窗口的最大值](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&&tqId=11217&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - -### 题目描述 - -给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。 - -例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}。 - -### 解题思路 - -可用大顶堆 - -```java -public ArrayList maxInWindows(int[] num, int size) { - ArrayList ret = new ArrayList<>(); - if (size > num.length || size < 1) - return ret; - PriorityQueue heap = new PriorityQueue<>((o1, o2) -> o2 - o1); /* 大顶堆 */ - for (int i = 0; i < size; i++) - heap.add(num[i]); - ret.add(heap.peek()); - for (int i = 0, j = i + size; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */ - heap.remove(num[i]); - heap.add(num[j]); - ret.add(heap.peek()); - } - return ret; -} -``` - - - -```java -public class T64 { - public ArrayList maxInWindows(int [] num, int size) { - if (null == num || size < 0) { - return null; - } - ArrayList list = new ArrayList<>(); - if(size == 0) { - return list; - } - int length = num.length; - ArrayList temp = null; - if (length < size) { - return list; - } else { - // 滑length-size+1次 - for (int i = 0; i < length - size + 1; i++) { - temp = new ArrayList<>(); - // 滑动窗口 - for (int j = i; j < size + i; j++) { - temp.add(num[j]); - } - // 排序 - Collections.sort(temp); - // 排序过后取最大值 并添加 - list.add(temp.get(temp.size() - 1)); - } - } - return list; - } -} -``` - - -## 61、[扑克牌顺序](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -五张牌,其中大小鬼为癞子,牌面为 0。判断这五张牌是否能组成顺子。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/eaa506b6-0747-4bee-81f8-3cda795d8154.png) - -### 解题思路 - -```java -public class T45 { - public boolean isContinuous(int [] numbers) { - int numOfZero = 0; - int numOfInterval = 0; - int length = numbers.length; - if (length == 0) return false; - // 排序 - Arrays.sort(numbers); - for (int i = 0; i < length - 1; i++) { - // 计算癞子数量 也就是计算0的数量 - if (numbers[i] == 0) { - numOfZero++; - continue; - } - // 对子直接返回(特殊情况) - if (numbers[i] == numbers[i + 1]) return false; - - numOfInterval += numbers[i + 1] - numbers[i] - 1; - } - if (numOfZero >= numOfInterval) return true; - return false; - } -} -``` - -## 62、[孩子们的游戏](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -让小朋友们围成一个大圈。然后,随机指定一个数 m,让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。 - -### 解题思路 - -约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。 - -```java -public int LastRemaining_Solution(int n, int m) { - if (n == 0) /* 特殊输入的处理 */ - return -1; - if (n == 1) /* 递归返回条件 */ - return 0; - return (LastRemaining_Solution(n - 1, m) + m) % n; -} -``` - - - -```java -public class T46 { - public int LastRemaining_Solution(int n, int m) { - // 边界判断 - if (n == 0 || m == 0) { - return -1; - } - ArrayList data = new ArrayList<>(); - // 遍历一次0~n - for (int i = 0; i < n; i++) { - data.add(i); - } - int index = -1; - // 循环 index + m 和 余 data的数量 - while (data.size() > 1) { - index = (index + m) % data.size(); - // 移除index - data.remove(index); - // index - 1 - index--; - } - return data.get(0); - } -} -``` - -## 63、[股票的最大利润](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/description/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china) - -### 题目描述 - -可以有一次买入和一次卖出,买入必须在前。求最大收益。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/42661013-750f-420b-b3c1-437e9a11fb65.png) - -### 解题思路 - -```java -class Solution { - public int maxProfit(int[] prices) { - if(prices.length <= 1) return 0; - // int min = prices[0], max = 0; - // for(int i = 1; i < prices.length; i++){ - // max = Math.max(max, prices[i] - min); - // min = Math.min(min, prices[i]); - // } - // return max; - int dp[] = new int [prices.length]; - dp[0] = prices[0]; - int max = 0; - for (int i = 1; i < prices.length; i++) { - max = Math.max(max, prices[i] - dp[i - 1]); - dp[i] = Math.min(dp[i - 1], prices[i]); - } - return max; - } -} -``` - -## 64、[求1+2+3+...+n](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句 A ? B : C。 - -### 解题思路 - -使用递归解法最重要的是指定返回条件,但是本题无法直接使用 if 语句来指定返回条件。 - -条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。 - -本题的递归返回条件为 n <= 0,取非后就是 n > 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。 - -```java -public class T47 { - public int Sum_Solution(int n) { - int res = n; - boolean t = ((res != 0) && ((res += Sum_Solution(n - 1)) != 0)); - return res; - } -} -``` - -## 65、[不用加减乘除做加法](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -写一个函数,求两个整数之和,要求不得使用 +、-、*、/ 四则运算符号。 - -### 解题思路 - -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。 - -递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 - -```java -public int Add(int a, int b) { - return b == 0 ? a : Add(a ^ b, (a & b) << 1); -} -``` - - - -```java -public class T48 { - public int Add(int num1,int num2) { - while (num2 != 0) { - int temp = num1 ^ num2; // 没有进位的相加 - num2 = (num1 & num2) << 1; // 进位 - num1 = temp; // - } - return num1; - } -} -``` - -## 66、[构建乘积数组](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -给定一个数组 A[0, 1,..., n-1],请构建一个数组 B[0, 1,..., n-1],其中 B 中的元素 B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。要求不能使用除法。 - -![](https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/4240a69f-4d51-4d16-b797-2dfe110f30bd.png) - -### 解题思路 - -```java -public class T51 { - public int[] multiply(int[] A) { - int length = A.length; - int[] B = new int[length]; - if (length != 0) { - B[0] = 1; - // 计算下三角连乘 - for (int i = 1; i < length; i++) { - B[i] = B[i - 1] * A[i - 1]; - } - int temp = 1; - // 计算上三角 - for (int j = length - 2; j >= 0; j--) { - temp *= A[j + 1]; - B[j] *= temp; - } - } - return B; - } -} -``` - - -## 67、[把字符串转成整数](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - -### 题目描述 - -将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0 - -### 解题思路 - -```java -public class T49 { - public int StrToInt(String str) { - if (str == null || str.length() == 0) - return 0; - boolean isNegative = str.charAt(0) == '-'; - int ret = 0; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (i == 0 && (c == '+' || c == '-')) /* 符号判定 */ - continue; - if (c < '0' || c > '9') /* 非法输入 */ - return 0; - ret = ret * 10 + (c - '0'); - } - return isNegative ? -ret : ret; - } -} -``` - - - - diff --git "a/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" "b/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" deleted file mode 100644 index 8f8c454b..00000000 --- "a/Interview/myself/\345\244\232\347\272\277\347\250\213\347\274\226\347\250\213\351\242\230.md" +++ /dev/null @@ -1,416 +0,0 @@ -#### 一个多线程的问题,用三个线程,顺序打印字母A-Z,输出结果是1A 2B 3C 1D 2E…打印完毕最后输出一个Ok。 - -```java -public class Test { - private static char c = 'A'; - private static int i = 0; - - public static void main(String[] args) { - Runnable r = new Runnable() { - @Override - public void run() { - synchronized (this) { - try { - int id = Integer.parseInt(Thread.currentThread().getName()); - while (i < 26) { - if (i % 3 == id - 1) { - System.out.println("线程id:" + id + " " + (char) c++); - i++; - notifyAll(); - } else { - wait(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - }; - new Thread(r, "1").start(); - new Thread(r, "2").start(); - new Thread(r, "3").start(); - } -} - -``` - -#### 每个线程把自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示 - -LockSupport - -```java -public class Main { - static Thread threadA, threadB, threadC; - public static void main(String[] args) { - threadA = new Thread(() -> { - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - LockSupport.unpark(threadB); - LockSupport.park(); - } - }, "A"); - - threadB = new Thread(() -> { - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - LockSupport.unpark(threadC); - LockSupport.park(); - } - }, "B"); - - threadC = new Thread(() -> { - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - LockSupport.unpark(threadA); - LockSupport.park(); - } - }, "C"); - - threadA.start(); - threadB.start(); - threadC.start(); - } -} -``` - -ReentrantLock - -```java -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; - -public class Main { - public static void main(String[] args) { - ReentrantLock lock = new ReentrantLock(); - Condition conA = lock.newCondition(); - Condition conB = lock.newCondition(); - Condition conC = lock.newCondition(); - - // A - new Thread(() -> { - try { - lock.lock(); - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - conB.signal(); - conA.await(); - } - conB.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "A").start(); - - // B - new Thread(() -> { - try { - lock.lock(); - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - conC.signal(); - conB.await(); - } - conC.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "B").start(); - - // C - new Thread(() -> { - try { - lock.lock(); - for (int i = 0; i < 10; i++) { - System.out.print(Thread.currentThread().getName()); - conA.signal(); - conC.await(); - } - conA.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "C").start(); - } -} -``` - -#### 两线程奇偶数打印 - -```java -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; - -public class Main { - static int value = 0; - public static void main(String[] args) { - ReentrantLock lock = new ReentrantLock(); - Condition conA = lock.newCondition(); - Condition conB = lock.newCondition(); - // A - new Thread(() -> { - try { - lock.lock(); - while (value <= 100) { - System.out.println(Thread.currentThread().getName() + " " + value++); - conB.signal(); - conA.await(); - } - conB.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "A").start(); - - // B - new Thread(() -> { - try { - lock.lock(); - while (value <= 100) { - System.out.println(Thread.currentThread().getName() + " " + value++); - conA.signal(); - conB.await(); - } - conA.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "B").start(); - - } -} -``` - -#### 启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10。直到75 - -```java -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; - -public class Main { - static int value = 1; - public static void main(String[] args) { - ReentrantLock lock = new ReentrantLock(); - Condition conA = lock.newCondition(); - Condition conB = lock.newCondition(); - // A - new Thread(() -> { - try { - lock.lock(); - while (value <= 75) { - System.out.print(Thread.currentThread().getName() + " "); - for (int i = 0; i < 5; i++) { - System.out.print(value++ + " "); - } - System.out.println(""); - conB.signal(); - conA.await(); - } - conB.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "A").start(); - - // B - new Thread(() -> { - try { - lock.lock(); - while (value <= 75) { - System.out.print(Thread.currentThread().getName() + " "); - for (int i = 0; i < 5; i++) { - System.out.print(value++ + " "); - } - System.out.println(""); - conA.signal(); - conB.await(); - } - conA.signal(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - }, "B").start(); - - } -} -``` - -#### 如何确保三个线程顺序执行? - -join - -```java -public class ThreadTest1 { -// T1、T2、T3三个线程顺序执行 -public static void main(String[] args) { -    Thread t1 = new Thread(new Work(null)); -    Thread t2 = new Thread(new Work(t1)); -    Thread t3 = new Thread(new Work(t2)); -    t1.start(); -    t2.start(); -    t3.start(); - -} -static class Work implements Runnable { -    private Thread beforeThread; -    public Work(Thread beforeThread) { -        this.beforeThread = beforeThread; -    } -    public void run() { -        if (beforeThread != null) { -            try { -                beforeThread.join(); -                System.out.println("thread start:" + Thread.currentThread().getName()); -            } catch (InterruptedException e) { -                e.printStackTrace(); -            } -        } else { -            System.out.println("thread start:" + Thread.currentThread().getName()); -        } -    } - } -} -``` - -CountDownLatch - -```java -public class ThreadTest2 { - -// T1、T2、T3三个线程顺序执行 -public static void main(String[] args) { -    CountDownLatch c0 = new CountDownLatch(0); //计数器为0 -    CountDownLatch c1 = new CountDownLatch(1); //计数器为1 -    CountDownLatch c2 = new CountDownLatch(1); //计数器为1 - -    Thread t1 = new Thread(new Work(c0, c1)); -    //c0为0,t1可以执行。t1的计数器减1 - -    Thread t2 = new Thread(new Work(c1, c2)); -    //t1的计数器为0时,t2才能执行。t2的计数器c2减1 - -    Thread t3 = new Thread(new Work(c2, c2)); -    //t2的计数器c2为0时,t3才能执行 - -    t1.start(); -    t2.start(); -    t3.start(); - -} - -//定义Work线程类,需要传入开始和结束的CountDownLatch参数 -static class Work implements Runnable { -    CountDownLatch c1; -    CountDownLatch c2; - -    Work(CountDownLatch c1, CountDownLatch c2) { -        super(); -        this.c1 = c1; -        this.c2 = c2; -    } - -    public void run() { -        try { -            c1.await();//前一线程为0才可以执行 -            System.out.println("thread start:" + Thread.currentThread().getName()); -            c2.countDown();//本线程计数器减少 -        } catch (InterruptedException e) { -        } - -    } - } -} -``` - -blockingQueue - -```java -public class ThreadTest4 { -// T1、T2、T3三个线程顺序执行 -public static void main(String[] args) { -    //blockingQueue保证顺序 -    BlockingQueue blockingQueue = new LinkedBlockingQueue(); -    Thread t1 = new Thread(new Work()); -    Thread t2 = new Thread(new Work()); -    Thread t3 = new Thread(new Work()); - -    blockingQueue.add(t1); -    blockingQueue.add(t2); -    blockingQueue.add(t3); - -    for (int i=0;i<3;i++) { -        Thread t = null; -        try { -            t = blockingQueue.take(); -        } catch (InterruptedException e) { -            e.printStackTrace(); -        } -        t.start(); -        //检测线程是否还活着 -        while (t.isAlive()); -    } -} - -static class Work implements Runnable { - -    public void run() { -        System.out.println("thread start:" + Thread.currentThread().getName()); -    } - } -} -``` - -CachedThreadPool - -```java - public class ThreadTest3 { -    // T1、T2、T3三个线程顺序执行 -   public static void main(String[] args) { -    FutureTask future1= new FutureTask(new Work(null)); -    Thread t1 = new Thread(future1); - -    FutureTask future2= new FutureTask(new Work(future1)); -    Thread t2 = new Thread(future2); - -    FutureTask future3= new FutureTask(new Work(future2)); -    Thread t3 = new Thread(future3); - -    t1.start(); -    t2.start(); -    t3.start(); -} - - static class Work  implements Callable { -    private FutureTask beforeFutureTask; -    public Work(FutureTask beforeFutureTask) { -        this.beforeFutureTask = beforeFutureTask; -    } -    public Integer call() throws Exception { -        if (beforeFutureTask != null) { -            Integer result = beforeFutureTask.get();//阻塞等待 -            System.out.println("thread start:" + Thread.currentThread().getName()); -        } else { -            System.out.println("thread start:" + Thread.currentThread().getName()); -        } -        return 0; -    } - } -} -``` - -[https://blog.csdn.net/Evankaka/article/details/80800081](https://blog.csdn.net/Evankaka/article/details/80800081) - diff --git "a/Interview/myself/\346\214\211\347\203\255\345\272\246\346\200\273\347\273\223lc.md" "b/Interview/myself/\346\214\211\347\203\255\345\272\246\346\200\273\347\273\223lc.md" deleted file mode 100644 index 579af11b..00000000 --- "a/Interview/myself/\346\214\211\347\203\255\345\272\246\346\200\273\347\273\223lc.md" +++ /dev/null @@ -1,4513 +0,0 @@ -## 说明 -> 按lc的热度总结的题目,我看了不少面经,总感觉他们出的算法题,不会太难,肯定是比较热门的题。 - -## 1. 两数之和(4897) -[https://leetcode-cn.com/problems/two-sum/](https://leetcode-cn.com/problems/two-sum/) - -```html -给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 -你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 -``` -```html -给定 nums = [2, 7, 11, 15], target = 9 - -因为 nums[0] + nums[1] = 2 + 7 = 9 -所以返回 [0, 1] -``` -方法很多,看个人,这一套根据我个人准备的,准确且快即可。 - -**双指针** - -```java -class Solution { - public int[] twoSum(int[] nums, int target) { - int p1 = 0, p2 = nums.length - 1; - while (p1 < p2) { - int sum = nums[p1] + nums[p2]; - if (sum < target) - p1++; - else if (sum > target) - p2--; - else return - new int[] {p1, p2}; - } - return new int[]{}; - } -} -``` - -## 2. 两数相加(2819) -[https://leetcode-cn.com/problems/add-two-numbers/](https://leetcode-cn.com/problems/add-two-numbers/) - -```html -给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 - -如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 - -您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 -``` -```html -输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) -输出:7 -> 0 -> 8 -原因:342 + 465 = 807 -``` - -1. 两个链表相加,先判断边界 -2. 创建一个新的链表 -3. while中也要注意边界 -4. 两个链表的值、进位相加 -5. 是否赋值给进位 -6. 三个链表移动指针 - -```java -class Solution { - public ListNode addTwoNumbers(ListNode l1, ListNode l2) { - if (l1 == null && l2 == null) return null; - if (l1 == null) return l2; - if (l2 == null) return l1; - ListNode p1 = l1; - ListNode p2 = l2; - ListNode l3 = new ListNode(-1); - ListNode p3 = l3; - int carried = 0; - while (p1 != null || p2 != null) { - int a = p1 != null ? p1.val : 0; - int b = p2 != null ? p2.val : 0; - p3.next = new ListNode((a + b + carried) % 10); - carried = (a + b + carried) / 10; - p3 = p3.next; - p1 = p1 != null ? p1.next : null; - p2 = p2 != null ? p2.next : null; - } - p3.next = carried != 0 ? new ListNode(1) : null; - return l3.next; - } -} -``` - -## 3. 无重复字符的最长子串(2862) -[https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) - -```html -给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 -``` -```html -输入: "abcabcbb" -输出: 3 -解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 - -输入: "bbbbb" -输出: 1 -解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 -``` -HashMap+两个指针 -两个指针分别记录字母的起始和结束,用map来存 -```java -class Solution { - public int lengthOfLongestSubstring(String s) { - int n = s.length(), ans = 0; - Map map = new HashMap<>(); - for (int i = 0, j = 0; j < n; j++) { - if (map.containsKey(s.charAt(j))) { - i = Math.max(map.get(s.charAt(j)), i); - } - ans = Math.max(ans, j - i + 1); - map.put(s.charAt(j), j + 1); - } - return ans; - } -} -``` - -## 5. 最长回文子串(1478) -[https://leetcode-cn.com/problems/longest-palindromic-substring/](https://leetcode-cn.com/problems/longest-palindromic-substring/) - -```html -给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 -``` -```html -输入: "babad" -输出: "bab" -注意: "aba" 也是一个有效答案。 -``` - -中心扩展 -- 两种情况 -- 奇数长度 -- 偶数长度 -- 取最长,求起始和结束位置 -- 用substring即可 - -```java -class Solution { - public String longestPalindrome(String s) { - if (s == null || s.length() == 0) return s; - int start = 0, end = 0; // 记录起始位置 - for (int i = 0; i < s.length(); i++) { - // 两种情况 以i为中心,以i和i+1为中心 - int len1 = expand(s, i - 1, i + 1); // 中心扩展 - int len2 = expand(s, i, i + 1); - int len = Math.max(len1, len2); // 取最长的长度 - if (len > end - start) { - start = i - (len - 1) / 2; - end = i + len / 2; - } - } - return s.substring(start, end + 1); - } - - private int expand(String s, int l, int r) { - while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { - l--; - r++; - } - // 这里要注意 - return r - l - 1; - } -} -``` - -## 7. 整数反转(2088) -[https://leetcode-cn.com/problems/reverse-integer/](https://leetcode-cn.com/problems/reverse-integer/) - -```html -给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 -``` -```html -输入: 123 -输出: 321 - -输入: -123 -输出: -321 - -输入: 120 -输出: 21 -``` - -```java -class Solution { - public int reverse(int x) { - 注意题目条件 - long ans = 0; - while(x != 0) { - // 常用公式 - ans = ans * 10 + x % 10; - x /= 10; - } - // 判断是否溢出 - if(ans > Integer.MAX_VALUE || ans < Integer.MIN_VALUE) { - return 0; - } - return (int)ans; - } -} -``` - -## 9. 回文数(2094) -[https://leetcode-cn.com/problems/palindrome-number/](https://leetcode-cn.com/problems/palindrome-number/) -```html -判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 -``` - -```html -输入: 121 -输出: true - -输入: -121 -输出: false -解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 - -输入: 10 -输出: false -解释: 从右向左读, 为 01 。因此它不是一个回文数。 -``` - -```java -class Solution { - public boolean isPalindrome(int x) { - // 0也是回文数 - if (x == 0) - return true; - // 特殊条件 - if (x < 0 || x % 10 == 0) - return false; - // 只需要一半 - int right = 0; - while ( x > right) { - right = right * 10 + x % 10; - x /= 10; - } - return x == right || x == right / 10; - } -} -``` -## 11. 盛最多水的容器(1267) -[https://leetcode-cn.com/problems/container-with-most-water/](https://leetcode-cn.com/problems/container-with-most-water/) -```html -给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 - -说明:你不能倾斜容器,且 n 的值至少为 2。 -``` - -```html -输入:[1,8,6,2,5,4,8,3,7] -输出:49 -``` - -```java -class Solution { - public int maxArea(int[] height) { - int max = 0; - for (int i = 0, j = height.length - 1; i < j;) { - // 双指针,谁小取谁,判断移动 - int minHeight = height[i] < height[j] ? height[i++] : height[j--]; - // 每一次都要维护最大值 - max = Math.max(max, (j - i + 1) * minHeight); - } - return max; - } -} -``` - -## 14. 最长公共前缀(1611) -[https://leetcode-cn.com/problems/longest-common-prefix/](https://leetcode-cn.com/problems/longest-common-prefix/) -```html -编写一个函数来查找字符串数组中的最长公共前缀。 - -如果不存在公共前缀,返回空字符串 ""。 -``` - -```html -输入: ["flower","flow","flight"] -输出: "fl" - -输入: ["dog","racecar","car"] -输出: "" -解释: 输入不存在公共前缀。 -``` - -```java -class Solution { - public String longestCommonPrefix(String[] strs) { - if(strs.length == 0) - return ""; - String str = strs[0]; - // 循环用indexOf和substring - for(int i = 1; i < strs.length; i++) { - while(strs[i].indexOf(str) != 0) { - // 每次substring去掉最后一位 - str = str.substring(0, str.length() - 1); - } - } - return str; - } -} -``` - -## 15. 三数之和 -[https://leetcode-cn.com/problems/3sum/](https://leetcode-cn.com/problems/3sum/) - -```html -给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 -``` - -```html -给定数组 nums = [-1, 0, 1, 2, -1, -4], - -满足要求的三元组集合为: -[ - [-1, 0, 1], - [-1, -1, 2] -] -``` - -这道题三要素: -1. 排序 -2. 双指针 -3. 去重复 -```java -class Solution { - public List> threeSum(int[] nums) { - // 排序的目的就是来告别重复 - Arrays.sort(nums); - List> ls = new ArrayList<>(); - for (int i = 0; i < nums.length - 2; i++) { - // 判断是否元素大于0,大于0,没必要操作了 - if (nums[i] > 0) break; - // 判断是否重复 - if (i > 0 && nums[i] == nums[i - 1]) continue; - // 双指针操作 - int l = i + 1, r = nums.length - 1; - while (l < r) { - if (nums[l] + nums[r] < -nums[i]) l++; - else if (nums[l] + nums[r] > -nums[i]) r--; - else { - // 相等了哈 - ls.add(Arrays.asList(nums[i], nums[l], nums[r])); - // 防止重复 - while (l < r && nums[l] == nums[l + 1]) l++; - while (l < r && nums[r] == nums[r - 1]) r--; - l++; - r--; - } - } - } - return ls; - } -} -``` - -哪种代码好理解就用哪种 -```java -class Solution { - public List> threeSum(int[] nums) { - // 排序 - Arrays.sort(nums); - List> ls = new ArrayList<>(); - for (int i = 0; i < nums.length - 2; i++) { - if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) { //跳过可能重复的 - // 转化为两数之和 - int l = i + 1, r = nums.length - 1, sum = 0 - nums[i]; - while(l < r) { - if (nums[l] + nums[r] == sum) { - ls.add(Arrays.asList(nums[i], nums[l], nums[r])); - while (l < r && nums[l] == nums[l + 1]) l++; // 还是一样,跳过重复 - while (l < r && nums[r] == nums[r - 1]) r--; - l++; - r--; - } else if (nums[l] + nums[r] < sum) { - while (l < r && nums[l] == nums[l + 1]) l++; - l++; - } else { - while (l < r && nums[r] == nums[r - 1]) r--; - r--; - } - } - } - } - return ls; - } -} -``` - -## 17. 电话号码的字母组合(1085) -[https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) -```html -给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 - -给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 -``` -```html -输入:"23" -输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. -``` - -回溯 -不需要标记 -但是记得deleteCharAt -```java -class Solution { - private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; - public List letterCombinations(String digits) { - List combinnations = new ArrayList<>(); - if (digits == null || digits.length() == 0) return combinnations; - doCombination(new StringBuilder(), combinnations, digits); - return combinnations; - } - - private void doCombination(StringBuilder prefix, List combinnations, final String digits) { - if (prefix.length() == digits.length()) { - combinnations.add(prefix.toString()); - return; - } - int curDigits = digits.charAt(prefix.length()) - '0'; - String letters = KEYS[curDigits]; - for (char c : letters.toCharArray()) { - prefix.append(c); - doCombination(prefix, combinnations, digits); - prefix.deleteCharAt(prefix.length() - 1); - } - } -} -``` - -## 19. 删除链表的倒数第N个节点(1316) -[https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/) -```html -给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 -``` -```html -给定一个链表: 1->2->3->4->5, 和 n = 2. - -当删除了倒数第二个节点后,链表变为 1->2->3->5. -``` - -快慢指针 -分情况 -```java -class Solution { - public ListNode removeNthFromEnd(ListNode head, int n) { - ListNode fast = head; - while (n-- > 0) { - fast = fast.next; - } - // 这里没懂, 得举例子就懂了 - if (fast == null) - return head.next; - ListNode slow = head; - while (fast.next != null) { - fast = fast.next; - slow = slow.next; - } - // 这里也懂了...举个例子就行 - slow.next = slow.next.next; - return head; - } -} -``` - -## 20. 有效的括号(2132) -[https://leetcode-cn.com/problems/valid-parentheses/](https://leetcode-cn.com/problems/valid-parentheses/) -```html -给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 - -有效字符串需满足: - -左括号必须用相同类型的右括号闭合。 -左括号必须以正确的顺序闭合。 -注意空字符串可被认为是有效字符串。 -``` -```html -输入: "()" -输出: true - -输入: "()[]{}" -输出: true - -输入: "(]" -输出: false - -输入: "([)]" -输出: false -``` -栈思想 -```java -class Solution { - public boolean isValid(String s) { - Stack stack = new Stack<>(); - for (char c : s.toCharArray()) { - if (stack.isEmpty()) - stack.push(c); - else if(isSym(stack.peek(), c)) - stack.pop(); - else - stack.push(c); - } - return stack.isEmpty(); - } - - private boolean isSym(char c1, char c2) { - return (c1 == '(' && c2 == ')') - || (c1 == '{' && c2 == '}') - || (c1 == '[' && c2 == ']'); - } -} -``` -## 21. 合并两个有序链表(1327) -[https://leetcode-cn.com/problems/merge-two-sorted-lists/](https://leetcode-cn.com/problems/merge-two-sorted-lists/) -```html -将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 -``` - -```html -输入:1->2->4, 1->3->4 -输出:1->1->2->3->4->4 -``` - -```java -class Solution { - public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; - } - } -} -``` - -## 25. K 个一组翻转链表(850) -```java -class Solution { - public ListNode reverseKGroup(ListNode head, int k) { - ListNode dummy = new ListNode(-1); - ListNode pre = dummy, cur = head, next; - dummy.next = head; - ListNode p = head; - int len = 0; - while (p != null){ - len++; - p = p.next; - } - // - for (int i = 0; i < len / k; i++) { - for (int j = 0; j < k - 1; j++) { - next = cur.next; - - // 注意这三步 - cur.next = next.next; - next.next = pre.next; - pre.next = next; - } - // 移动的时候注意 - pre = cur; - cur = pre.next; - } - return dummy.next; - } -} -``` - -## 26. 删除排序数组中的重复项(1748) -[https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) -```html -给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 - -不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 -``` -```html -给定数组 nums = [1,1,2], - -函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 - -你不需要考虑数组中超出新长度后面的元素。 -``` -```java -class Solution { - public int removeDuplicates(int[] nums) { - int p = 0; - for(int i = 1; i < nums.length; i++) { - if(nums[p] != nums[i]) { - nums[++p] = nums[i]; - } - } - return p+1; - } -} -``` - -## 27. 移除元素(1411) -[https://leetcode-cn.com/problems/remove-element/](https://leetcode-cn.com/problems/remove-element/) -```html -给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 - -不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 - -元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 -``` -```html -给定 nums = [3,2,2,3], val = 3, - -函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 - -你不需要考虑数组中超出新长度后面的元素。 -``` - -```java -class Solution { - public int removeElement(int[] nums, int val) { - int p = 0; - for (int i = 0; i < nums.length; i++) { - if(nums[i] != val) - nums[p++] = nums[i]; - } - return p; - } -} -``` - -## 28. 实现 strStr()(1176) -[https://leetcode-cn.com/problems/implement-strstr/](https://leetcode-cn.com/problems/implement-strstr/) -```html -实现 strStr() 函数。 - -给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。 -``` - -```html -输入: haystack = "hello", needle = "ll" -输出: 2 -``` -```java -class Solution { - public int strStr(String haystack, String needle) { - int l = haystack.length(), n = needle.length(); - for(int start = 0; start < l - n + 1; start++) { - // subtring + equals - if(haystack.substring(start, start + n).equals(needle)) { - return start; - } - } - return -1; - } -} -``` - -## 33. 搜索旋转排序数组(1033) -[https://leetcode-cn.com/problems/search-in-rotated-sorted-array/](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) - -变相的二分,稍微有点难度 - -```java -class Solution { - public int search(int[] nums, int target) { - int len = nums.length; - int left = 0, right = len - 1; - while (left <= right) { - int mid = (left + right) / 2; - if (nums[mid] == target) - return mid; - else if(nums[mid] < nums[right]) { - // 注意边界条件 - if (nums[mid] < target && target <= nums[right]) - left = mid + 1; - else - right = mid - 1; - } else { - if (nums[left] <= target && target < nums[mid]) - right = mid - 1; - else - left = mid + 1; - } - } - return -1; - } -} -``` - -## 35. 搜索插入位置(1239) -[https://leetcode-cn.com/problems/search-insert-position/](https://leetcode-cn.com/problems/search-insert-position/) -```html -给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 - -你可以假设数组中无重复元素。 -``` -```html -输入: [1,3,5,6], 5 -输出: 2 -输入: [1,3,5,6], 2 -输出: 1 -输入: [1,3,5,6], 7 -输出: 4 -输入: [1,3,5,6], 0 -输出: 0 -``` -二分法 -但要考虑边界 -```java -class Solution { - public int searchInsert(int[] nums, int target) { - int l = 0; - int h = nums.length - 1; - while (l <= h) { - int mid = l + (h - l) / 2; - if (nums[mid] < target) - l = mid + 1; - else if (nums[mid] > target) - h = mid - 1; - else - return mid; - } - // 注意边界 - if (h < 0 && l == 0) - return (l + h) % 2 + 1; - else - return (l + h) / 2 + 1; - } -} -``` - -## 38. 外观数列(1053) -[https://leetcode-cn.com/problems/count-and-say/](https://leetcode-cn.com/problems/count-and-say/) -```html -给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。 - -注意:整数序列中的每一项将表示为一个字符串。 - -「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下: - -1. 1 -2. 11 -3. 21 -4. 1211 -5. 111221 - -第一项是数字 1 - -描述前一项,这个数是 1 即 “一个 1 ”,记作 11 - -描述前一项,这个数是 11 即 “两个 1 ” ,记作 21 - -描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211 - -描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221 -``` - -```html -输入: 1 -输出: "1" -解释:这是一个基本样例。 - -输入: 4 -输出: "1211" -解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。 -``` -StringBuffer + cnt -```java -class Solution { - public String countAndSay(int n) { - if (n == 1) { - return "1"; - } - String str = "1"; - for (int i = 0; i < n - 1; i++) { - StringBuffer sb = new StringBuffer(); - int count = 0; - char code = str.charAt(0); - for (int j = 0; j < str.length(); j++) { - if (str.charAt(j) != code) { - sb.append(count); - sb.append(code); - code = str.charAt(j); - count = 1; - } else { - count++; - } - } - sb.append(count); - sb.append(str.charAt(str.length() - 1)); - str = sb.toString(); - } - return str; - } -} -``` - -## 42. 接雨水(1145) -[https://leetcode-cn.com/problems/trapping-rain-water/](https://leetcode-cn.com/problems/trapping-rain-water/) - -双指针 - -```java -class Solution { - public int trap(int[] height) { - int min = 0, max = 0; - int l = 0, r = height.length - 1; - int res = 0; - while(l < r) { - // 双指针维护最小值 - min = height[height[l] < height[r] ? l++ : r--]; - // 接着维护最大值 - max = Math.max(max, min); - // 累加差值 - res += max - min; - } - return res; - } -} -``` - -## 53. 最大子序和(1385) -[https://leetcode-cn.com/problems/maximum-subarray/](https://leetcode-cn.com/problems/maximum-subarray/) - -```html -给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 -``` - -```html -输入: [-2,1,-3,4,-1,2,1,-5,4], -输出: 6 -解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 -``` - -```java -class Solution { - public int maxSubArray(int[] nums) { - if (nums == null || nums.length == 0) return 0; - int preSum = nums[0]; - int maxSum = preSum; - for (int i = 1; i < nums.length; i++) { - // 注意条件 - preSum = preSum > 0 ? preSum + nums[i] : nums[i]; - maxSum = Math.max(maxSum, preSum); - } - return maxSum; - } -} -``` - -## 55. 跳跃游戏(1059) -[https://leetcode-cn.com/problems/jump-game/](https://leetcode-cn.com/problems/jump-game/) - -```html -给定一个非负整数数组,你最初位于数组的第一个位置。 - -数组中的每个元素代表你在该位置可以跳跃的最大长度。 - -判断你是否能够到达最后一个位置。 -``` - -```html -输入: [2,3,1,1,4] -输出: true -解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 - -输入: [3,2,1,0,4] -输出: false -解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。 -``` - -贪心 -```java -class Solution { - public boolean canJump(int[] nums) { - if (nums.length <= 1) return true; - int n = nums.length; - int max = nums[0]; - for (int i = 1; i < n - 1; i++) { - // 注意条件 - if (i <= max) { - // 最远索引 - max = Math.max(max, nums[i] + i); - } else { - break; - } - } - // 注意判断 - return max >= n - 1; - } -} -``` - -## 66. 加一(1254) -[https://leetcode-cn.com/problems/plus-one/](https://leetcode-cn.com/problems/plus-one/) - -```html -给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 - -最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 - -你可以假设除了整数 0 之外,这个整数不会以零开头。 -``` -```html -输入: [1,2,3] -输出: [1,2,4] -解释: 输入数组表示数字 123。 - -输入: [4,3,2,1] -输出: [4,3,2,2] -解释: 输入数组表示数字 4321。 -``` - -正常操作 -加法中常用 -a = x % 10 -b = x / 10 - -```java -class Solution { - public int[] plusOne(int[] digits) { - int length = digits.length; - int[] res = new int[length + 1]; - int carry = 1; - for (int i = length - 1; i >= 0 ; i--) { - int sums = digits[i] + carry; - res[i] = sums % 10; - carry = sums / 10; - } - if (carry == 1) { - res[0] = 1; - return res; - } - return Arrays.copyOfRange(res,0,length); - - } -} -``` - -## 70. 爬楼梯(1407) -[https://leetcode-cn.com/problems/climbing-stairs/](https://leetcode-cn.com/problems/climbing-stairs/) - -```html -假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 - -每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? - -注意:给定 n 是一个正整数。 -``` - -```html -输入: 2 -输出: 2 -解释: 有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 - -输入: 3 -输出: 3 -解释: 有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 -``` -自底向上 -```java -class Solution { - public int climbStairs(int n) { - if (n <= 2) return n; - int pre2 = 1, pre1 = 2; - for (int i = 3; i <= n; i++) { - int cur = pre2 + pre1; - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - -## 88. 合并两个有序数组(1057) -[https://leetcode-cn.com/problems/merge-sorted-array/](https://leetcode-cn.com/problems/merge-sorted-array/) - -```html -给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 -说明: - -初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 -你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 - -``` -```html -输入: -nums1 = [1,2,3,0,0,0], m = 3 -nums2 = [2,5,6], n = 3 - -输出: [1,2,2,3,5,6] -``` -三指针 -```java -public void merge(int[] nums1, int m, int[] nums2, int n) { - int index1 = m - 1, index2 = n - 1; - int indexMerge = m + n - 1; - while (index1 >= 0 || index2 >= 0) { - if (index1 < 0) { - nums1[indexMerge--] = nums2[index2--]; - } else if (index2 < 0) { - nums1[indexMerge--] = nums1[index1--]; - } else if (nums1[index1] > nums2[index2]) { - nums1[indexMerge--] = nums1[index1--]; - } else { - nums1[indexMerge--] = nums2[index2--]; - } - } -} - -``` - -## 102. 二叉树的层序遍历(1054) -[https://leetcode-cn.com/problems/binary-tree-level-order-traversal/](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) -```html -给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 -``` - -```html - 3 - / \ - 9 20 - / \ - 15 7 - -[ - [3], - [9,20], - [15,7] -] -``` -队列 -```java -class Solution { - public List> levelOrder(TreeNode root) { - List> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(root); - while (!queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) - continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (list.size() != 0) - ret.add(list); - } - return ret; - } -} -``` - -## 121. 买卖股票的最佳时机(1491) -[https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) - -```html -给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 - -如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 - -注意:你不能在买入股票前卖出股票。 -``` - -```html -输入: [7,1,5,3,6,4] -输出: 5 -解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 - 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票 - -输入: [7,6,4,3,1] -输出: 0 -解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 -``` -```java -class Solution { - public int maxProfit(int[] prices) { - // 边界 - if(prices.length == 0) return 0; - // 长度 - int n = prices.length; - // min - int min = prices[0]; - // max - int max = 0; - for (int i = 1; i < n; i++) { - // 一直找最小的股 - min = prices[i] < min ? prices[i] : min; - // 遍历一圈,存最大的利润 - max = Math.max(max, prices[i] - min); - } - return max; - } -} -``` - -## 169. 多数元素(1096) -[https://leetcode-cn.com/problems/majority-element/](https://leetcode-cn.com/problems/majority-element/) - -```html -给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 - -你可以假设数组是非空的,并且给定的数组总是存在多数元素。 - -``` -```html -输入: [3,2,3] -输出: 3 -``` - -```java -class Solution { - public int majorityElement(int[] nums) { - Arrays.sort(nums); - return nums[nums.length / 2]; - } -} -``` - -## 198. 打家劫舍(1035) -[https://leetcode-cn.com/problems/house-robber/](https://leetcode-cn.com/problems/house-robber/) - - -```html -你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 - -给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 -``` - -```html -输入:[1,2,3,1] -输出:4 -解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 -  偷窃到的最高金额 = 1 + 3 = 4 。 - -输入:[2,7,9,3,1] -输出:12 -解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 -  偷窃到的最高金额 = 2 + 9 + 1 = 12 。 -``` - -```java -class Solution { - public int rob(int[] nums) { - int pre2 = 0, pre1 = 0; - for (int i = 0; i < nums.length; i++) { - int cur = Math.max(pre2 + nums[i], pre1); - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - -## 206. 反转链表(5127) -[https://leetcode-cn.com/problems/reverse-linked-list/](https://leetcode-cn.com/problems/reverse-linked-list/) - -```html -输入: 1->2->3->4->5->NULL -输出: 5->4->3->2->1->NULL -``` - -```java -class Solution { - public ListNode reverseList(ListNode head) { - // 尾递归 - // return reverse(null, head); - // 头插 - ListNode pre = null; - ListNode cur = head; - while (cur != null) { - ListNode next = cur.next; - cur.next = pre; - pre = cur; - cur = next; - } - return pre; - } - private ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; - ListNode next = cur.next; - cur.next = pre; - return reverse(cur, next); - } -} -``` - -## 225. 用队列实现栈(4169) -[https://leetcode-cn.com/problems/implement-stack-using-queues/](https://leetcode-cn.com/problems/implement-stack-using-queues/) - -```html -使用队列实现栈的下列操作: - -push(x) -- 元素 x 入栈 -pop() -- 移除栈顶元素 -top() -- 获取栈顶元素 -empty() -- 返回栈是否为空 -``` - -```java -class MyStack { - private Queue queue; - /** Initialize your data structure here. */ - public MyStack() { - queue = new LinkedList<>(); - } - - /** Push element x onto stack. */ - public void push(int x) { - queue.add(x); - // 加完取长度 - int cnt = queue.size(); - // 倒置 - while (cnt-- > 1) { - queue.add(queue.poll()); - } - } - - /** Removes the element on top of the stack and returns that element. */ - public int pop() { - return queue.remove(); - } - - /** Get the top element. */ - public int top() { - return queue.peek(); - } - - /** Returns whether the stack is empty. */ - public boolean empty() { - return queue.isEmpty(); - } -} -``` - -## 283. 移动零(1008) -[https://leetcode-cn.com/problems/move-zeroes/](https://leetcode-cn.com/problems/move-zeroes/) - -```html -输入: [0,1,0,3,12] -输出: [1,3,12,0,0] -``` - -1. 先把不是0的移动左 -2. 最后陆续加0 - -```java -class Solution { - public void moveZeroes(int[] nums) { - int idx = 0; - for (int num : nums) { - if (num != 0) - nums[idx++] = num; - } - while (idx < nums.length) { - nums[idx++] =0; - } - } -} -``` -## 1103. 分糖果 II(1004) -[https://leetcode-cn.com/problems/distribute-candies-to-people/](https://leetcode-cn.com/problems/distribute-candies-to-people/) - -```html -排排坐,分糖果。 - -我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。 - -给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。 - -然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。 - -重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。 - -返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。 -``` -```html -输入:candies = 7, num_people = 4 -输出:[1,2,3,1] -解释: -第一次,ans[0] += 1,数组变为 [1,0,0,0]。 -第二次,ans[1] += 2,数组变为 [1,2,0,0]。 -第三次,ans[2] += 3,数组变为 [1,2,3,0]。 -第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。 - -输入:candies = 10, num_people = 3 -输出:[5,2,3] -解释: -第一次,ans[0] += 1,数组变为 [1,0,0]。 -第二次,ans[1] += 2,数组变为 [1,2,0]。 -第三次,ans[2] += 3,数组变为 [1,2,3]。 -第四次,ans[0] += 4,最终数组变为 [5,2,3]。 - -``` -```java -class Solution { - public int[] distributeCandies(int candies, int num_people) { - int[] ans = new int[num_people]; - int i; - for (i = 0; candies > 0; i++) { - ans[i % num_people] += i + 1; - candies -= i + 1; - } - ans[(i - 1) % num_people] += candies; - return ans; - } -} -``` - -## 23. 合并K个排序链表(924) -[https://leetcode-cn.com/problems/merge-k-sorted-lists/](https://leetcode-cn.com/problems/merge-k-sorted-lists/) - -```html -输入: -[ - 1->4->5, - 1->3->4, - 2->6 -] -输出: 1->1->2->3->4->4->5->6 -``` - -最小堆 -```java -class Solution { - public ListNode mergeKLists(ListNode[] lists) { - if (lists == null || lists.length == 0) return null; - PriorityQueue queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val); - ListNode dummy = new ListNode(0); - ListNode p = dummy; - for (ListNode node : lists) { - if (node != null) queue.add(node); - } - while (!queue.isEmpty()) { - p.next = queue.poll(); - p = p.next; - if (p.next != null) queue.add(p.next); - } - return dummy.next; - } -} -``` - -分治 -```java -class Solution { - public ListNode mergeKLists(ListNode[] lists) { - if (lists == null || lists.length == 0) return null; - return merge(lists, 0, lists.length - 1); - } - - private ListNode merge(ListNode[] lists, int left, int right) { - if (left == right) return lists[left]; - int mid = left + (right - left) / 2; - ListNode l1 = merge(lists, left, mid); - ListNode l2 = merge(lists, mid + 1, right); - return mergeTwoLists(l1, l2); - } - - private ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1,l2.next); - return l2; - } - } -} -``` - -## 24. 两两交换链表中的节点(947) -[https://leetcode-cn.com/problems/swap-nodes-in-pairs/](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) -```html -给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 - -你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 -``` - -```html -给定 1->2->3->4, 你应该返回 2->1->4->3. -``` - -```java -class Solution { - public ListNode swapPairs(ListNode head) { - ListNode node = new ListNode(-1); - node.next = head; - ListNode pre = node; - while (pre.next != null && pre.next.next != null) { - ListNode l1 = pre.next, l2 = pre.next.next; - ListNode next = l2.next; - l1.next = next; - l2.next = l1; - pre.next = l2; - pre = l1; - } - return node.next; - } -} -``` - -## 34. 在排序数组中查找元素的第一个和最后一个位置(935) -[https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) - -```html -给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 - -你的算法时间复杂度必须是 O(log n) 级别。 - -如果数组中不存在目标值,返回 [-1, -1]。 - -``` -```html -输入: nums = [5,7,7,8,8,10], target = 8 -输出: [3,4] - -输入: nums = [5,7,7,8,8,10], target = 6 -输出: [-1,-1] -``` - -双指针+二分法 -```java -class Solution { - public int[] searchRange(int[] nums, int target) { - int first = findFirst(nums, target); - int last = findFirst(nums, target + 1) - 1; - if (first == nums.length || nums[first] != target) { - return new int[] {-1, -1}; - } else { - return new int[]{first, Math.max(first, last)}; - } - } - private int findFirst(int[] nums, int target) { - int l = 0, h = nums.length; // h 的初始值和往常不一样 - while (l < h) { - int m = l + ( h - l) / 2; - if (nums[m] >= target) h = m; - else l = m + 1; - } - return l; - } -} -``` - -## 46. 全排列(985) -[https://leetcode-cn.com/problems/permutations/](https://leetcode-cn.com/problems/permutations/) - -```html -给定一个 没有重复 数字的序列,返回其所有可能的全排列。 -``` -```html -输入: [1,2,3] -输出: -[ - [1,2,3], - [1,3,2], - [2,1,3], - [2,3,1], - [3,1,2], - [3,2,1] -] -``` - -dfs -```java -class Solution { - public List> permute(int[] nums) { - List> permutes = new ArrayList<>(); - List permuteList = new ArrayList<>(); - boolean[] hasVisited = new boolean[nums.length]; - backtracking(permuteList, permutes, hasVisited, nums); - return permutes; - } - private void backtracking(List permuteList, List> permutes, boolean[] visited, final int[] nums) { - if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); // 重新构造一个List - return; - } - for (int i = 0; i < visited.length; i++) { - if (visited[i]) continue; - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - permuteList.remove(permuteList.size() - 1); - visited[i] = false; - } - - } -} -``` - -## 56. 合并区间(950) -[https://leetcode-cn.com/problems/merge-intervals/](https://leetcode-cn.com/problems/merge-intervals/) - -```html -给出一个区间的集合,请合并所有重叠的区间。 -``` - -```html -输入: [[1,3],[2,6],[8,10],[15,18]] -输出: [[1,6],[8,10],[15,18]] -解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. - -输入: [[1,4],[4,5]] -输出: [[1,5]] -解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 -``` - - -```java -class Solution { - public int[][] merge(int[][] intervals) { - if (intervals == null || intervals.length <= 1) return intervals; - Arrays.sort(intervals, (a, b) -> a[0] - b[0]); - List list = new ArrayList<>(); - int i = 0; - int n = intervals.length; - while (i < n) { - int l = intervals[i][0]; - int r = intervals[i][1]; - while (i < n - 1 && r >= intervals[i + 1][0]) { - r = Math.max(r, intervals[i + 1][1]); - i++; - } - list.add(new int[] {l, r}); - i++; - } - return list.toArray(new int[list.size()][2]); - } -} -``` - -## 58. 最后一个单词的长度(966) -[https://leetcode-cn.com/problems/length-of-last-word/](https://leetcode-cn.com/problems/length-of-last-word/) -```html -给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。 - -如果不存在最后一个单词,请返回 0 。 - -说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。 -``` - -```html -输入: "Hello World" -输出: 5 -``` - -```java -class Solution { - public int lengthOfLastWord(String s) { - String[] strs = s.split(" "); - if(strs.length == 0) return 0; - return strs[strs.length-1].length(); - } -} -``` - -## 67. 二进制求和(919) -[https://leetcode-cn.com/problems/add-binary/](https://leetcode-cn.com/problems/add-binary/) - -```html -给你两个二进制字符串,返回它们的和(用二进制表示)。 - -输入为 非空 字符串且只包含数字 1 和 0。 -``` - -```html -输入: a = "11", b = "1" -输出: "100" - -输入: a = "1010", b = "1011" -输出: "10101" -``` - -```java -class Solution { - public String addBinary(String a, String b) { - int i = a.length() - 1, j = b.length() - 1, carry = 0; - StringBuilder str = new StringBuilder(); - while (carry == 1 || i >= 0 || j >= 0) { - if (i >= 0 && a.charAt(i--) == '1') carry++; - if (j >= 0 && b.charAt(j--) == '1') carry++; - // 注意这里 - str.append(carry % 2); - carry /= 2; - } - return str.reverse().toString(); - } - -``` - -## 101. 对称二叉树(929) -[https://leetcode-cn.com/problems/symmetric-tree/](https://leetcode-cn.com/problems/symmetric-tree/) - -```html -给定一个二叉树,检查它是否是镜像对称的。 - - 1 - / \ - 2 2 - / \ / \ -3 4 4 3 -``` - -```java -class Solution { - public boolean isSymmetric(TreeNode root) { - if (root == null) return true; - return isSymmetric(root.left, root.right); - } - private boolean isSymmetric(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) return true; - if (t1 == null || t2 == null) return false; - if (t1.val != t2.val) return false; - return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left); - } -} -``` - -## 125. 验证回文串(930) -[https://leetcode-cn.com/problems/valid-palindrome/](https://leetcode-cn.com/problems/valid-palindrome/) - -```html -给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 - -说明:本题中,我们将空字符串定义为有效的回文串。 -``` - -```html -输入: "A man, a plan, a canal: Panama" -输出: true - -输入: "race a car" -输出: false -``` -双指针 -```java -class Solution { - public boolean isPalindrome(String s) { - if (s.equals("")) return true; - s = s.toLowerCase(); - char[] sChar = s.toCharArray(); - int l = 0, r = sChar.length - 1; - while (l <= r) { - if (sChar[l] == sChar[r]) { - l++; - r--; - } else if (!isNormalChar(sChar[l])) { - l++; - } else if (!isNormalChar(sChar[r])) { - r--; - } else { - return false; - } - } - return true; - } - private boolean isNormalChar(char a){ - return Character.isLowerCase(a) || Character.isUpperCase(a) || Character.isDigit(a); - } -} -``` - -## 104. 二叉树的最大深度(857) -[https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) - - -```html -给定一个二叉树,找出其最大深度。 - -二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 - -说明: 叶子节点是指没有子节点的节点。 -``` -```html - 3 - / \ - 9 20 - / \ - 15 7 -``` - -```java -class Solution { - public int maxDepth(TreeNode root) { - if (root == null) return 0; - int l = maxDepth(root.left); - int r = maxDepth(root.right); - return 1 + Math.max(l, r); - } -} -``` - -## 136. 只出现一次的数字(890) -[https://leetcode-cn.com/problems/single-number/](https://leetcode-cn.com/problems/single-number/) - -```html -输入: [2,2,1] -输出: 1 -``` -异或 -```java -class Solution { - public int singleNumber(int[] nums) { - int ret = 0; - for (int num : nums) - ret = ret ^ num; - return ret; - } -} -``` - -## 141. 环形链表(865) -[https://leetcode-cn.com/problems/linked-list-cycle/](https://leetcode-cn.com/problems/linked-list-cycle/) - -```html -给定一个链表,判断链表中是否有环。 - -为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 - -输入:head = [3,2,0,-4], pos = 1 -输出:true -解释:链表中有一个环,其尾部连接到第二个节点。 -``` -快慢指针 -```java -public class Solution { - public boolean hasCycle(ListNode head) { - if (head == null) { - return false; - } - ListNode l1 = head, l2 = head.next; - while (l1 != null && l2 != null && l2.next != null) { - if (l1 == l2) { - return true; - } - l1 = l1.next; - l2 = l2.next.next; - } - return false; - } -} -``` - -## 200. 岛屿数量(853) -[https://leetcode-cn.com/problems/number-of-islands/](https://leetcode-cn.com/problems/number-of-islands/) - -```html -给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 - -岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。 - -此外,你可以假设该网格的四条边均被水包围。 -``` - -```html -输入: -[ -['1','1','1','1','0'], -['1','1','0','1','0'], -['1','1','0','0','0'], -['0','0','0','0','0'] -] -输出: 1 - -输入: -[ -['1','1','0','0','0'], -['1','1','0','0','0'], -['0','0','1','0','0'], -['0','0','0','1','1'] -] -输出: 3 -解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。 - -``` - -dfs -```java -class Solution { - private int m, n; - private int[][] direaction = {{0,1},{0,-1},{1,0},{-1,0}}; - public int numIslands(char[][] grid) { - if (grid == null || grid.length == 0) return 0; - m = grid.length; - n = grid[0].length; - int islandsNum = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (grid[i][j] != '0') { - dfs(grid, i, j); - islandsNum++; - } - } - } - return islandsNum; - } - - private void dfs(char[][] grid, int i, int j) { - if (i < 0 || i >= m || j < 0 || j >=n || grid[i][j] == '0') { - return; - } - grid[i][j] = '0'; - for (int[] d : direaction) { - dfs(grid, i + d[0], j + d[1]); - } - } -} -``` - -## 215. 数组中的第K个最大元素(855) -[https://leetcode-cn.com/problems/kth-largest-element-in-an-array/](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) -```html - -在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 -``` - -```html -输入: [3,2,1,5,6,4] 和 k = 2 -输出: 5 - -输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 -输出: 4 -``` - -快排 -```java -class Solution { - public int findKthLargest(int[] nums, int k) { - k = nums.length - k; - int l = 0, h = nums.length - 1; - while (l < h) { - int j = partition(nums, l , h); - if (j == k) { - break; - } else if (j < k) { - l = j + 1; - } else { - h = j - 1; - } - } - return nums[k]; - } - - private int partition(int[] arr, int l, int r) { - int pivot = l; - int index = pivot + 1; - for (int i = index; i <= r; i++) { - if (arr[i] < arr[pivot]) { - swap(arr, i, index++); - } - } - swap(arr, pivot, index - 1); - return index - 1; - } - - private void swap(int[] arr, int i, int j) { - int t = arr[i]; - arr[i] = arr[j]; - arr[j] = t; - } -} -``` - -## 409. 最长回文串(876) -[https://leetcode-cn.com/problems/longest-palindrome/](https://leetcode-cn.com/problems/longest-palindrome/) - -```html - -给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。 - -在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。 - -注意: -假设字符串的长度不会超过 1010。 -``` - -```html -输入: -"abccccdd" - -输出: -7 - -解释: -我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。 -``` - -```java -class Solution { - public int longestPalindrome(String s) { - int[] cnts = new int[256]; - for (char c : s.toCharArray()) { - cnts[c]++; - } - int palindrome = 0; - // 偶数个字母加起来,就算不是偶数个,也拿偶数个,比如5拿4 - for (int cnt : cnts) { - palindrome += (cnt / 2) * 2; - } - // 小于,则说明有个字母或多个字母是奇数个,拿一放中间 - if (palindrome < s.length()) { - palindrome++; - } - return palindrome; - } -} -``` - -## 876. 链表的中间结点(853) -[https://leetcode-cn.com/problems/middle-of-the-linked-list/](https://leetcode-cn.com/problems/middle-of-the-linked-list/) - -```html -给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 - -如果有两个中间结点,则返回第二个中间结点。 -``` - -```html -输入:[1,2,3,4,5] -输出:此列表中的结点 3 (序列化形式:[3,4,5]) -返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 -注意,我们返回了一个 ListNode 类型的对象 ans,这样: -ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. - -输入:[1,2,3,4,5,6] -输出:此列表中的结点 4 (序列化形式:[4,5,6]) -由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 -``` -快慢指针 -```java -class Solution { - public ListNode middleNode(ListNode head) { - ListNode p = head, q = head; - while (q != null && q.next != null) { - q = q.next.next; - p = p.next; - } - return p; - } -} -``` - -## 41. 缺失的第一个正数(751) -[https://leetcode-cn.com/problems/first-missing-positive/](https://leetcode-cn.com/problems/first-missing-positive/) - -```html -给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。 -``` - -``` -输入: [1,2,0] -输出: 3 -``` - -``` -输入: [3,4,-1,1] -输出: 2 -``` -``` -输入: [7,8,9,11,12] -输出: 1 -``` - -怎么会怎么来,排序,接着遍历。 -```java -class Solution { - public int firstMissingPositive(int[] nums) { - int ans = 1; - Arrays.sort(nums); - for (int i = 0; i < nums.length; i++) { - if (nums[i] > ans) break; - if (nums[i] == ans) ans++; - } - return ans; - } -} -``` - -## 62. 不同路径(780) - -``` -html -一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 - -机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 - -问总共有多少条不同的路径? -``` -```html -输入: m = 3, n = 2 -输出: 3 -解释: -从左上角开始,总共有 3 条路径可以到达右下角。 -1. 向右 -> 向右 -> 向下 -2. 向右 -> 向下 -> 向右 -3. 向下 -> 向右 -> 向右 -``` -dp -```java -class Solution { - public int uniquePaths(int m, int n) { - int[] dp = new int[n]; - Arrays.fill(dp, 1); - for (int i = 1; i < m; i++) { - for (int j = 1; j < n; j++) { - dp[j] += dp[j - 1]; - } - } - return dp[n - 1]; - } -} -``` - -## 151. 翻转字符串里的单词(702) -```html -输入: "the sky is blue" -输出: "blue is sky the" - -输入: "  hello world!  " -输出: "world! hello" -解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 - -输入: "a good   example" -输出: "example good a" -解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 - -``` - -用了api -```java -class Solution { - public String reverseWords(String s) { - String[] words = s.trim().split(" +"); - Collections.reverse(Arrays.asList(words)); - return String.join(" ", words); - } -} -``` - -## 155. 最小栈(726) -[https://leetcode-cn.com/problems/min-stack/](https://leetcode-cn.com/problems/min-stack/) - -```html -设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 - -push(x) —— 将元素 x 推入栈中。 -pop() —— 删除栈顶的元素。 -top() —— 获取栈顶元素。 -getMin() —— 检索栈中的最小元素。 -``` - -辅助栈 - -```java -class MinStack { - private Stack dataStack; - private Stack minStack; - /** initialize your data structure here. */ - public MinStack() { - dataStack = new Stack<>(); - minStack = new Stack<>(); - } - - public void push(int x) { - dataStack.push(x); - minStack.push(minStack.isEmpty() ? x : Math.min(minStack.peek(), x)); - } - - public void pop() { - dataStack.pop(); - minStack.pop(); - } - - public int top() { - return dataStack.peek(); - } - - public int getMin() { - return min; - } -} -``` - -## 300. 最长上升子序列(718) -[https://leetcode-cn.com/problems/longest-increasing-subsequence/](https://leetcode-cn.com/problems/longest-increasing-subsequence/) - -```html -给定一个无序的整数数组,找到其中最长上升子序列的长度。 -``` - -```html -输入: [10,9,2,5,3,7,101,18] -输出: 4 -解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 -``` -dp -```java -class Solution { - public int lengthOfLIS(int[] nums) { - if (nums.length == 0) return 0; - int[] dp = new int[nums.length]; - Arrays.fill(dp, 1); - for (int i = 0; i < nums.length; i++) { - for (int j = 0; j < i; j++) { - if (nums[i] > nums[j]) { - dp[i] = Math.max(dp[i], dp[j] + 1); // 关键这里, - } - } - } - return Arrays.stream(dp).max().orElse(0); - } -} -``` - -## 322. 零钱兑换(755) -[https://leetcode-cn.com/problems/coin-change/](https://leetcode-cn.com/problems/coin-change/) - -```html -给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 - -``` -```html -输入: coins = [1, 2, 5], amount = 11 -输出: 3 -解释: 11 = 5 + 5 + 1 - -输入: coins = [2], amount = 3 -输出: -1 -``` - -完全背包 -```java -class Solution { - public int coinChange(int[] coins, int amount) { - if (amount == 0) return 0; - int[] dp = new int[amount + 1]; - for (int coin : coins) { - for (int i = coin; i <= amount; i++) { //将逆序遍历改为正序遍历 - if (i == coin) { - dp[i] = 1; - } else if (dp[i] == 0 && dp[i - coin] != 0) { - dp[i] = dp[i - coin] + 1; - - } else if (dp[i - coin] != 0) { - dp[i] = Math.min(dp[i], dp[i - coin] + 1); - } - } - } - return dp[amount] == 0 ? -1 : dp[amount]; - } - -} -``` - -## 1013. 将数组分成和相等的三个部分(798) -[https://leetcode-cn.com/problems/partition-array-into-three-parts-with-equal-sum/](https://leetcode-cn.com/problems/partition-array-into-three-parts-with-equal-sum/) - -```html -给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。 - -形式上,如果可以找出索引 i+1 < j 且满足 A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + A[A.length - 1] 就可以将数组三等分。 -``` - -```html -输入:[0,2,1,-6,6,-7,9,1,2,0,1] -输出:true -解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1 - -输入:[0,2,1,-6,6,7,9,-1,2,0,1] -输出:false - -输入:[3,3,6,5,-2,2,5,1,-9,4] -输出:true - -``` - -```java -class Solution { - public boolean canThreePartsEqualSum(int[] A) { - int sum = 0; - // 遍历数组求总和 - for (int num : A) { - sum += num; - } - // 数组A的和如果不能被3整除直接返回false - if (sum % 3 != 0) { - return false; - } - // 遍历数组累加,每累加到目标值cnt加1,表示又找到1段 - sum /= 3; - int curSum = 0, cnt = 0; - for (int i = 0; i < A.length; i++) { - curSum += A[i]; - if (curSum == sum) { - cnt++; - curSum = 0; - } - } - // 最后判断是否找到了3段(注意如果目标值是0的话可以大于3段) - return cnt == 3 || (cnt > 3 && sum == 0); - } -} -``` - -## 1160. 拼写单词(705) -[https://leetcode-cn.com/problems/find-words-that-can-be-formed-by-characters/](https://leetcode-cn.com/problems/find-words-that-can-be-formed-by-characters/) - -```html -给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars。 - -假如你可以用 chars 中的『字母』(字符)拼写出 words 中的某个『单词』(字符串),那么我们就认为你掌握了这个单词。 - -注意:每次拼写(指拼写词汇表中的一个单词)时,chars 中的每个字母都只能用一次。 - -返回词汇表 words 中你掌握的所有单词的 长度之和。 -``` -```html -输入:words = ["cat","bt","hat","tree"], chars = "atach" -输出:6 -解释: -可以形成字符串 "cat" 和 "hat",所以答案是 3 + 3 = 6。 - -输入:words = ["hello","world","leetcode"], chars = "welldonehoneyr" -输出:10 -解释: -可以形成字符串 "hello" 和 "world",所以答案是 5 + 5 = 10。 -``` - -类似于map的数组即可。双map -```java -class Solution { - public int countCharacters(String[] words, String chars) { - int[] hash = new int[26]; - for (char ch : chars.toCharArray()) { - hash[ch - 'a']++; - } - int[] tmp = new int[26]; - int len = 0; - for (String word : words) { - Arrays.fill(tmp, 0); - boolean flag = true; - for (char ch : word.toCharArray()) { - tmp[ch - 'a']++; - if (tmp[ch - 'a'] > hash[ch - 'a']) - flag = false; - } - len += flag ? word.length() : 0; - } - return len; - } -} -``` - -## 78. 子集(633) -[https://leetcode-cn.com/problems/subsets/](https://leetcode-cn.com/problems/subsets/) - -```html -给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 - -说明:解集不能包含重复的子集。 -``` - -```html -输入: nums = [1,2,3] -输出: -[ - [3], -  [1], -  [2], -  [1,2,3], -  [1,3], -  [2,3], -  [1,2], -  [] -] -``` - -```java -class Solution { - public List> subsets(int[] nums) { - List> subsets = new ArrayList<>(); - List tempSubset = new ArrayList<>(); - for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小 - } - return subsets; - } - - private void backtracking(int start, List tempSubset, List> subsets, - final int size, final int[] nums) { - - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); - return; - } - for (int i = start; i < nums.length; i++) { - tempSubset.add(nums[i]); - backtracking(i + 1, tempSubset, subsets, size, nums); - tempSubset.remove(tempSubset.size() - 1); - } - } - -} -``` - -## 83. 删除排序链表中的重复元素(603) -[https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) - -```html -给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 -``` - -```html -输入: 1->1->2 -输出: 1->2 - -输入: 1->1->2->3->3 -输出: 1->2->3 -``` - -```java -class Solution { - public ListNode deleteDuplicates(ListNode head) { - if (head == null || head.next == null) return head; - head.next = deleteDuplicates(head.next); - return head.val == head.next.val ? head.next : head; - } -} -``` - -## 94. 二叉树的中序遍历(683) -[https://leetcode-cn.com/problems/binary-tree-inorder-traversal/](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) - -```html -给定一个二叉树,返回它的中序 遍历。 -``` -```html -输入: [1,null,2,3] - 1 - \ - 2 - / - 3 - -输出: [1,3,2] -``` -栈 -```java -class Solution { - public List inorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - if (root == null) return ret; - Stack stack = new Stack<>(); - TreeNode cur = root; - while (cur != null || !stack.isEmpty()) { - while (cur != null) { - stack.push(cur); - cur = cur.left; // 遍历到做左 - } - TreeNode node = stack.pop(); // 从下往上弹 - ret.add(node.val); - cur = node.right; // 弹完遍历右 - } - return ret; - } -} -``` -## 100. 相同的树(652) -[https://leetcode-cn.com/problems/same-tree/](https://leetcode-cn.com/problems/same-tree/) - -```html -给定两个二叉树,编写一个函数来检验它们是否相同。 - -如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 -``` - -```html -输入: 1 1 - / \ / \ - 2 3 2 3 - - [1,2,3], [1,2,3] - -输出: true - -输入: 1 1 - / \ - 2 2 - - [1,2], [1,null,2] - -输出: false - -``` -```java -class Solution { - public boolean isSameTree(TreeNode p, TreeNode q) { - if(p == null && q == null) return true; - if(p == null || q == null) return false; - if(p.val != q.val) return false; - return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); - } -} -``` - -## 118. 杨辉三角(642) -[https://leetcode-cn.com/problems/pascals-triangle/](https://leetcode-cn.com/problems/pascals-triangle/) - -```html -输入: 5 -输出: -[ - [1], - [1,1], - [1,2,1], - [1,3,3,1], - [1,4,6,4,1] -] -``` - -```java -class Solution { - public List> generate(int numRows) { - List> ans = new ArrayList<>(); - for(int i = 0; i < numRows; i++) { - List curRow = new ArrayList<>(); - for(int j = 0; j <= i; j++) { - if(j == 0 || j == i) { - curRow.add(1); - continue; - } - if(i == 0 || i == 1) { - continue; - } - List preRow = ans.get(i - 1); - int value = preRow.get(j - 1) + preRow.get(j); - curRow.add(value); - } - ans.add(curRow); - } - return ans; - } -} -``` - -## 160. 相交链表(669) -[https://leetcode-cn.com/problems/intersection-of-two-linked-lists/](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/) - -双指针,A走完,走B,B走完,走A -```java -public class Solution { - public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - ListNode l1 = headA, l2 = headB; - while (l1 != l2) { - l1 = (l1 == null) ? headB : l1.next; - l2 = (l2 == null) ? headA : l2.next; - } - return l1; - } -} -``` - -## 202. 快乐数(668) -[https://leetcode-cn.com/problems/happy-number/](https://leetcode-cn.com/problems/happy-number/) - -```html -编写一个算法来判断一个数 n 是不是快乐数。 - -「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。 - -如果 n 是快乐数就返回 True ;不是,则返回 False 。 - -``` - -```html -输入:19 -输出:true -解释: -12 + 92 = 82 -82 + 22 = 68 -62 + 82 = 100 -12 + 02 + 02 = 1 -``` - -```java -class Solution { - public boolean isHappy(int n) { - if(n == 1) return true; - HashSet set = new HashSet<>(); - while(2 > 1) { - int sum = 0; - while (n > 0) { - sum += (n % 10) *(n % 10); - n /= 10; - } - if(sum == 1) return true; - if(!set.add(sum)) return false; - n = sum; - } - } -} -``` - -## 234. 回文链表(624) -[https://leetcode-cn.com/problems/palindrome-linked-list/](https://leetcode-cn.com/problems/palindrome-linked-list/) - -请判断一个链表是否为回文链表。 - -```html -输入: 1->2 -输出: false -输入: 1->2->2->1 -输出: true -``` - -```java -class Solution { - public boolean isPalindrome(ListNode head) { - if(head == null || head.next == null) return true; - // 找中点 - ListNode slow = head, fast = head.next; - while(fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - if(fast != null) slow = slow.next; - // cut - cut(head, slow); - // 比较 - return isEqual(head, reverse(slow)); - - } - - public void cut (ListNode head, ListNode cutNode) { - ListNode node = head; - while(node.next != cutNode) { - node = node.next; - } - node.next = null; - } - - public ListNode reverse(ListNode head) { - ListNode pre = null; - ListNode cur = head; - while(cur != null) { - ListNode nextNode = cur.next; - cur.next = pre; - pre = cur; - cur = nextNode; - } - return pre; - } - - public boolean isEqual(ListNode l1, ListNode l2) { - while(l1 != null && l2 != null) { - if(l1.val != l2.val) return false; - l1 = l1.next; - l2 = l2.next; - } - return true; - } -} -``` - -## 344. 反转字符串(660) -[https://leetcode-cn.com/problems/reverse-string/](https://leetcode-cn.com/problems/reverse-string/) - -```html -编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 - -不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 - -你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 - -``` -```html -输入:["h","e","l","l","o"] -输出:["o","l","l","e","h"] -输入:["H","a","n","n","a","h"] -输出:["h","a","n","n","a","H"] -``` -双指针 - -```java -class Solution { - public void reverseString(char[] s) { - int p1 = 0, p2 = s.length - 1; - while(p1 < p2 ){ - swap(s, p1++, p2--); - } - } - public void swap(char[] s, int i, int j) { - char temp = s[i]; - s[i] = s[j]; - s[j] = temp; - } -} -``` - -## 695. 岛屿的最大面积(648) -[https://leetcode-cn.com/problems/max-area-of-island/](https://leetcode-cn.com/problems/max-area-of-island/) - -```html -给定一个包含了一些 0 和 1 的非空二维数组 grid 。 - -一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。 - -找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。) -``` - -```html -[[0,0,1,0,0,0,0,1,0,0,0,0,0], - [0,0,0,0,0,0,0,1,1,1,0,0,0], - [0,1,1,0,1,0,0,0,0,0,0,0,0], - [0,1,0,0,1,1,0,0,1,0,1,0,0], - [0,1,0,0,1,1,0,0,1,1,1,0,0], - [0,0,0,0,0,0,0,0,0,0,1,0,0], - [0,0,0,0,0,0,0,1,1,1,0,0,0], - [0,0,0,0,0,0,0,1,1,0,0,0,0]] - 对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。 -``` - -```java -class Solution { - private int m, n; - private int[][] direaction = {{0,1},{0,-1},{1,0},{-1,0}}; - public int maxAreaOfIsland(int[][] grid) { - if(grid == null || grid.length == 0) return 0; - m = grid.length; - n = grid[0].length; - int maxArea = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - maxArea = Math.max(maxArea, dfs(grid, i, j)); - } - } - return maxArea; - } - private int dfs(int[][] grid, int r, int c) { - if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) { - return 0; - } - grid[r][c] = 0; - int area = 1; - for (int[] d : direaction) { - area += dfs(grid, r + d[0], c + d[1]); - } - return area; - } -} -``` -## 739. 每日温度(698) -[https://leetcode-cn.com/problems/daily-temperatures/](https://leetcode-cn.com/problems/daily-temperatures/) - -```html -请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 - -例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 - -提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 -``` -递减栈 -```java -class Solution { - public int[] dailyTemperatures(int[] T) { - Stack stack = new Stack<>(); - int[] res = new int[T.length]; - for (int i = 0; i < T.length; i++) { - while (!stack.isEmpty() && T[i] > T[stack.peek()]) { - int t = stack.pop(); - res[t] = i - t; - } - stack.push(i); - } - return res; - } -} -``` - -## 39. 组合总和(582) -[https://leetcode-cn.com/problems/combination-sum/](https://leetcode-cn.com/problems/combination-sum/) - -```html -给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 - -candidates 中的数字可以无限制重复被选取。 - -``` - -```html -输入: candidates = [2,3,6,7], target = 7, -所求解集为: -[ - [7], - [2,2,3] -] - -输入: candidates = [2,3,6,7], target = 7, -所求解集为: -[ - [7], - [2,2,3] -] - -``` -```java -class Solution { - public List> combinationSum(int[] candidates, int target) { - List> combinations = new ArrayList<>(); - backtracking(new ArrayList<>(), combinations, 0, target, candidates); - return combinations; - } - - private void backtracking(List tempCombination, List> combinations, - int start, int target, final int[] candidates) { - - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); - return; - } - for (int i = start; i < candidates.length; i++) { - if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - backtracking(tempCombination, combinations, i, target - candidates[i], candidates); - tempCombination.remove(tempCombination.size() - 1); - } - } - } -} -``` - -## 75. 颜色分类(584) -[https://leetcode-cn.com/problems/sort-colors/](https://leetcode-cn.com/problems/sort-colors/) -给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 - -此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 -``` -```html -输入: [2,0,2,1,1,0] -输出: [0,0,1,1,2,2] -``` -zero和two作为双指针 -```java -class Solution { - public void sortColors(int[] nums) { - int zero = -1, one = 0, two = nums.length; - while (one < two) { - if (nums[one] == 0) { - swap(nums, ++zero, one++); - } else if (nums[one] == 2){ - swap(nums, --two, one); - } else { - ++one; - } - } - } - private void swap(int[] a, int i, int j) { - int t = a[i]; - a[i] = a[j]; - a[j] = t; - } -} -``` - -## 111. 二叉树的最小深度(594) -[https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) - -```html - -给定一个二叉树,找出其最小深度。 - -最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 - -说明: 叶子节点是指没有子节点的节点。 - - 3 - / \ - 9 20 - / \ - 15 7 - -2 -``` -```java -class Solution { - public int minDepth(TreeNode root) { - if (root == null) return 0; - int l = minDepth(root.left); - int r = minDepth(root.right); - if(l == 0 || r == 0) return l + r + 1; - return Math.min(l, r) + 1; - } -} -``` - -## 120. 三角形最小路径和(523) -[https://leetcode-cn.com/problems/triangle/](https://leetcode-cn.com/problems/triangle/) - -```html -给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 - -相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。 -``` - -```html -[ - [2], - [3,4], - [6,5,7], - [4,1,8,3] -] -``` - -dp - -```java -class Solution { - public int minimumTotal(List> triangle) { - if(triangle.size() == 0) return 0; - int row = triangle.size(); - int[][] dp = new int[row][triangle.get(row - 1).size()]; - // 初始化 - for(int i = 0; i < row; i++) { - for (int j =0; j < triangle.get(i).size(); j++) { - dp[i][j] = triangle.get(i).get(j); - } - } - // 从下往上, 初始化最后一行 - for (int i = 0; i < triangle.get(row - 1).size(); i++) { - dp[row - 1][i] = triangle.get(row - 1).get(i); - } - // 动态规划 - for (int i = row - 2; i >= 0; i--) { - for (int j = 0; j < triangle.get(i).size(); j++) { - dp[i][j] = Math.min(dp[i+1][j], dp[i+1][j+1]) + triangle.get(i).get(j); - } - } - return dp[0][0]; - } -} -``` - -## 144. 二叉树的前序遍历(510) -[https://leetcode-cn.com/problems/binary-tree-preorder-traversal/](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) - -```html -输入: [1,null,2,3] - 1 - \ - 2 - / - 3 - -输出: [1,2,3] -``` - -```java -class Solution { - public List preorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - if (node == null) continue; - ret.add(node.val); - stack.push(node.right); - stack.push(node.left); - } - return ret; - } -} -``` - -## 145. 二叉树的后序遍历(495) -[https://leetcode-cn.com/problems/binary-tree-postorder-traversal/](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) - -```html -输入: [1,null,2,3] - 1 - \ - 2 - / - 3 - -输出: [3,2,1] -``` - -```java -class Solution { - public List postorderTraversal(TreeNode root) { - List ret = new ArrayList<>(); - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - if (node == null) continue; - ret.add(node.val); - stack.push(node.left); - stack.push(node.right); - } - Collections.reverse(ret); - return ret; - } -} -``` - -## 152. 乘积最大子数组(541) -[https://leetcode-cn.com/problems/maximum-product-subarray/](https://leetcode-cn.com/problems/maximum-product-subarray/) - -```html -给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 -``` - -```html -输入: [2,3,-2,4] -输出: 6 -解释: 子数组 [2,3] 有最大乘积 6。 - -输入: [-2,0,-1] -输出: 0 -解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 -``` -dp -```java -class Solution { - public int maxProduct(int[] nums) { - if (nums.length == 0) return 0; - int ans = Integer.MIN_VALUE; - int[] dpMax = new int[nums.length + 1]; - int[] dpMin = new int[nums.length + 1]; - dpMax[0] = 1; - dpMin[0] = 1; - for (int i = 1; i <= nums.length; i++) { - if (nums[i-1] < 0) { - int temp = dpMax[i-1]; - dpMax[i-1] = dpMin[i-1]; - dpMin[i-1] = temp; - } - dpMax[i] = Math.max(dpMax[i-1]*nums[i-1], nums[i-1]); - dpMin[i] = Math.min(dpMin[i-1]*nums[i-1], nums[i-1]); - ans = Math.max(ans, dpMax[i]); - } - return ans; - } -} -``` - -## 167. 两数之和 II - 输入有序数组(559) -[https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/) - -```html - -给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 - -函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 - -说明: - -返回的下标值(index1 和 index2)不是从零开始的。 -你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 - -``` - -```html -输入: numbers = [2, 7, 11, 15], target = 9 -输出: [1,2] -解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 -``` - -```java -class Solution { - public int[] twoSum(int[] numbers, int target) { - if (numbers == null) return null; - // 双指针 - int p1 = 0, p2 = numbers.length - 1; - while (p1 < p2) { - int sum = numbers[p1] + numbers[p2]; - if (sum == target) return new int[]{p1+1, p2+1}; - else if (sum < target) p1++; - else p2--; - } - return null; - } -} -``` - -## 189. 旋转数组(517) -[https://leetcode-cn.com/problems/rotate-array/](https://leetcode-cn.com/problems/rotate-array/) - -给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 - -```html -输入: [1,2,3,4,5,6,7] 和 k = 3 -输出: [5,6,7,1,2,3,4] -解释: -向右旋转 1 步: [7,1,2,3,4,5,6] -向右旋转 2 步: [6,7,1,2,3,4,5] -向右旋转 3 步: [5,6,7,1,2,3,4] - -输入: [-1,-100,3,99] 和 k = 2 -输出: [3,99,-1,-100] -解释: -向右旋转 1 步: [99,-1,-100,3] -向右旋转 2 步: [3,99,-1,-100] - -``` - -```java -class Solution { - public void rotate(int[] nums, int k) { - int n = nums.length; - k %= n; - reverse(nums, 0, n - 1); - reverse(nums, 0, k - 1); - reverse(nums, k, n - 1); - } - private void reverse(int[] nums, int start, int end) { - while(start < end) { - int temp = nums[start]; - nums[start++] = nums[end]; - nums[end--] = temp; - } - } -} -``` - -## 226. 翻转二叉树(582) -[https://leetcode-cn.com/problems/invert-binary-tree/](https://leetcode-cn.com/problems/invert-binary-tree/) - -```html - 4 - / \ - 2 7 - / \ / \ -1 3 6 9 - - 4 - / \ - 7 2 - / \ / \ -9 6 3 1 -``` - -```java -class Solution { - public TreeNode invertTree(TreeNode root) { - if (root == null) return null; - TreeNode left = root.left; - root.left = invertTree(root.right); - root.right = invertTree(left); - return root; - } -} -``` - -## 242. 有效的字母异位词(513) -[https://leetcode-cn.com/problems/valid-anagram/](https://leetcode-cn.com/problems/valid-anagram/) - -```html -给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 -``` - -```html -输入: s = "anagram", t = "nagaram" -输出: true - -输入: s = "rat", t = "car" -输出: false -``` - -```java -class Solution { - public boolean isAnagram(String s, String t) { - int[] cnts = new int[26]; - for (char c : s.toCharArray()) { - cnts[c - 'a']++; - } - for (char c : t.toCharArray()) { - cnts[c - 'a']--; - } - for (int c : cnts) { - if (c != 0) return false; - } - return true; - } -} -``` - -## 287. 寻找重复数(515) -[https://leetcode-cn.com/problems/find-the-duplicate-number/](https://leetcode-cn.com/problems/find-the-duplicate-number/) - -```html -给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 - -输入: [1,3,4,2,2] -输出: 2 - -输入: [3,1,3,4,2] -输出: 3 - -``` - -快慢指针 -```java -class Solution { - public int findDuplicate(int[] nums) { - int slow = nums[0], fast = nums[nums[0]]; - while (slow != fast) { - slow = nums[slow]; - fast = nums[nums[fast]]; - } - fast = 0; - while (slow != fast) { - slow = nums[slow]; - fast = nums[fast]; - } - return slow; - } -} -``` - -## 392. 判断子序列(509) -[https://leetcode-cn.com/problems/is-subsequence/](https://leetcode-cn.com/problems/is-subsequence/) - -```html -给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 - -你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。 - -字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 - -s = "abc", t = "ahbgdc" - -返回 true. - -s = "axc", t = "ahbgdc" - -返回 false. - -``` -```java -class Solution { - public boolean isSubsequence(String s, String t) { - // 这里用到了String到indexof - int inx = -1; - for (char c : s.toCharArray()) { - inx = t.indexOf(c, inx + 1); - if (inx == -1) return false; - } - return true; - } -} -``` - -## 445. 两数相加 II(562) -[https://leetcode-cn.com/problems/add-two-numbers-ii/](https://leetcode-cn.com/problems/add-two-numbers-ii/) - -```html -给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 - -你可以假设除了数字 0 之外,这两个数字都不会以零开头。 - -输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) -输出:7 -> 8 -> 0 -> 7 - -``` - -双栈 -```java -class Solution { - public ListNode addTwoNumbers(ListNode l1, ListNode l2) { - Stack l1Stack = buildStack(l1); - Stack l2Stack = buildStack(l2); - ListNode head = new ListNode(-1); - int carray = 0; - while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carray != 0) { - int x = l1Stack.isEmpty() ? 0 : l1Stack.pop(); - int y = l2Stack.isEmpty() ? 0 : l2Stack.pop(); - int sum = x + y + carray; - ListNode node = new ListNode(sum % 10); - node.next = head.next; - head.next = node; - carray = sum / 10; - } - return head.next; - } - - private Stack buildStack(ListNode l) { - Stack stack = new Stack<>(); - while (l != null) { - stack.push(l.val); - l = l.next; - } - return stack; - } -} -``` - -## 836. 矩形重叠(570) -[https://leetcode-cn.com/problems/rectangle-overlap/](https://leetcode-cn.com/problems/rectangle-overlap/) - -```html -矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。 - -如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 - -给出两个矩形,判断它们是否重叠并返回结果。 - -矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。 - -如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 - -给出两个矩形,判断它们是否重叠并返回结果。 - -输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] -输出:true - -输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] -输出:false -``` - -```java -class Solution { - public boolean isRectangleOverlap(int[] rec1, int[] rec2) { - if (rec2[1] >= rec1[3] || rec1[1] >= rec2[3]) { - return false; - } - if (rec1[0] >= rec2[2] || rec1[2] <= rec2[0]) { - return false; - } - return true; - } -} -``` -## 914. 卡牌分组(506) -[https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/](https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/) - -```html -给定一副牌,每张牌上都写着一个整数。 - -此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组: - -每组都有 X 张牌。 -组内所有的牌上都写着相同的整数。 -仅当你可选的 X >= 2 时返回 true。 - -输入:[1,2,3,4,4,3,2,1] -输出:true -解释:可行的分组是 [1,1],[2,2],[3,3],[4,4] - -输入:[1,1,1,2,2,2,3,3] -输出:false -解释:没有满足要求的分组。 - -``` -```java -class Solution { - public boolean hasGroupsSizeX(int[] deck) { - // hash - HashMap map = new HashMap<>(); - for(int num : deck) { - if(map.containsKey(num)) { - map.put(num, map.get(num) + 1); - } else { - map.put(num, 1); - } - } - // 最大公约数 - int t = 0; - for(int a : map.values()) { - t = gcd(t, a); - } - return t >= 2; - } - - // 最大公约数 - private int gcd(int a, int b) { - return b == 0 ? a : gcd(b, a % b); - } -} -``` - -## 1071. 字符串的最大公因子(583) -[https://leetcode-cn.com/problems/greatest-common-divisor-of-strings/](https://leetcode-cn.com/problems/greatest-common-divisor-of-strings/) - -```html -对于字符串 S 和 T,只有在 S = T + ... + T(T 与自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。 - -返回最长字符串 X,要求满足 X 能除尽 str1 且 X 能除尽 str2。 - -输入:str1 = "ABCABC", str2 = "ABC" -输出:"ABC" - -输入:str1 = "ABABAB", str2 = "ABAB" -输出:"AB" - -输入:str1 = "LEET", str2 = "CODE" -输出:"" -``` - -```java -class Solution { - public String gcdOfStrings(String str1, String str2) { - if (!(str1 + str2).equals(str2 + str1)) { - return ""; - } - return str2.substring(0, gcd(str1.length(), str2.length())); - } - - private int gcd(int a, int b) { - return b == 0 ? a : gcd(b, a % b); - } -} -``` - -### 31. 下一个排列(588) -```java - //源于离散数学及其应用的算法:(以3 4 5 2 1 为例) - //从后往前寻找第一次出现的正序对:(找到 4,5) - //之后因为从5 开始都是逆序,所以把他们反转就是正序:3 4 1 2 5 - //之后4 的位置应该是:在它之后的,比他大的最小值(5) - //交换这两个值:得到 3 5 1 2 4 - // 对于初始即为逆序的序列,将在反转步骤直接完成 -class Solution { - public void nextPermutation(int[] nums) { - int len = nums.length; - if (len < 2) return; - int i = len - 1; - while (i > 0 && nums[i - 1] >= nums[i]) - i--; // 从后向前找第一个正序,这里最后i指向的是逆序起始位置 - reverse(nums, i, len - 1); // 翻转后面的逆序区域,使其变为正序 - if (i == 0) return; - int j = i - 1; - while(i < len && nums[j] >= nums[i]) - i++; // 找到第一个比nums[j]大的元素,交换即可 - // 交换 - swap(nums, i, j); - } - private void reverse(int[] nums, int i, int j) { - while (i < j) { - swap(nums, i++, j--); - } - } - - private void swap(int[] nums, int i, int j){ - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; - } -} -``` - -## 40. 组合总和 II(401) -[https://leetcode-cn.com/problems/combination-sum-ii/](https://leetcode-cn.com/problems/combination-sum-ii/) -```html - -给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 - -candidates 中的每个数字在每个组合中只能使用一次。 - -说明: - -所有数字(包括目标数)都是正整数。 -解集不能包含重复的组合。 - -``` - -```html -输入: candidates = [10,1,2,7,6,1,5], target = 8, -所求解集为: -[ - [1, 7], - [1, 2, 5], - [2, 6], - [1, 1, 6] -] -``` -```java -class Solution { - public List> combinationSum2(int[] candidates, int target) { - List> combinations = new ArrayList<>(); - Arrays.sort(candidates); - backtracking(new ArrayList<>(), combinations, new boolean[candidates.length], 0, target, candidates); - return combinations; - } - - private void backtracking(List tempCombination, List> combinations, - boolean[] hasVisited, int start, int target, final int[] candidates) { - - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); - return; - } - for (int i = start; i < candidates.length; i++) { - if (i != 0 && candidates[i] == candidates[i - 1] && !hasVisited[i - 1]) { - continue; - } - if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - hasVisited[i] = true; - backtracking(tempCombination, combinations, hasVisited, i + 1, target - candidates[i], candidates); - hasVisited[i] = false; - tempCombination.remove(tempCombination.size() - 1); - } - } - } - -} -``` - -## 47. 全排列 II(429) -[https://leetcode-cn.com/problems/permutations-ii/](https://leetcode-cn.com/problems/permutations-ii/) - -```html -给定一个可包含重复数字的序列,返回所有不重复的全排列。 -``` - -```html -输入: [1,1,2] -输出: -[ - [1,1,2], - [1,2,1], - [2,1,1] -] -``` - -```java -class Solution { - public List> permuteUnique(int[] nums) { - List> permutes = new ArrayList<>(); - List permuteList = new ArrayList<>(); - Arrays.sort(nums); // 排序 - boolean[] hasVisited = new boolean[nums.length]; - backtracking(permuteList, permutes, hasVisited, nums); - return permutes; - } - private void backtracking(List permuteList, List> permutes, boolean[] visited, final int[] nums) { - if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); - return; - } - for (int i = 0; i < visited.length; i++) { - if (i != 0 && nums[i] == nums[i -1] && !visited[i - 1]) { - continue; // 防止重复 - } - if (visited[i]) continue; - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - permuteList.remove(permuteList.size() - 1); - visited[i] = false; - } - } -} -``` - -## 72. 编辑距离(496) -```html -给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 - -你可以对一个单词进行如下三种操作: - -插入一个字符 -删除一个字符 -替换一个字符 - -输入:word1 = "horse", word2 = "ros" -输出:3 -解释: -horse -> rorse (将 'h' 替换为 'r') -rorse -> rose (删除 'r') -rose -> ros (删除 'e') - -``` - -```java -class Solution { - public int minDistance(String word1, String word2) { - if (word1 == null || word2 == null) { - return 0; - } - int m = word1.length(), n = word2.length(); - int[][] dp = new int[m + 1][n + 1]; - for (int i = 1; i <= m; i++) { - dp[i][0] = i; - } - for (int i = 1; i <= n; i++) { - dp[0][i] = i; - } - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= n; j++) { - if (word1.charAt(i - 1) == word2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1; - } - } - } - return dp[m][n]; - } - -} -``` - -## 79. 单词搜索(420) -[https://leetcode-cn.com/problems/word-search/](https://leetcode-cn.com/problems/word-search/) - -```html -给定一个二维网格和一个单词,找出该单词是否存在于网格中。 - -单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 - -board = -[ - ['A','B','C','E'], - ['S','F','C','S'], - ['A','D','E','E'] -] - -给定 word = "ABCCED", 返回 true -给定 word = "SEE", 返回 true -给定 word = "ABCB", 返回 false - -``` - -```java -class Solution { - private final static int[][] direction = {{1,0},{-1,0},{0,1},{0,-1}}; - private int m; - private int n; - public boolean exist(char[][] board, String word) { - if (word == null || word.length() == 0) return true; - if (board == null || board.length == 0 || board[0].length == 0) return false; - m = board.length; - n = board[0].length; - boolean[][] hasVisited = new boolean[m][n]; - for (int r = 0; r < m; r++) { - for (int c = 0; c < n; c++) { - if (backtracking(0, r, c, hasVisited, board, word)) { - return true; - } - } - } - return false; - } - private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) { - if (curLen == word.length()) return true; - if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(curLen) || visited[r][c]) return false; - visited[r][c] = true; - for (int[] d : direction) { - if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) return true; - } - visited[r][c] = false; - return false; - } -} -``` - -## 91. 解码方法(462) -[https://leetcode-cn.com/problems/decode-ways/](https://leetcode-cn.com/problems/decode-ways/) -```html -一条包含字母 A-Z 的消息通过以下方式进行了编码: -'A' -> 1 -'B' -> 2 -... -'Z' -> 26 - -输入: "12" -输出: 2 -解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 - -输入: "226" -输出: 3 -解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 -``` -dp -```java -class Solution { - public int numDecodings(String s) { - if (s == null || s.length() == 0) { - return 0; - } - int n = s.length(); - int[] dp = new int[n + 1]; - dp[0] = 1; - dp[1] = s.charAt(0) == '0' ? 0 : 1; - for (int i = 2; i <= n; i++) { - int one = Integer.valueOf(s.substring(i - 1, i)); - if (one != 0) { - dp[i] += dp[i - 1]; - } - if (s.charAt(i - 2) == '0') continue; - int two = Integer.valueOf(s.substring(i - 2, i)); - if (two <= 26) { - dp[i] += dp[i - 2]; - } - } - return dp[n]; - } -} -``` - -## 110. 平衡二叉树(485) -[https://leetcode-cn.com/problems/balanced-binary-tree/](https://leetcode-cn.com/problems/balanced-binary-tree/) -```html -给定一个二叉树,判断它是否是高度平衡的二叉树。 - -本题中,一棵高度平衡二叉树定义为: - -一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 - 3 - / \ - 9 20 - / \ - 15 7 - - true -``` - -```java -lass Solution { - private boolean res = true; - public boolean isBalanced(TreeNode root) { - Depth(root); - return res; - } - - private int Depth (TreeNode root) { - if (root == null) return 0; - int l = Depth(root.left); - int r = Depth(root.right); - if (Math.abs(l - r) > 1) res = false;; - return 1 + Math.max(l , r); - } -} -``` - -## 139. 单词拆分(475) -[https://leetcode-cn.com/problems/word-break/](https://leetcode-cn.com/problems/word-break/) -```html -给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 - -说明: - -拆分时可以重复使用字典中的单词。 -你可以假设字典中没有重复的单词。 - -输入: s = "leetcode", wordDict = ["leet", "code"] -输出: true -解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。 - -输入: s = "applepenapple", wordDict = ["apple", "pen"] -输出: true -解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。 -  注意你可以重复使用字典中的单词。 - -来输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] -输出: false -``` -```java -class Solution { - public boolean wordBreak(String s, List wordDict) { - int n = s.length(); - boolean[] dp = new boolean[n + 1]; - dp[0] = true; - for (int i = 1; i <= n; i++) { - for (String word: wordDict) { - // 对物品的迭代应该放在最里层 - int len = word.length(); - if (len <= i && word.equals(s.substring(i - len , i))) { - dp[i] = dp[i] || dp[i - len]; - } - } - } - return dp[n]; - } -} -``` - -## 217. 存在重复元素(471) -[https://leetcode-cn.com/problems/contains-duplicate/](https://leetcode-cn.com/problems/contains-duplicate/) - -```html -给定一个整数数组,判断是否存在重复元素。 - -如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。 - -输入: [1,2,3,1] -输出: true - -``` - -```java -class Solution { - public boolean containsDuplicate(int[] nums) { - HashSet set = new HashSet<>(); - for (int num : nums) { - if (!set.add(num)) { - return true; - } - } - return false; - } -} -``` - -## 237. 删除链表中的节点(423) -[https://leetcode-cn.com/problems/delete-node-in-a-linked-list/](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) - -```html -输入: head = [4,5,1,9], node = 5 -输出: [4,1,9] -解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. - -``` -```java -class Solution { - public void deleteNode(ListNode node) { - node.val = node.next.val; - node.next = node.next.next; - } -} -``` - -## 238. 除自身以外数组的乘积(467) -[https://leetcode-cn.com/problems/product-of-array-except-self/](https://leetcode-cn.com/problems/product-of-array-except-self/) - -```html - -给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 -输入: [1,2,3,4] -输出: [24,12,8,6] -``` - -```java -class Solution { - public int[] productExceptSelf(int[] nums) { - int n = nums.length; - int[] products = new int[n]; - Arrays.fill(products, 1); - int left = 1; - for (int i = 1; i < n; i++) { - left *= nums[i - 1]; - products[i] *= left; - } - int right = 1; - for (int i = n - 2; i >= 0; i--) { - right *= nums[i + 1]; - products[i] *= right; - } - return products; - } -} -``` - -## 350. 两个数组的交集 II(402) -[https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) - -给定两个数组,编写一个函数来计算它们的交集。 - -```html -输入: nums1 = [1,2,2,1], nums2 = [2,2] -输出: [2,2] - -输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] -输出: [4,9] - -``` - -```java -class Solution { - public int[] intersect(int[] nums1, int[] nums2) { - ArrayList list1 = new ArrayList<>(); - for (int num : nums1) { - list1.add(num); - } - ArrayList list2 = new ArrayList<>(); - for (int num : nums2) { - if (list1.contains(num)) { - list2.add(num); - list1.remove(num); - } - } - return list2.stream().mapToInt(Integer::valueOf).toArray(); - } -} -``` - -## 461. 汉明距离(411) -[https://leetcode-cn.com/problems/hamming-distance/](https://leetcode-cn.com/problems/hamming-distance/) - -两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。 - -给出两个整数 x 和 y,计算它们之间的汉明距离。 - -```html -输入: x = 1, y = 4 - -输出: 2 - -解释: -1 (0 0 0 1) -4 (0 1 0 0) - ↑ ↑ - -上面的箭头指出了对应二进制位不同的位置。 -``` - -```java -class Solution { - public int hammingDistance(int x, int y) { - int z = x ^ y; - int cnt = 0; - while (z != 0) { - if ((z & 1) == 1) cnt++; - z = z >> 1; - } - return cnt; - } -} -``` - -## 572. 另一个树的子树(426) -[https://leetcode-cn.com/problems/subtree-of-another-tree/](https://leetcode-cn.com/problems/subtree-of-another-tree/) - -```html -给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 - 3 - / \ - 4 5 - / \ - 1 2 - - 4 - / \ - 1 2 - -``` -```java - class Solution { - public boolean isSubtree(TreeNode s, TreeNode t) { - if (s == null) return false; - return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); - } - - private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) { - if (t == null && s == null) return true; - if (t == null || s == null) return false; - if (t.val != s.val) return false; - return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right); - } -} -``` - -## 680. 验证回文字符串 Ⅱ(474) -[https://leetcode-cn.com/problems/valid-palindrome-ii/](https://leetcode-cn.com/problems/valid-palindrome-ii/) -给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。 -```html -输入: "aba" -输出: True - -输入: "abca" -输出: True -解释: 你可以删除c字符。 -``` -```java -class Solution { - public boolean validPalindrome(String s) { - // 普通判断回文串用前后双指针即可,但是,难点在于如果去删除一个元素后的字符串是不是回文串 - // 如果前后指针的元素不相等,此时子串的范围(i+1,j)或(j-1)的俩子串只要任意一个是,则结果是 - // 否则,则不是 - int i =0, j = s.length() - 1; - while(i < j) { - if(s.charAt(i) != s.charAt(j)) { - return isVaild(s, i+1, j) || isVaild(s, i, j-1); - } - i++; - j--; - } - return true; - } - - public boolean isVaild(String s, int i, int j) { - while(i < j) { - if(s.charAt(i) != s.charAt(j)) { - return false; - } - i++; - j--; - } - return true; - } -} -``` - -## 146. LRU缓存机制(496) -[https://leetcode-cn.com/problems/lru-cache/](https://leetcode-cn.com/problems/lru-cache/) - -```java -class LRUCache { - private int cap; - private Map map = new LinkedHashMap<>(); - public LRUCache(int capacity) { - this.cap = capacity; - } - - public int get(int key) { - if (map.containsKey(key)) { - int value = map.get(key); - // 查一次,就将查到到仍在队尾 - map.remove(key); - map.put(key,value); - return value; - } - return -1; - } - - public void put(int key, int value) { - if (map.containsKey(key)) { - map.remove(key); - } else if (map.size() == cap) { - // 满了 - Iterator> iterator = map.entrySet().iterator(); - iterator.next(); - iterator.remove(); - } - map.put(key, value); - } -} - -``` - -## 90. 子集 II(304) -[https://leetcode-cn.com/problems/subsets-ii/](https://leetcode-cn.com/problems/subsets-ii/) - -```html -给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 - -说明:解集不能包含重复的子集。 - -输入: [1,2,2] -输出: -[ - [2], - [1], - [1,2,2], - [2,2], - [1,2], - [] -] -``` - -```java -class Solution { - public List> subsetsWithDup(int[] nums) { - Arrays.sort(nums); - List> subsets = new ArrayList<>(); - List tempSubset = new ArrayList<>(); - boolean[] hasVisited = new boolean[nums.length]; - for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小 - } - return subsets; - } - - private void backtracking(int start, List tempSubset, List> subsets, boolean[] hasVisited, - final int size, final int[] nums) { - - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); - return; - } - for (int i = start; i < nums.length; i++) { - if (i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]) { - continue; - } - tempSubset.add(nums[i]); - hasVisited[i] = true; - backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums); - hasVisited[i] = false; - tempSubset.remove(tempSubset.size() - 1); - } - } - -} -``` - -## 130. 被围绕的区域(328) -[https://leetcode-cn.com/problems/surrounded-regions/](https://leetcode-cn.com/problems/surrounded-regions/) - -给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。 - -找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。 - -```html -X X X X -X O O X -X X O X -X O X X - -X X X X -X X X X -X X X X -X O X X -``` - -```java -class Solution { - private int[][] direction = {{0,1},{0,-1},{1,0},{-1,0}}; - private int m, n; - public void solve(char[][] board) { - if (board == null || board.length == 0) return; - m = board.length; - n = board[0].length; - for (int i = 0; i < m; i++) { - dfs(board, i, 0); - dfs(board, i, n - 1); - } - for (int i = 0; i < n; i++) { - dfs(board, 0, i); - dfs(board, m - 1, i); - } - - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (board[i][j] == 'T') { - board[i][j] = 'O'; - } else if (board[i][j] == 'O') { - board[i][j] = 'X'; - } - } - } - } - - private void dfs(char[][] board, int r, int c) { - if(r<0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') { - return; - } - board[r][c] = 'T'; - for (int[] d : direction) { - dfs(board, r + d[0], c + d[1]); - } - } -} -``` - -## 153. 寻找旋转排序数组中的最小值(316) -[https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) - -```html -假设按照升序排序的数组在预先未知的某个点上进行了旋转。 - -( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 - -请找出其中最小的元素。 - -你可以假设数组中不存在重复元素。 - -输入: [3,4,5,1,2] -输出: 1 - -输入: [4,5,6,7,0,1,2] -输出: 0 -``` - -```java -class Solution { - public int findMin(int[] nums) { - int l = 0, h = nums.length - 1; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] <= nums[h]) { - h = m; - } else { - l = m + 1; - } - } - return nums[l]; - } -} -``` - -## 191. 位1的个数(324) -[https://leetcode-cn.com/problems/number-of-1-bits/](https://leetcode-cn.com/problems/number-of-1-bits/) -编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 -```html -输入:00000000000000000000000000001011 -输出:3 -解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 - -输入:00000000000000000000000010000000 -输出:1 -解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 -``` -```java -public class Solution { - // you need to treat n as an unsigned value - public int hammingWeight(int n) { - int ans = 0; - while(n != 0) { - n &= n - 1; - ans++; - } - return ans; - } -} -``` - -## 213. 打家劫舍 II(375) -[https://leetcode-cn.com/problems/house-robber-ii/](https://leetcode-cn.com/problems/house-robber-ii/) -```html -你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 - -给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 - -输入: [2,3,2] -输出: 3 -解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 - -输入: [1,2,3,1] -输出: 4 -解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 -  偷窃到的最高金额 = 1 + 3 = 4 。 - -``` -```java -class Solution { - public int rob(int[] nums) { - if (nums == null || nums.length == 0) return 0; - int n = nums.length; - if (n == 1) return nums[0]; - return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1)); - } - - private int rob(int[] nums, int first, int last) { - int pre2 = 0, pre1 = 0; - for (int i = first; i <= last; i++) { - int cur = Math.max(pre1, pre2 + nums[i]); - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - -## 219. 存在重复元素 II(340) -[https://leetcode-cn.com/problems/contains-duplicate-ii/](https://leetcode-cn.com/problems/contains-duplicate-ii/) - -```html -输入: nums = [1,2,3,1], k = 3 -输出: true - -输入: nums = [1,0,1,1], k = 1 -输出: true -``` - -```java -class Solution { - public boolean containsNearbyDuplicate(int[] nums, int k) { - HashSet set = new HashSet<>(); - for (int i = 0; i < nums.length; i++) { - if(set.contains(nums[i])) { - return true; - } - set.add(nums[i]); - if (set.size() > k) { - set.remove(nums[i - k]); - } - } - return false; - } -} -``` - -## 230. 二叉搜索树中第K小的元素(301) -[https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/) - -给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 - -说明: -你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 - -```html -输入: root = [3,1,4,null,2], k = 1 - 3 - / \ - 1 4 - \ - 2 -输出: 1 -``` - -```java -class Solution { - public int kthSmallest(TreeNode root, int k) { - int leftCnt = count(root.left); - if (leftCnt == k - 1) return root.val; - if (leftCnt > k - 1) return kthSmallest(root.left, k); - return kthSmallest(root.right, k - leftCnt - 1); - } - - private int count (TreeNode node) { - if (node == null) return 0; - return 1 + count(node.left) + count(node.right); - } -} -``` - -## 231. 2的幂(359) -[https://leetcode-cn.com/problems/power-of-two/](https://leetcode-cn.com/problems/power-of-two/) - -```html -输入: 1 -输出: true -解释: 2的2次方 = 1 - -输入: 16 -输出: true -解释: 2的四次方 = 16 - -输入: 218 -输出: false -``` -```java -class Solution { - public boolean isPowerOfTwo(int n) { - return n > 0 && Integer.bitCount(n) == 1; - } -} -``` - -## 232. 用栈实现队列(379) -[https://leetcode-cn.com/problems/implement-queue-using-stacks/](https://leetcode-cn.com/problems/implement-queue-using-stacks/) - -```java -class MyQueue { - - private Stack in; - private Stack out; - - /** Initialize your data structure here. */ - public MyQueue() { - in = new Stack<>(); - out = new Stack<>(); - } - - /** Push element x to the back of queue. */ - public void push(int x) { - in.push(x); - } - - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - in2Out(); - return out.pop(); - } - - /** Get the front element. */ - public int peek() { - in2Out(); - return out.peek(); - } - - private void in2Out() { - if (out.isEmpty()) { - while (!in.isEmpty()) { - out.push(in.pop()); - } - } - } - - /** Returns whether the queue is empty. */ - public boolean empty() { - return in.isEmpty() && out.isEmpty(); - } -} -``` - -## 268. 缺失数字(380) -[https://leetcode-cn.com/problems/missing-number/](https://leetcode-cn.com/problems/missing-number/) - -给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。 - -```html -输入: [3,0,1] -输出: 2 - -输入: [9,6,4,2,3,5,7,0,1] -输出: 8 -``` -```java -class Solution { - public int missingNumber(int[] nums) { - int ret = 0; - for (int i = 0; i < nums.length; i++) { - ret = ret ^ i ^ nums[i]; - } - return ret ^ nums.length; - } -} -``` - -## 279. 完全平方数(375) -[https://leetcode-cn.com/problems/perfect-squares/](https://leetcode-cn.com/problems/perfect-squares/) - -给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 - -```html -输入: n = 12 -输出: 3 -解释: 12 = 4 + 4 + 4. - -输入: n = 13 -输出: 2 -解释: 13 = 4 + 9. -``` -```java -class Solution { - public int numSquares(int n) { - List squareList = generateSquareList(n); - int[] dp = new int[n + 1]; - for (int i = 1; i <= n; i++) { - int min = Integer.MAX_VALUE; - for (int square : squareList) { - if (square > i) break; - min = Math.min(min, dp[i - square] + 1); - } - dp[i] = min; - } - return dp[n]; - } - private List generateSquareList(int n) { - List squareList = new ArrayList<>(); - int diff = 3; - int square = 1; - while (square <= n) { - squareList.add(square); - square += diff; - diff += 2; - } - return squareList; - } -} -``` - -## 328. 奇偶链表(317) -[https://leetcode-cn.com/problems/odd-even-linked-list/](https://leetcode-cn.com/problems/odd-even-linked-list/) - -```html - -给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 - -请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 -输入: 1->2->3->4->5->NULL -输出: 1->3->5->2->4->NULL -``` - -```java -class Solution { - public ListNode oddEvenList(ListNode head) { - if (head == null) return head; - ListNode odd = head, even = head.next, evenHead = even; - while (even != null && even.next != null) { - odd.next = odd.next.next; - odd = odd.next; - even.next = even.next.next; - even = even.next; - } - odd.next = evenHead; - return head; - } -} -``` - -## 378. 有序矩阵中第K小的元素(337) -[https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/) - -```html -给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 -请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。 - -matrix = [ - [ 1, 5, 9], - [10, 11, 13], - [12, 13, 15] -], -k = 8, - -返回 13。 -``` - -```java -class Solution { - public int kthSmallest(int[][] matrix, int k) { - int m = matrix.length, n = matrix[0].length; - int lo = matrix[0][0], hi = matrix[m - 1][n - 1]; - while (lo <= hi) { - int mid = lo + (hi - lo) / 2; - int cnt = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n && matrix[i][j] <= mid; j++) { - cnt++; - } - } - if (cnt < k) lo = mid + 1; - else hi = mid - 1; - } - return lo; - } -} -``` - -## 387. 字符串中的第一个唯一字符(340) -[https://leetcode-cn.com/problems/first-unique-character-in-a-string/](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) -```java -class Solution { - public int firstUniqChar(String s) { - HashMap map = new HashMap<>(); - for (char c : s.toCharArray()){ - map.put(c, map.getOrDefault(c, 0) + 1); - } - for (int i = 0; i < s.length(); i++) { - if(map.get(s.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} -``` - -## 415. 字符串相加(321) -[https://leetcode-cn.com/problems/add-strings/](https://leetcode-cn.com/problems/add-strings/) - -```java -class Solution { - public String addStrings(String num1, String num2) { - StringBuilder str = new StringBuilder(); - int carry = 0, i = num1.length() - 1, j = num2.length() - 1; - while (carry == 1 || i >= 0 || j >= 0) { - int x = i < 0 ? 0 : num1.charAt(i--) - '0'; - int y = j < 0 ? 0 : num2.charAt(j--) - '0'; - str.append((x + y + carry) % 10); - carry = (x + y + carry) / 10; - } - return str.reverse().toString(); - } -} -``` -## 617. 合并二叉树(351) -[https://leetcode-cn.com/problems/merge-two-binary-trees/](https://leetcode-cn.com/problems/merge-two-binary-trees/) - -```java -class Solution { - public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) return null; - if (t1 == null) return t2; - if (t2 == null) return t1; - TreeNode root = new TreeNode(t1.val + t2.val); - root.left = mergeTrees(t1.left, t2.left); - root.right = mergeTrees(t1.right, t2.right); - return root; - } -} -``` - -## 86. 分隔链表(367) -[https://leetcode-cn.com/problems/partition-list/](https://leetcode-cn.com/problems/partition-list/) - -```java -class Solution { - public ListNode partition(ListNode head, int x) { - ListNode dummy1 = new ListNode(0); - ListNode dummy2 = new ListNode(0); - ListNode node1 = dummy1, node2 = dummy2; - while (head != null){ - if (head.val < x){ - node1.next = head; - head = head.next; - node1 = node1.next; - node1.next = null; - } else { - node2.next = head; - head = head.next; - node2 = node2.next; - node2.next = null; - } - } - node1.next = dummy2.next; - return dummy1.next; - } -} -``` \ No newline at end of file diff --git "a/Interview/mysql/MySQL\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Interview/mysql/MySQL\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 931cc3a7..00000000 --- "a/Interview/mysql/MySQL\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,16 +0,0 @@ -- [数据库引擎innodb与myisam的区别?](/Interview/mysql/sql-存储引擎.md) -- [Mysql的ACID原理](/Interview/mysql/Mysql中ACID的原理.md) -- [并发事务带来的问题](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E5%B9%B6%E5%8F%91%E4%BA%8B%E5%8A%A1%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%97%AE%E9%A2%98) -- [数据库的隔离级别](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB) -- [为什么使用索引?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95) -- [索引这么多优点,为什么不对表中的每一个列创建一个索引呢?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E7%B4%A2%E5%BC%95%E8%BF%99%E4%B9%88%E5%A4%9A%E4%BC%98%E7%82%B9%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%AF%B9%E8%A1%A8%E4%B8%AD%E7%9A%84%E6%AF%8F%E4%B8%80%E4%B8%AA%E5%88%97%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%B4%A2%E5%BC%95%E5%91%A2) -- [索引如如何提高查询速度的?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E7%B4%A2%E5%BC%95%E5%A6%82%E5%A6%82%E4%BD%95%E6%8F%90%E9%AB%98%E6%9F%A5%E8%AF%A2%E9%80%9F%E5%BA%A6%E7%9A%84) -- [使用索引的注意事项?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9) -- [Mysql索引主要使用的两种数据结构](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#mysql%E7%B4%A2%E5%BC%95%E4%B8%BB%E8%A6%81%E4%BD%BF%E7%94%A8%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) -- [MyISAM和InnoDB实现BTree索引方式的区别](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#myisam%E5%92%8Cinnodb%E5%AE%9E%E7%8E%B0btree%E7%B4%A2%E5%BC%95%E6%96%B9%E5%BC%8F%E7%9A%84%E5%8C%BA%E5%88%AB) -- [数据库结构优化](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%BC%98%E5%8C%96) -- [主键 超键 候选键 外键是什么](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E4%B8%BB%E9%94%AE-%E8%B6%85%E9%94%AE-%E5%80%99%E9%80%89%E9%94%AE-%E5%A4%96%E9%94%AE%E6%98%AF%E4%BB%80%E4%B9%88) -- [drop,delete与truncate的区别](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#dropdelete%E4%B8%8Etruncate%E7%9A%84%E5%8C%BA%E5%88%AB) -- [视图的作用,视图可以更改么?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E8%A7%86%E5%9B%BE%E7%9A%84%E4%BD%9C%E7%94%A8%E8%A7%86%E5%9B%BE%E5%8F%AF%E4%BB%A5%E6%9B%B4%E6%94%B9%E4%B9%88) -- [数据库范式](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8C%83%E5%BC%8F) -- [什么是覆盖索引?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/mysql/Mysql-%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E9%97%AE%E9%A2%98.md#%E4%BB%80%E4%B9%88%E6%98%AF%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95) \ No newline at end of file diff --git "a/Interview/mysql/Mysql-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" "b/Interview/mysql/Mysql-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 614a3857..00000000 --- "a/Interview/mysql/Mysql-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,209 +0,0 @@ -## 引言 - -> Mysql,那可是老生常谈了,对于后端同学,那是必须要掌握的呀。 - -## 常见问题 - -### 数据库引擎innodb与myisam的区别? - -[InnoDB和MyISAM比较](http://dreamcat.ink/2019/11/15/sql-notes/1/) - -### 4大特性 - -[Mysql的ACID原理](http://dreamcat.ink/2019/11/20/sql-notes/1/) - -### 并发事务带来的问题 - -#### 脏读 - -![](https://www.pdai.tech/_images/pics/dd782132-d830-4c55-9884-cfac0a541b8e.png) - -#### 丢弃修改 - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最 终结果A=19,事务1的修改被丢失。 - -![](https://www.pdai.tech/_images/pics/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png) - -### 不可重复读 - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -![](https://www.pdai.tech/_images/pics/c8d18ca9-0b09-441a-9a0c-fb063630d708.png) - -#### 幻读 - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 - -![](https://www.pdai.tech/_images/pics/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png) - -#### 不可重复度和幻读区别: - -不可重复读的重点是修改,幻读的重点在于新增或者删除。 - -例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操 作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。 - -例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所 有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记 录就变为了5条,这样就导致了幻读。 - -### 数据库的隔离级别 - -1. 未提交读,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** -2. 提交读,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -3. 可重复读,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生。** -4. 可串行化读,在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读。** - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| ---------------- | ---- | ---------- | ------ | -| READ-UNCOMMITTED | √ | √ | √ | -| READ-COMMITTED | × | √ | √ | -| REPEATABLE-READ | × | × | √ | -| SERIALIZABLE | × | × | × | - -MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过 - -命令来查看。我们可以通过`SELECT@@tx_isolation;` - -这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。 - -InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 - -### 为什么使用索引? - -- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 -- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 -- 帮助服务器避免排序和临时表 -- 将随机IO变为顺序IO -- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 - -### 索引这么多优点,为什么不对表中的每一个列创建一个索引呢? - -- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 -- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 -- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加 - -### 索引如如何提高查询速度的? - -- 将无序的数据变成相对有序的数据(就像查目一样) - -### 使用索引的注意事项? - -- 在经常需要搜索的列上,可以加快搜索的速度; -- 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 -- 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间 -- 在中到大型表索引都是非常有效的,但是特大型表的维护开销会很大,不适合建索引 -- 在经常用到连续的列上,这些列主要是由一些外键,可以加快连接的速度 -- 避免where子句中对字段施加函数,这会造成无法命中索引 -- 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 -- **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** -- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 -- 在使用limit offset查询缓存时,可以借助索引来提高性能。 - -### Mysql索引主要使用的两种数据结构 - -- 哈希索引,对于哈希索引来说,底层的数据结构肯定是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引 -- BTree索引,Mysql的BTree索引使用的是B树中的B+Tree但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。 - -### MyISAM和InnoDB实现BTree索引方式的区别 - -- MyISAM,B+Tree叶节点的data域存放的是数据记录的地址,在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址读区相应的数据记录,这被称为“非聚簇索引” -- InnoDB,其数据文件本身就是索引文件,相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的节点data域保存了完整的数据记录,这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引”或者聚集索引,而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方,在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。因此,在设计表的时候,不建议使用过长的字段为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 - -### 数据库结构优化 - -- 范式优化: 比如消除冗余(节省空间。。) -- 反范式优化:比如适当加冗余等(减少join) -- 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时 候,我们可以控制在一个月的范围内。 -- 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读; -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 -- 拆分其实又分垂直拆分和水平拆分: - - 案例: 简单购物系统暂设涉及如下表: - - 1.产品表(数据量10w,稳定) - - 2.订单表(数据量200w,且有增长趋势) - - 3.用户表 (数据量100w,且有增长趋势) - - 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 - - **垂直拆分:** - - 解决问题:表与表之间的io竞争 - - 不解决问题:单表中数据量增长出现的压力 - - 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - - **水平拆分:** - - 解决问题:单表中数据量增长出现的压力 - - 不解决问题:表与表之间的io争夺 - - 方案:**用户表** 通过性别拆分为男用户表和女用户表,**订单表** 通过已完成和完成中拆分为已完成订单和未完成订单,**产品表** 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。 - -### 主键 超键 候选键 外键是什么 - -#### 定义 - -超键:在关系中能唯一标识元组的属性集称为关系模式的超键 - -候选键:不含有多余属性的超键称为候选键。也就是在候选键中,若再删除属性,就不是键了! - -主键:用户选作元组标识的一个候选键程序主键 - -外键:如果关系模式R中属性K是其它模式的主键,那么k在模式R中称为外键。 - -#### 举例 - -| 学号 | 姓名 | 性别 | 年龄 | 系别 | 专业 | -| -------- | ------ | ---- | ---- | ------ | -------- | -| 20020612 | 李辉 | 男 | 20 | 计算机 | 软件开发 | -| 20060613 | 张明 | 男 | 18 | 计算机 | 软件开发 | -| 20060614 | 王小玉 | 女 | 19 | 物理 | 力学 | -| 20060615 | 李淑华 | 女 | 17 | 生物 | 动物学 | -| 20060616 | 赵静 | 男 | 21 | 化学 | 食品化学 | -| 20060617 | 赵静 | 女 | 20 | 生物 | 植物学 | - -1. 超键:于是我们从例子中可以发现 学号是标识学生实体的唯一标识。那么该元组的超键就为学号。除此之外我们还可以把它跟其他属性组合起来,比如:(`学号`,`性别`),(`学号`,`年龄`) -2. 候选键:根据例子可知,学号是一个可以唯一标识元组的唯一标识,因此学号是一个候选键,实际上,候选键是超键的子集,比如 (学号,年龄)是超键,但是它不是候选键。因为它还有了额外的属性。 -3. 主键:简单的说,例子中的元组的候选键为学号,但是我们选定他作为该元组的唯一标识,那么学号就为主键。 -4. 外键是相对于主键的,比如在学生记录里,主键为学号,在成绩单表中也有学号字段,因此学号为成绩单表的外键,为学生表的主键。 - -#### 总结 - -**主键为候选键的子集,候选键为超键的子集,而外键的确定是相对于主键的。** - -### drop,delete与truncate的区别 - -drop直接删掉表;truncate删除表中数据,再插入时自增长id又从1开始 ;delete删除表中数据,可以加where字句。 - -1. DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。 -2. 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。 -3. 一般而言,drop > truncate > delete -4. 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view -5. TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。 -6. truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。 -7. delete语句为DML(Data Manipulation Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。 -8. truncate、drop是DDL(Data Define Language),操作立即生效,原数据不放到 rollback segment中,不能回滚 -9. 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老是想触发trigger,还是用delete。 -10. Truncate table 表名 速度快,而且效率高,因为: truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。 -11. TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。 -12. 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。 - -### 视图的作用,视图可以更改么? - -视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。 - -视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。 - -创建视图:`create view xxx as xxxx` - -对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。 - -### 数据库范式 - -#### 第一范式 - -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -#### 第二范式 - -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,**第二范式就是非主属性非部分依赖于主关键字**。 - -#### 第三范式 - -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 什么是覆盖索引? - -如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称 之为“覆盖索引”。我们知道在InnoDB存储引 擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就 会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! \ No newline at end of file diff --git "a/Interview/mysql/Mysql\344\270\255ACID\347\232\204\345\216\237\347\220\206.md" "b/Interview/mysql/Mysql\344\270\255ACID\347\232\204\345\216\237\347\220\206.md" deleted file mode 100644 index 80f378e8..00000000 --- "a/Interview/mysql/Mysql\344\270\255ACID\347\232\204\345\216\237\347\220\206.md" +++ /dev/null @@ -1,105 +0,0 @@ -## 引言 - -## 场景 - -> **面试官:"知道事务的四大特性么?"** -> -> **你:"懂,ACID嘛,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)!"** -> -> **面试官:"你们是用mysql数据库吧,能简单说说innodb中怎么实现这四大特性的么?“ 你:"我只知道隔离性是怎么做的balabala~~"** -> **面试官:"还是回去等通知吧~"** - - - -## 举例 - -我们以从A账户转账50元到B账户为例进行说明一下ACID,四大特性。 - -## 原子性 - -根据定义,原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。即要么转账成功,要么转账失败,是不存在中间的状态! - -**如果无法保证原子性会怎么样?** - -OK,就会出现**数据不一致**的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~ - -## 隔离性 - -根据定义,隔离性是指多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 - -**如果无法保证隔离性会怎么样?** - -OK,假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,会出现下面的情形 - -![](https://pic2.zhimg.com/80/v2-e2e899c5187386e58d495a755894f635_hd.jpg) - -如图所示,如果不保证隔离性,A扣款两次,而B只加款一次,凭空消失了50元,依然出现了**数据不一致**的情形! - -## 持久性 - -根据定义,持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。 - -**如果无法保证持久性会怎么样?** - -在Mysql中,为了解决CPU和磁盘速度不一致问题,Mysql是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。 - -设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是**数据不一致**的情形。 - -## 一致性 - -根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的! - -**如果无法保证一致性会怎么样?** - -- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。 -- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。 - -## 解答 - -#### 问题一:Mysql怎么保证一致性的? - -OK,这个问题分为两个层面来说。 从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。 - -但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。 - -从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据! - -#### 问题二: Mysql怎么保证原子性的? - -OK,是利用Innodb的`undo log`。 `undo log`名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如 - -- (1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据 -- (2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作 -- (3)当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作 - -`undo log`记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。 - -#### 问题三: Mysql怎么保证持久性的? - -OK,是利用Innodb的`redo log`。 正如之前说的,Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 *怎么解决这个问题?* 简单啊,事务提交前直接把数据写入磁盘就行啊。 *这么做有什么问题?* - -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用`redo log`解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在`redo log`中记录这次操作。当事务提交的时候,会将`redo log`日志进行刷盘(`redo log`一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将`redo log`中的内容恢复到数据库中,再根据`undo log`和`binlog`内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将`redo log`进行刷盘比对数据页刷盘效率高,具体表现如下 - -- `redo log`体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- `redo log`是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -#### 问题四: Mysql怎么保证隔离性的? - -OK,利用的是锁和MVCC机制。还是拿转账例子来说明,有一个账户表如下 表名`t_balance` - -![](https://pic4.zhimg.com/80/v2-86bedce66efcc4a3b90d09740163e3a7_hd.jpg) - -其中id是主键,user_id为账户名,balance为余额。还是以转账两次为例,如下图所示 - -![](https://pic2.zhimg.com/80/v2-9e136615fa9a741763dfc7ba2f976a99_hd.jpg) - -至于MVCC,即多版本并发控制(Multi Version Concurrency Control),一个行记录数据有多个版本对快照数据,这些快照数据在`undo log`中。 如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本。 由于MVCC机制在可重复读(Repeateable Read)和读已提交(Read Commited)的MVCC表现形式不同,就不赘述了。 - -但是有一点说明一下,在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的。 \ No newline at end of file diff --git "a/Interview/mysql/sql-\345\255\230\345\202\250\345\274\225\346\223\216.md" "b/Interview/mysql/sql-\345\255\230\345\202\250\345\274\225\346\223\216.md" deleted file mode 100644 index 70543084..00000000 --- "a/Interview/mysql/sql-\345\255\230\345\202\250\345\274\225\346\223\216.md" +++ /dev/null @@ -1,40 +0,0 @@ -## 引言 - -**sql-存储引擎** - -## InnoDB - -是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 - -实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ)。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ 间隙锁(Next-Key Locking)防止幻影读。 - -主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 - -内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 - - - -支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -## MyISAM - -设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 - -提供了大量的特性,包括压缩表、空间数据索引等。 - -不支持事务。 - -不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。 - -如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。 - -## 比较 - -- 事务: InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。 -- 并发: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -- 外键: InnoDB 支持外键。 -- 备份: InnoDB 支持在线热备份。 -- 崩溃恢复: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- 其它特性: MyISAM 支持压缩表和空间数据索引。 \ No newline at end of file diff --git "a/Interview/mysql/sql-\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" "b/Interview/mysql/sql-\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" deleted file mode 100644 index 5d115e72..00000000 --- "a/Interview/mysql/sql-\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" +++ /dev/null @@ -1,460 +0,0 @@ -## 引言 - -**sql-数据库系统原理** - -## 事务 - -### 概念 - -事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。 - -### ACID - -#### 原子性(Atomicity) - -事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。 - -回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 - - - -#### 一致性(Consistency) - -数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。 - -#### 隔离性(Isolation) - -一个事务所做的修改在最终提交以前,对其它事务是不可见的。 - -#### 持久性(Durability) - -一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 - -可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。 - -事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系: - -- 只有满足一致性,事务的执行结果才是正确的。 -- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 -- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 -- 事务满足持久化是为了能应对数据库崩溃的情况。 - -![](https://www.pdai.tech/_images/pics/a58e294a-615d-4ea0-9fbf-064a6daec4b2.png) - -### AUTOCOMMIT - -MySQL 默认采用自动提交模式。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。 - -## 并发一致性问题 - -在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。 - -### 丢弃修改 - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 - -![](https://www.pdai.tech/_images/pics/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png) - -### 读脏数据 - -T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 - -![](https://www.pdai.tech/_images/pics/dd782132-d830-4c55-9884-cfac0a541b8e.png) - -### 不可重复读 - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -![](https://www.pdai.tech/_images/pics/c8d18ca9-0b09-441a-9a0c-fb063630d708.png) - -### 幻影读 - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 - -![](https://www.pdai.tech/_images/pics/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png) - -产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 - -## 锁 - -### 封锁粒度 - -MySQL 中提供了两种封锁粒度: 行级锁以及表级锁。 - -应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。 - -但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。 - -### 封锁类型 - -#### 读写锁 - -- 排它锁(Exclusive),简写为 X 锁,又称写锁。 -- 共享锁(Shared),简写为 S 锁,又称读锁。 - -有以下两个规定: - -- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。 -- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。 - -#### 意向锁 - -使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 - -在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。 - -意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定: - -- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁; -- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。 - -通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。 - -- 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁; -- S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。 - -### 封锁协议 - -#### 三级封锁协议 - -##### 一级封锁协议 - -事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。 - -可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。 - -| T1 | T2 | -| :---------: | :---------: | -| lock-x(A) | | -| read A=20 | | -| | lock-x(A) | -| | wait | -| write A=19 | | -| commit | | -| Unlock-x(A) | | -| | obtain | -| | read A = 19 | -| | write A=21 | -| | commit | -| | unlock-x(A) | - -##### 二级封锁协议 - -在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。 - -可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。 - -| T1 | T2 | -| :---------: | :---------: | -| lock-x(A) | | -| read A=20 | | -| write A=19 | | -| | lock-s(A) | -| | wait | -| rollback | | -| A=20 | . | -| unlock-x(A) | | -| | obtain | -| | read A=20 | -| | commit | -| | unlock-s(A) | - -##### 三级封锁协议 - -在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。 - -可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。 - -| T1 | T2 | -| :---------: | :---------: | -| lock-s(A) | | -| read A=20 | | -| | lock-x(A) | -| | wait | -| read A=20 | | -| commit | | -| unlock-s(A) | | -| | obtain | -| | read A=20 | -| | write A=19 | -| | commit | -| | unlock-X(A) | - -#### 两段锁协议 - -加锁和解锁分为两个阶段进行。 - -可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。 - -事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。 - -但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。 - -### Mysql隐式与显式锁定 - -MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。 - -## 隔离级别 - -### 未提交读(READ UNCOMMITTED) - -事务中的修改,即使没有提交,对其它事务也是可见的。 - -### 提交读(READ COMMITTED) - -一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 - -### 可重复读(REPEATABLE READ) - -保证在同一个事务中多次读取同样数据的结果是一样的。 - -### 可串行化(SERIALIZABLE) - -强制事务串行执行。 - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| :------: | :--: | :--------: | :----: | -| 未提交读 | √ | √ | √ | -| 提交读 | × | √ | √ | -| 可重复读 | × | × | √ | -| 可串行化 | × | × | × | - -## 多版本并发控制 - -多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 - -### 版本号 - -- 系统版本号: 是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 -- 事务版本号: 事务开始时的系统版本号。 - -### 隐藏的列 - -MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号: - -- 创建版本号: 指示创建一个数据行的快照时的系统版本号; -- 删除版本号: 如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。 - -### Undo日志 - -MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。 - -![](https://www.pdai.tech/_images/pics/e41405a8-7c05-4f70-8092-e961e28d3112.jpg) - -### 实现过程 - -以下实现过程针对可重复读隔离级别。 - -当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。 - -#### SELECT - -多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。 - -把没有对一个数据行做修改的事务称为 T,T 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。除此之外,T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。 - -#### INSERT - -将当前系统版本号作为数据行快照的创建版本号。 - -#### DELETE - -将当前系统版本号作为数据行快照的删除版本号。 - -#### UPDATE - -将当前系统版本号作为更新前的数据行快照的删除版本号,并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。 - -### 快照读与当前读 - -#### 快照读 - -使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。 - -`select * from table ...;` - -#### 当前读 - -读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。 - -```mysql -select * from table where ? lock in share mode; -select * from table where ? for update; -insert; -update; -delete; -``` - -## Next-Key Locks - -Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。 - -MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。 - -### Record Locks - -锁定一个记录上的索引,而不是记录本身。 - -如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。 - -### Gap Locks - -锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。 - -`SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;` - -### Next-Key Locks - -它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。例如一个索引包含以下值: 10, 11, 13, and 20,那么就需要锁定以下区间: - -```mysql -(negative infinity, 10] -(10, 11] -(11, 13] -(13, 20] -(20, positive infinity) -``` - -## 面试常问的几个问题 - -### 数据库引擎innodb与myisam的区别? - -[InnoDB和MyISAM比较](http://dreamcat.ink/2019/11/15/sql-notes/1/) - -### 4大特性 - -[Mysql的ACID原理](http://dreamcat.ink/2019/11/20/sql-notes/1/) - -### 数据库的隔离级别 - -1. 未提交读,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100. -2. 提交读,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了; -3. 可重复读,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的; -4. 可重复读,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的; - -### 索引的分为哪几类? - -- 分为聚集索引和非聚集索引 -- 非聚集索引又有唯一索引,普通索引,主键索引,和全文索引。 - -### 聚集索引是什么? - -聚集索引是数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,也就是说索引中的顺序也就是在内存中顺序。 - -### 主键 超键 候选键 外键是什么 - -#### 定义 - -超键:在关系中能唯一标识元组的属性集称为关系模式的超键 - -候选键:不含有多余属性的超键称为候选键。也就是在候选键中,若再删除属性,就不是键了! - -主键:用户选作元组标识的一个候选键程序主键 - -外键:如果关系模式R中属性K是其它模式的主键,那么k在模式R中称为外键。 - -#### 举例 - -| 学号 | 姓名 | 性别 | 年龄 | 系别 | 专业 | -| -------- | ------ | ---- | ---- | ------ | -------- | -| 20020612 | 李辉 | 男 | 20 | 计算机 | 软件开发 | -| 20060613 | 张明 | 男 | 18 | 计算机 | 软件开发 | -| 20060614 | 王小玉 | 女 | 19 | 物理 | 力学 | -| 20060615 | 李淑华 | 女 | 17 | 生物 | 动物学 | -| 20060616 | 赵静 | 男 | 21 | 化学 | 食品化学 | -| 20060617 | 赵静 | 女 | 20 | 生物 | 植物学 | - -1. 超键:于是我们从例子中可以发现 学号是标识学生实体的唯一标识。那么该元组的超键就为学号。除此之外我们还可以把它跟其他属性组合起来,比如:(`学号`,`性别`),(`学号`,`年龄`) -2. 候选键:根据例子可知,学号是一个可以唯一标识元组的唯一标识,因此学号是一个候选键,实际上,候选键是超键的子集,比如 (学号,年龄)是超键,但是它不是候选键。因为它还有了额外的属性。 -3. 主键:简单的说,例子中的元组的候选键为学号,但是我们选定他作为该元组的唯一标识,那么学号就为主键。 -4. 外键是相对于主键的,比如在学生记录里,主键为学号,在成绩单表中也有学号字段,因此学号为成绩单表的外键,为学生表的主键。 - -#### 总结 - -**主键为候选键的子集,候选键为超键的子集,而外键的确定是相对于主键的。** - -### 视图的作用,视图可以更改么? - -视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。 - -视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。 - -创建视图:`create view xxx as xxxx` - -对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。 - -### drop,delete与truncate的区别 - -drop直接删掉表;truncate删除表中数据,再插入时自增长id又从1开始 ;delete删除表中数据,可以加where字句。 - -1. DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。 -2. 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。 -3. 一般而言,drop > truncate > delete -4. 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view -5. TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。 -6. truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。 -7. delete语句为DML(Data Manipulation Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。 -8. truncate、drop是DDL(Data Define Language),操作立即生效,原数据不放到 rollback segment中,不能回滚 -9. 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老是想触发trigger,还是用delete。 -10. Truncate table 表名 速度快,而且效率高,因为: truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。 -11. TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。 -12. 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。 - -### 数据库范式 - -#### 第一范式 - -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -#### 第二范式 - -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,**第二范式就是非主属性非部分依赖于主关键字**。 - -#### 第三范式 - -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 数据库优化的思路 - -#### SQL语句优化 - -- 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 -- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:`select id from t where num is null` ;可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:`select id from t where num=0` -- 很多时候用 exists 代替 in 是一个好的选择。 -- 用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤。 - -#### 索引优化 - -#### 数据库结构优化 - -- 范式优化: 比如消除冗余(节省空间。。) -- 反范式优化:比如适当加冗余等(减少join) -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 -- 拆分其实又分垂直拆分和水平拆分: - - 案例: 简单购物系统暂设涉及如下表: - - 1.产品表(数据量10w,稳定) - - 2.订单表(数据量200w,且有增长趋势) - - 3.用户表 (数据量100w,且有增长趋势) - - 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 - - **垂直拆分:** - - 解决问题:表与表之间的io竞争 - - 不解决问题:单表中数据量增长出现的压力 - - 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - - **水平拆分:** - - 解决问题:单表中数据量增长出现的压力 - - 不解决问题:表与表之间的io争夺 - - 方案:**用户表** 通过性别拆分为男用户表和女用户表,**订单表** 通过已完成和完成中拆分为已完成订单和未完成订单,**产品表** 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。 - -#### 服务器硬件优化 - -多花钱 - -### 存储过程与触发器的区别 - -触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激活)执行。 - -触发器是在一个修改了指定表中的数据时执行的存储过程。 - -通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。由于用户不能绕过触发器,所以可以用它来强制实施复杂的业务规则,以确保数据的完整性。 - -触发器不同于存储过程,触发器主要是通过事件执行触发而被执行的,而存储过程可以通过存储过程名称名字而直接调用。当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时,SQLSERVER就会自动执行触发器所定义的SQL语句,从而确保对数据的处理必须符合这些SQL语句所定义的规则。 \ No newline at end of file diff --git "a/Interview/mysql/sql-\347\264\242\345\274\225-B-\346\240\221.md" "b/Interview/mysql/sql-\347\264\242\345\274\225-B-\346\240\221.md" deleted file mode 100644 index 884eb4c3..00000000 --- "a/Interview/mysql/sql-\347\264\242\345\274\225-B-\346\240\221.md" +++ /dev/null @@ -1,134 +0,0 @@ -## 引言 - -**sql-索引(B+树)** - -## B+Tree原理 - -### 数据结构 - -B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。 - -B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。 - -在 B+ Tree 中,一个节点中的 key 从左到右非递减排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。 - - - -### 操作 - -进行查找操作时,首先在根节点进行二分查找,找到一个key所在的指针,然后递归地指针指向的节点进行查找。直到查找到叶子结点,然后在叶子节点上进行二分查找,找出key所对应的data。 - -插入删除操作记录会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性。 - -### 与红黑树的比较 - -红黑树等平衡树可以用实现索引,但是文件系统及数据库系统普遍采用B+Tree作为索引结构,主要有以下两个原因: - -(1)更少的查找次数 - -平衡树查找操作的时间复杂度等于树高h,而树高大致为 O(h)=O(logdN),其中 d 为每个节点的出度。 - -红黑树的出度为2,而B+Tree的出度一般非常大,所以红黑树的树高h很明显比B+ Tree 大非常多,检索的次数也就更多。 - -(2)利用计算机预读特性 - -为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。 - -操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入。 - -## MySQL 索引 - -索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 - -### B+Tree索引 - -是大多数MySQL存储引擎的默认索引类型。 - -因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 - -可以指定多个列作为索引列,多个索引列共同组成键。 - -适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 - -InnoDB 的 B+Tree 索引分为主索引和辅助索引。 - -主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 - -辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。 - -### 哈希索引 - -哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制: - -- 无法用于排序与分组; -- 只支持精确查找,无法用于部分查找和范围查找。 - -InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 - -### 全文索引 - -MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 - -全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。 - -InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 - -### 空间数据索引 - -MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 - -必须使用 GIS 相关的函数来维护数据。 - -## 索引优化 - -### 独立的列 - -在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 - -`SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;` - -### 多列索引 - -在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 - -`SELECT film_id, actor_ id FROM sakila.film_actor -WHERE actor_id = 1 AND film_id = 1;` - -### 索引列的顺序 - -让选择性最强的索引列放在前面,索引的选择性是指: 不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 - -例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 - -·`SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, -COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, -COUNT(*) -FROM payment;` - -### 前缀索引 - -对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 - -对于前缀长度的选取需要根据索引选择性来确定。 - -### 覆盖索引 - -索引包含所有需要查询的字段的值。 - -具有以下优点: - -- 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。 -- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 -- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 - -## 索引的优点 - -- 大大减少了服务器需要扫描的数据行数。 -- 帮助服务器避免进行排序和分组,也就不需要创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。 -- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。 - -### 索引的使用场景 - -- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。 -- 对于中到大型的表,索引就非常有效。 -- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 \ No newline at end of file diff --git a/Interview/mysql/test.sql b/Interview/mysql/test.sql deleted file mode 100644 index 13354b95..00000000 --- a/Interview/mysql/test.sql +++ /dev/null @@ -1,170 +0,0 @@ --- MySQL dump 10.13 Distrib 8.0.16, for osx10.14 (x86_64) --- --- Host: localhost Database: test --- ------------------------------------------------------ --- Server version 8.0.16 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; - SET NAMES utf8mb4 ; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `employees` --- - -DROP TABLE IF EXISTS `employees`; -/*!40101 SET @saved_cs_client = @@character_set_client */; - SET character_set_client = utf8mb4 ; -CREATE TABLE `employees` ( - `employee_id` int(6) NOT NULL AUTO_INCREMENT, - `first_name` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL, - `last_name` varchar(25) COLLATE utf8mb4_general_ci DEFAULT NULL, - `email` varchar(25) COLLATE utf8mb4_general_ci DEFAULT NULL, - `phone_number` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL, - `job_id` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL, - `salary` double(10,2) DEFAULT NULL, - `commission_pct` double(4,2) DEFAULT NULL, - `manager_id` int(6) DEFAULT NULL, - `department_id` int(4) DEFAULT NULL, - `hiredate` datetime DEFAULT NULL, - PRIMARY KEY (`employee_id`) USING BTREE, - KEY `dept_id_fk` (`department_id`) USING BTREE, - KEY `job_id_fk` (`job_id`) USING BTREE, - CONSTRAINT `dept_id_fk` FOREIGN KEY (`department_id`) REFERENCES `departments` (`department_id`) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT `job_id_fk` FOREIGN KEY (`job_id`) REFERENCES `jobs` (`job_id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE=InnoDB AUTO_INCREMENT=207 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `employees` --- - -LOCK TABLES `employees` WRITE; -/*!40000 ALTER TABLE `employees` DISABLE KEYS */; -INSERT INTO `employees` VALUES (100,'Steven','K_ing','SKING','515.123.4567','AD_PRES',24000.00,NULL,NULL,90,'1992-04-03 00:00:00'),(101,'Neena','Kochhar','NKOCHHAR','515.123.4568','AD_VP',17000.00,NULL,100,90,'1992-04-03 00:00:00'),(102,'Lex','De Haan','LDEHAAN','515.123.4569','AD_VP',17000.00,NULL,100,90,'1992-04-03 00:00:00'),(103,'Alexander','Hunold','AHUNOLD','590.423.4567','IT_PROG',9000.00,NULL,102,60,'1992-04-03 00:00:00'),(104,'Bruce','Ernst','BERNST','590.423.4568','IT_PROG',6000.00,NULL,103,60,'1992-04-03 00:00:00'),(105,'David','Austin','DAUSTIN','590.423.4569','IT_PROG',4800.00,NULL,103,60,'1998-03-03 00:00:00'),(106,'Valli','Pataballa','VPATABAL','590.423.4560','IT_PROG',4800.00,NULL,103,60,'1998-03-03 00:00:00'),(107,'Diana','Lorentz','DLORENTZ','590.423.5567','IT_PROG',4200.00,NULL,103,60,'1998-03-03 00:00:00'),(108,'Nancy','Greenberg','NGREENBE','515.124.4569','FI_MGR',12000.00,NULL,101,100,'1998-03-03 00:00:00'),(109,'Daniel','Faviet','DFAVIET','515.124.4169','FI_ACCOUNT',9000.00,NULL,108,100,'1998-03-03 00:00:00'),(110,'John','Chen','JCHEN','515.124.4269','FI_ACCOUNT',8200.00,NULL,108,100,'2000-09-09 00:00:00'),(111,'Ismael','Sciarra','ISCIARRA','515.124.4369','FI_ACCOUNT',7700.00,NULL,108,100,'2000-09-09 00:00:00'),(112,'Jose Manuel','Urman','JMURMAN','515.124.4469','FI_ACCOUNT',7800.00,NULL,108,100,'2000-09-09 00:00:00'),(113,'Luis','Popp','LPOPP','515.124.4567','FI_ACCOUNT',6900.00,NULL,108,100,'2000-09-09 00:00:00'),(114,'Den','Raphaely','DRAPHEAL','515.127.4561','PU_MAN',11000.00,NULL,100,30,'2000-09-09 00:00:00'),(115,'Alexander','Khoo','AKHOO','515.127.4562','PU_CLERK',3100.00,NULL,114,30,'2000-09-09 00:00:00'),(116,'Shelli','Baida','SBAIDA','515.127.4563','PU_CLERK',2900.00,NULL,114,30,'2000-09-09 00:00:00'),(117,'Sigal','Tobias','STOBIAS','515.127.4564','PU_CLERK',2800.00,NULL,114,30,'2000-09-09 00:00:00'),(118,'Guy','Himuro','GHIMURO','515.127.4565','PU_CLERK',2600.00,NULL,114,30,'2000-09-09 00:00:00'),(119,'Karen','Colmenares','KCOLMENA','515.127.4566','PU_CLERK',2500.00,NULL,114,30,'2000-09-09 00:00:00'),(120,'Matthew','Weiss','MWEISS','650.123.1234','ST_MAN',8000.00,NULL,100,50,'2004-02-06 00:00:00'),(121,'Adam','Fripp','AFRIPP','650.123.2234','ST_MAN',8200.00,NULL,100,50,'2004-02-06 00:00:00'),(122,'Payam','Kaufling','PKAUFLIN','650.123.3234','ST_MAN',7900.00,NULL,100,50,'2004-02-06 00:00:00'),(123,'Shanta','Vollman','SVOLLMAN','650.123.4234','ST_MAN',6500.00,NULL,100,50,'2004-02-06 00:00:00'),(124,'Kevin','Mourgos','KMOURGOS','650.123.5234','ST_MAN',5800.00,NULL,100,50,'2004-02-06 00:00:00'),(125,'Julia','Nayer','JNAYER','650.124.1214','ST_CLERK',3200.00,NULL,120,50,'2004-02-06 00:00:00'),(126,'Irene','Mikkilineni','IMIKKILI','650.124.1224','ST_CLERK',2700.00,NULL,120,50,'2004-02-06 00:00:00'),(127,'James','Landry','JLANDRY','650.124.1334','ST_CLERK',2400.00,NULL,120,50,'2004-02-06 00:00:00'),(128,'Steven','Markle','SMARKLE','650.124.1434','ST_CLERK',2200.00,NULL,120,50,'2004-02-06 00:00:00'),(129,'Laura','Bissot','LBISSOT','650.124.5234','ST_CLERK',3300.00,NULL,121,50,'2004-02-06 00:00:00'),(130,'Mozhe','Atkinson','MATKINSO','650.124.6234','ST_CLERK',2800.00,NULL,121,50,'2004-02-06 00:00:00'),(131,'James','Marlow','JAMRLOW','650.124.7234','ST_CLERK',2500.00,NULL,121,50,'2004-02-06 00:00:00'),(132,'TJ','Olson','TJOLSON','650.124.8234','ST_CLERK',2100.00,NULL,121,50,'2004-02-06 00:00:00'),(133,'Jason','Mallin','JMALLIN','650.127.1934','ST_CLERK',3300.00,NULL,122,50,'2004-02-06 00:00:00'),(134,'Michael','Rogers','MROGERS','650.127.1834','ST_CLERK',2900.00,NULL,122,50,'2002-12-23 00:00:00'),(135,'Ki','Gee','KGEE','650.127.1734','ST_CLERK',2400.00,NULL,122,50,'2002-12-23 00:00:00'),(136,'Hazel','Philtanker','HPHILTAN','650.127.1634','ST_CLERK',2200.00,NULL,122,50,'2002-12-23 00:00:00'),(137,'Renske','Ladwig','RLADWIG','650.121.1234','ST_CLERK',3600.00,NULL,123,50,'2002-12-23 00:00:00'),(138,'Stephen','Stiles','SSTILES','650.121.2034','ST_CLERK',3200.00,NULL,123,50,'2002-12-23 00:00:00'),(139,'John','Seo','JSEO','650.121.2019','ST_CLERK',2700.00,NULL,123,50,'2002-12-23 00:00:00'),(140,'Joshua','Patel','JPATEL','650.121.1834','ST_CLERK',2500.00,NULL,123,50,'2002-12-23 00:00:00'),(141,'Trenna','Rajs','TRAJS','650.121.8009','ST_CLERK',3500.00,NULL,124,50,'2002-12-23 00:00:00'),(142,'Curtis','Davies','CDAVIES','650.121.2994','ST_CLERK',3100.00,NULL,124,50,'2002-12-23 00:00:00'),(143,'Randall','Matos','RMATOS','650.121.2874','ST_CLERK',2600.00,NULL,124,50,'2002-12-23 00:00:00'),(144,'Peter','Vargas','PVARGAS','650.121.2004','ST_CLERK',2500.00,NULL,124,50,'2002-12-23 00:00:00'),(145,'John','Russell','JRUSSEL','011.44.1344.429268','SA_MAN',14000.00,0.40,100,80,'2002-12-23 00:00:00'),(146,'Karen','Partners','KPARTNER','011.44.1344.467268','SA_MAN',13500.00,0.30,100,80,'2002-12-23 00:00:00'),(147,'Alberto','Errazuriz','AERRAZUR','011.44.1344.429278','SA_MAN',12000.00,0.30,100,80,'2002-12-23 00:00:00'),(148,'Gerald','Cambrault','GCAMBRAU','011.44.1344.619268','SA_MAN',11000.00,0.30,100,80,'2002-12-23 00:00:00'),(149,'Eleni','Zlotkey','EZLOTKEY','011.44.1344.429018','SA_MAN',10500.00,0.20,100,80,'2002-12-23 00:00:00'),(150,'Peter','Tucker','PTUCKER','011.44.1344.129268','SA_REP',10000.00,0.30,145,80,'2014-03-05 00:00:00'),(151,'David','Bernstein','DBERNSTE','011.44.1344.345268','SA_REP',9500.00,0.25,145,80,'2014-03-05 00:00:00'),(152,'Peter','Hall','PHALL','011.44.1344.478968','SA_REP',9000.00,0.25,145,80,'2014-03-05 00:00:00'),(153,'Christopher','Olsen','COLSEN','011.44.1344.498718','SA_REP',8000.00,0.20,145,80,'2014-03-05 00:00:00'),(154,'Nanette','Cambrault','NCAMBRAU','011.44.1344.987668','SA_REP',7500.00,0.20,145,80,'2014-03-05 00:00:00'),(155,'Oliver','Tuvault','OTUVAULT','011.44.1344.486508','SA_REP',7000.00,0.15,145,80,'2014-03-05 00:00:00'),(156,'Janette','K_ing','JKING','011.44.1345.429268','SA_REP',10000.00,0.35,146,80,'2014-03-05 00:00:00'),(157,'Patrick','Sully','PSULLY','011.44.1345.929268','SA_REP',9500.00,0.35,146,80,'2014-03-05 00:00:00'),(158,'Allan','McEwen','AMCEWEN','011.44.1345.829268','SA_REP',9000.00,0.35,146,80,'2014-03-05 00:00:00'),(159,'Lindsey','Smith','LSMITH','011.44.1345.729268','SA_REP',8000.00,0.30,146,80,'2014-03-05 00:00:00'),(160,'Louise','Doran','LDORAN','011.44.1345.629268','SA_REP',7500.00,0.30,146,80,'2014-03-05 00:00:00'),(161,'Sarath','Sewall','SSEWALL','011.44.1345.529268','SA_REP',7000.00,0.25,146,80,'2014-03-05 00:00:00'),(162,'Clara','Vishney','CVISHNEY','011.44.1346.129268','SA_REP',10500.00,0.25,147,80,'2014-03-05 00:00:00'),(163,'Danielle','Greene','DGREENE','011.44.1346.229268','SA_REP',9500.00,0.15,147,80,'2014-03-05 00:00:00'),(164,'Mattea','Marvins','MMARVINS','011.44.1346.329268','SA_REP',7200.00,0.10,147,80,'2014-03-05 00:00:00'),(165,'David','Lee','DLEE','011.44.1346.529268','SA_REP',6800.00,0.10,147,80,'2014-03-05 00:00:00'),(166,'Sundar','Ande','SANDE','011.44.1346.629268','SA_REP',6400.00,0.10,147,80,'2014-03-05 00:00:00'),(167,'Amit','Banda','ABANDA','011.44.1346.729268','SA_REP',6200.00,0.10,147,80,'2014-03-05 00:00:00'),(168,'Lisa','Ozer','LOZER','011.44.1343.929268','SA_REP',11500.00,0.25,148,80,'2014-03-05 00:00:00'),(169,'Harrison','Bloom','HBLOOM','011.44.1343.829268','SA_REP',10000.00,0.20,148,80,'2014-03-05 00:00:00'),(170,'Tayler','Fox','TFOX','011.44.1343.729268','SA_REP',9600.00,0.20,148,80,'2014-03-05 00:00:00'),(171,'William','Smith','WSMITH','011.44.1343.629268','SA_REP',7400.00,0.15,148,80,'2014-03-05 00:00:00'),(172,'Elizabeth','Bates','EBATES','011.44.1343.529268','SA_REP',7300.00,0.15,148,80,'2014-03-05 00:00:00'),(173,'Sundita','Kumar','SKUMAR','011.44.1343.329268','SA_REP',6100.00,0.10,148,80,'2014-03-05 00:00:00'),(174,'Ellen','Abel','EABEL','011.44.1644.429267','SA_REP',11000.00,0.30,149,80,'2014-03-05 00:00:00'),(175,'Alyssa','Hutton','AHUTTON','011.44.1644.429266','SA_REP',8800.00,0.25,149,80,'2014-03-05 00:00:00'),(176,'Jonathon','Taylor','JTAYLOR','011.44.1644.429265','SA_REP',8600.00,0.20,149,80,'2014-03-05 00:00:00'),(177,'Jack','Livingston','JLIVINGS','011.44.1644.429264','SA_REP',8400.00,0.20,149,80,'2014-03-05 00:00:00'),(178,'Kimberely','Grant','KGRANT','011.44.1644.429263','SA_REP',7000.00,0.15,149,NULL,'2014-03-05 00:00:00'),(179,'Charles','Johnson','CJOHNSON','011.44.1644.429262','SA_REP',6200.00,0.10,149,80,'2014-03-05 00:00:00'),(180,'Winston','Taylor','WTAYLOR','650.507.9876','SH_CLERK',3200.00,NULL,120,50,'2014-03-05 00:00:00'),(181,'Jean','Fleaur','JFLEAUR','650.507.9877','SH_CLERK',3100.00,NULL,120,50,'2014-03-05 00:00:00'),(182,'Martha','Sullivan','MSULLIVA','650.507.9878','SH_CLERK',2500.00,NULL,120,50,'2014-03-05 00:00:00'),(183,'Girard','Geoni','GGEONI','650.507.9879','SH_CLERK',2800.00,NULL,120,50,'2014-03-05 00:00:00'),(184,'Nandita','Sarchand','NSARCHAN','650.509.1876','SH_CLERK',4200.00,NULL,121,50,'2014-03-05 00:00:00'),(185,'Alexis','Bull','ABULL','650.509.2876','SH_CLERK',4100.00,NULL,121,50,'2014-03-05 00:00:00'),(186,'Julia','Dellinger','JDELLING','650.509.3876','SH_CLERK',3400.00,NULL,121,50,'2014-03-05 00:00:00'),(187,'Anthony','Cabrio','ACABRIO','650.509.4876','SH_CLERK',3000.00,NULL,121,50,'2014-03-05 00:00:00'),(188,'Kelly','Chung','KCHUNG','650.505.1876','SH_CLERK',3800.00,NULL,122,50,'2014-03-05 00:00:00'),(189,'Jennifer','Dilly','JDILLY','650.505.2876','SH_CLERK',3600.00,NULL,122,50,'2014-03-05 00:00:00'),(190,'Timothy','Gates','TGATES','650.505.3876','SH_CLERK',2900.00,NULL,122,50,'2014-03-05 00:00:00'),(191,'Randall','Perkins','RPERKINS','650.505.4876','SH_CLERK',2500.00,NULL,122,50,'2014-03-05 00:00:00'),(192,'Sarah','Bell','SBELL','650.501.1876','SH_CLERK',4000.00,NULL,123,50,'2014-03-05 00:00:00'),(193,'Britney','Everett','BEVERETT','650.501.2876','SH_CLERK',3900.00,NULL,123,50,'2014-03-05 00:00:00'),(194,'Samuel','McCain','SMCCAIN','650.501.3876','SH_CLERK',3200.00,NULL,123,50,'2014-03-05 00:00:00'),(195,'Vance','Jones','VJONES','650.501.4876','SH_CLERK',2800.00,NULL,123,50,'2014-03-05 00:00:00'),(196,'Alana','Walsh','AWALSH','650.507.9811','SH_CLERK',3100.00,NULL,124,50,'2014-03-05 00:00:00'),(197,'Kevin','Feeney','KFEENEY','650.507.9822','SH_CLERK',3000.00,NULL,124,50,'2014-03-05 00:00:00'),(198,'Donald','OConnell','DOCONNEL','650.507.9833','SH_CLERK',2600.00,NULL,124,50,'2014-03-05 00:00:00'),(199,'Douglas','Grant','DGRANT','650.507.9844','SH_CLERK',2600.00,NULL,124,50,'2014-03-05 00:00:00'),(200,'Jennifer','Whalen','JWHALEN','515.123.4444','AD_ASST',4400.00,NULL,101,10,'2016-03-03 00:00:00'),(201,'Michael','Hartstein','MHARTSTE','515.123.5555','MK_MAN',13000.00,NULL,100,20,'2016-03-03 00:00:00'),(202,'Pat','Fay','PFAY','603.123.6666','MK_REP',6000.00,NULL,201,20,'2016-03-03 00:00:00'),(203,'Susan','Mavris','SMAVRIS','515.123.7777','HR_REP',6500.00,NULL,101,40,'2016-03-03 00:00:00'),(204,'Hermann','Baer','HBAER','515.123.8888','PR_REP',10000.00,NULL,101,70,'2016-03-03 00:00:00'),(205,'Shelley','Higgins','SHIGGINS','515.123.8080','AC_MGR',12000.00,NULL,101,110,'2016-03-03 00:00:00'),(206,'William','Gietz','WGIETZ','515.123.8181','AC_ACCOUNT',8300.00,NULL,205,110,'2016-03-03 00:00:00'); -/*!40000 ALTER TABLE `employees` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `job_grades` --- - -DROP TABLE IF EXISTS `job_grades`; -/*!40101 SET @saved_cs_client = @@character_set_client */; - SET character_set_client = utf8mb4 ; -CREATE TABLE `job_grades` ( - `grade_level` varchar(3) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, - `lowest_sal` int(11) DEFAULT NULL, - `highest_sal` int(11) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `job_grades` --- - -LOCK TABLES `job_grades` WRITE; -/*!40000 ALTER TABLE `job_grades` DISABLE KEYS */; -INSERT INTO `job_grades` VALUES ('A',1000,2999),('B',3000,5999),('C',6000,9999),('D',10000,14999),('E',15000,24999),('F',25000,40000); -/*!40000 ALTER TABLE `job_grades` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `jobs` --- - -DROP TABLE IF EXISTS `jobs`; -/*!40101 SET @saved_cs_client = @@character_set_client */; - SET character_set_client = utf8mb4 ; -CREATE TABLE `jobs` ( - `job_id` varchar(10) COLLATE utf8mb4_general_ci NOT NULL, - `job_title` varchar(35) COLLATE utf8mb4_general_ci DEFAULT NULL, - `min_salary` int(6) DEFAULT NULL, - `max_salary` int(6) DEFAULT NULL, - PRIMARY KEY (`job_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `jobs` --- - -LOCK TABLES `jobs` WRITE; -/*!40000 ALTER TABLE `jobs` DISABLE KEYS */; -INSERT INTO `jobs` VALUES ('AC_ACCOUNT','Public Accountant',4200,9000),('AC_MGR','Accounting Manager',8200,16000),('AD_ASST','Administration Assistant',3000,6000),('AD_PRES','President',20000,40000),('AD_VP','Administration Vice President',15000,30000),('FI_ACCOUNT','Accountant',4200,9000),('FI_MGR','Finance Manager',8200,16000),('HR_REP','Human Resources Representative',4000,9000),('IT_PROG','Programmer',4000,10000),('MK_MAN','Marketing Manager',9000,15000),('MK_REP','Marketing Representative',4000,9000),('PR_REP','Public Relations Representative',4500,10500),('PU_CLERK','Purchasing Clerk',2500,5500),('PU_MAN','Purchasing Manager',8000,15000),('SA_MAN','Sales Manager',10000,20000),('SA_REP','Sales Representative',6000,12000),('SH_CLERK','Shipping Clerk',2500,5500),('ST_CLERK','Stock Clerk',2000,5000),('ST_MAN','Stock Manager',5500,8500); -/*!40000 ALTER TABLE `jobs` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `locations` --- - -DROP TABLE IF EXISTS `locations`; -/*!40101 SET @saved_cs_client = @@character_set_client */; - SET character_set_client = utf8mb4 ; -CREATE TABLE `locations` ( - `location_id` int(11) NOT NULL AUTO_INCREMENT, - `street_address` varchar(40) COLLATE utf8mb4_general_ci DEFAULT NULL, - `postal_code` varchar(12) COLLATE utf8mb4_general_ci DEFAULT NULL, - `city` varchar(30) COLLATE utf8mb4_general_ci DEFAULT NULL, - `state_province` varchar(25) COLLATE utf8mb4_general_ci DEFAULT NULL, - `country_id` varchar(2) COLLATE utf8mb4_general_ci DEFAULT NULL, - PRIMARY KEY (`location_id`) -) ENGINE=InnoDB AUTO_INCREMENT=3201 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `locations` --- - -LOCK TABLES `locations` WRITE; -/*!40000 ALTER TABLE `locations` DISABLE KEYS */; -INSERT INTO `locations` VALUES (1000,'1297 Via Cola di Rie','00989','Roma',NULL,'IT'),(1100,'93091 Calle della Testa','10934','Venice',NULL,'IT'),(1200,'2017 Shinjuku-ku','1689','Tokyo','Tokyo Prefecture','JP'),(1300,'9450 Kamiya-cho','6823','Hiroshima',NULL,'JP'),(1400,'2014 Jabberwocky Rd','26192','Southlake','Texas','US'),(1500,'2011 Interiors Blvd','99236','South San Francisco','California','US'),(1600,'2007 Zagora St','50090','South Brunswick','New Jersey','US'),(1700,'2004 Charade Rd','98199','Seattle','Washington','US'),(1800,'147 Spadina Ave','M5V 2L7','Toronto','Ontario','CA'),(1900,'6092 Boxwood St','YSW 9T2','Whitehorse','Yukon','CA'),(2000,'40-5-12 Laogianggen','190518','Beijing',NULL,'CN'),(2100,'1298 Vileparle (E)','490231','Bombay','Maharashtra','IN'),(2200,'12-98 Victoria Street','2901','Sydney','New South Wales','AU'),(2300,'198 Clementi North','540198','Singapore',NULL,'SG'),(2400,'8204 Arthur St',NULL,'London',NULL,'UK'),(2500,'Magdalen Centre, The Oxford Science Park','OX9 9ZB','Oxford','Oxford','UK'),(2600,'9702 Chester Road','09629850293','Stretford','Manchester','UK'),(2700,'Schwanthalerstr. 7031','80925','Munich','Bavaria','DE'),(2800,'Rua Frei Caneca 1360 ','01307-002','Sao Paulo','Sao Paulo','BR'),(2900,'20 Rue des Corps-Saints','1730','Geneva','Geneve','CH'),(3000,'Murtenstrasse 921','3095','Bern','BE','CH'),(3100,'Pieter Breughelstraat 837','3029SK','Utrecht','Utrecht','NL'),(3200,'Mariano Escobedo 9991','11932','Mexico City','Distrito Federal,','MX'); -/*!40000 ALTER TABLE `locations` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `departments` --- - -DROP TABLE IF EXISTS `departments`; -/*!40101 SET @saved_cs_client = @@character_set_client */; - SET character_set_client = utf8mb4 ; -CREATE TABLE `departments` ( - `department_id` int(4) NOT NULL AUTO_INCREMENT, - `department_name` varchar(3) COLLATE utf8mb4_general_ci DEFAULT NULL, - `manager_id` int(6) DEFAULT NULL, - `location_id` int(4) DEFAULT NULL, - PRIMARY KEY (`department_id`) USING BTREE, - KEY `loc_id_fk` (`location_id`) USING BTREE, - CONSTRAINT `loc_id_fk` FOREIGN KEY (`location_id`) REFERENCES `locations` (`location_id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE=InnoDB AUTO_INCREMENT=271 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `departments` --- - -LOCK TABLES `departments` WRITE; -/*!40000 ALTER TABLE `departments` DISABLE KEYS */; -INSERT INTO `departments` VALUES (10,'Adm',200,1700),(20,'Mar',201,1800),(30,'Pur',114,1700),(40,'Hum',203,2400),(50,'Shi',121,1500),(60,'IT',103,1400),(70,'Pub',204,2700),(80,'Sal',145,2500),(90,'Exe',100,1700),(100,'Fin',108,1700),(110,'Acc',205,1700),(120,'Tre',NULL,1700),(130,'Cor',NULL,1700),(140,'Con',NULL,1700),(150,'Sha',NULL,1700),(160,'Ben',NULL,1700),(170,'Man',NULL,1700),(180,'Con',NULL,1700),(190,'Con',NULL,1700),(200,'Ope',NULL,1700),(210,'IT ',NULL,1700),(220,'NOC',NULL,1700),(230,'IT ',NULL,1700),(240,'Gov',NULL,1700),(250,'Ret',NULL,1700),(260,'Rec',NULL,1700),(270,'Pay',NULL,1700); -/*!40000 ALTER TABLE `departments` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2020-03-06 1:56:25 diff --git a/Interview/mysql/test2.sql b/Interview/mysql/test2.sql deleted file mode 100644 index b4cab93d..00000000 --- a/Interview/mysql/test2.sql +++ /dev/null @@ -1,72 +0,0 @@ -/* - Navicat Premium Data Transfer - - Source Server : mysql0815 - Source Server Type : MySQL - Source Server Version : 50562 - Source Host : localhost:3306 - Source Schema : girls - - Target Server Type : MySQL - Target Server Version : 50562 - File Encoding : 65001 - - Date: 06/11/2019 16:26:51 -*/ -USE test; -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - - --- ---------------------------- --- Table structure for beauty --- ---------------------------- -DROP TABLE IF EXISTS `beauty`; -CREATE TABLE `beauty` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'Ů', - `borndate` datetime NULL DEFAULT '1987-01-01 00:00:00', - `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `photo` blob NULL, - `boyfriend_id` int(11) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; - --- ---------------------------- --- Records of beauty --- ---------------------------- -INSERT INTO `beauty` VALUES (1, '柳岩', '女', '1988-02-03 00:00:00', '18209876577', NULL, 8); -INSERT INTO `beauty` VALUES (2, '苍老师', '女', '1987-12-30 00:00:00', '18219876577', NULL, 9); -INSERT INTO `beauty` VALUES (3, 'Angelababy', '女', '1989-02-03 00:00:00', '18209876567', NULL, 3); -INSERT INTO `beauty` VALUES (4, '热巴', '女', '1993-02-03 00:00:00', '18209876579', NULL, 2); -INSERT INTO `beauty` VALUES (5, '周冬雨', '女', '1992-02-03 00:00:00', '18209179577', NULL, 9); -INSERT INTO `beauty` VALUES (6, '周芷若', '女', '1988-02-03 00:00:00', '18209876577', NULL, 1); -INSERT INTO `beauty` VALUES (7, '岳灵珊', '女', '1987-12-30 00:00:00', '18219876577', NULL, 9); -INSERT INTO `beauty` VALUES (8, '小昭', '女', '1989-02-03 00:00:00', '18209876567', NULL, 1); -INSERT INTO `beauty` VALUES (9, '双儿', '女', '1993-02-03 00:00:00', '18209876579', NULL, 9); -INSERT INTO `beauty` VALUES (10, '王语嫣', '女', '1992-02-03 00:00:00', '18209179577', NULL, 4); -INSERT INTO `beauty` VALUES (11, '夏雪', '女', '1993-02-03 00:00:00', '18209876579', NULL, 9); -INSERT INTO `beauty` VALUES (12, '赵敏', '女', '1992-02-03 00:00:00', '18209179577', NULL, 1); - --- ---------------------------- --- Table structure for boys --- ---------------------------- -DROP TABLE IF EXISTS `boys`; -CREATE TABLE `boys` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `boyName` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - `userCP` int(11) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; - --- ---------------------------- --- Records of boys --- ---------------------------- -INSERT INTO `boys` VALUES (1, '张无忌', 100); -INSERT INTO `boys` VALUES (2, '鹿晗', 800); -INSERT INTO `boys` VALUES (3, '黄晓', 50); -INSERT INTO `boys` VALUES (4, '段誉', 300); - -SET FOREIGN_KEY_CHECKS = 1; - diff --git "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-DNS\346\230\257\345\271\262\344\273\200\344\271\210\347\232\204.md" "b/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-DNS\346\230\257\345\271\262\344\273\200\344\271\210\347\232\204.md" deleted file mode 100644 index 6378a757..00000000 --- "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-DNS\346\230\257\345\271\262\344\273\200\344\271\210\347\232\204.md" +++ /dev/null @@ -1,74 +0,0 @@ -## 引言 - -> 咱们这个这样一个面试问题:**"从输入url到页面展示到底发生了什么?**" -> -> 回答的话,的确很简单!但是其中涉及到的细节非常多,估计能讲半个小时。 -> -> 大概的过程分为这8步(俗称天龙八部) -> -> 1. 根据域名,进行DNS域名解析; -> 2. 拿到解析的IP地址,建立TCP连接; -> 3. 向IP地址,发送HTTP请求; -> 4. 服务器处理请求; -> 5. 返回响应结果; -> 6. 关闭TCP连接; -> 7. 浏览器解析HTML; -> 8. 浏览器布局渲染; -> -> 咱们目前这细讲dns,关于这个问题,每一步后续都会有文章讲解。 - - - -## 解析过程 - -先看图 - -![解析图](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121165025.png) - -- 请求一旦发起,若是chrome浏览器,先在浏览器找之前有没有缓存过的域名所对应的ip地址,有的话,直接跳过dns解析了,若是没有,就会找硬盘的hosts文件,看看有没有,有的话,直接找到hosts文件里面的ip -- 如果本地的hosts文件没有能的到对应的ip地址,浏览器会发出一个dns请求到本地dns服务器,本地dns服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动等。 -- 查询你输入的网址的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询。 -- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 -- 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。 - -## DNS是? - -DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 - -通俗的讲,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2。而计算机更擅长记住网站的ip地址,而不是像www.baidu.com等链接。因为,DNS就相当于一个电话本,比如你要找www.baidu.com这个域名,那我翻一翻我的电话本,我就知道,哦,它的电话(ip)是167.23.10.2。 - -## 查询方式 - -### 递归解析 - -当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。此时有两种方式。局部DNS服务器自己负责向其他DNS服务器进行查询,一般是先向该域名的根域服务器查询,再由根域名服务器一级级向下查询。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。 - -### 迭代解析 - -当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析。局部DNS服务器不是自己向其他DNS服务器进行查询,而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。 - -## dns域名城空间组织方式 - -我们在前面有说到根DNS服务器,域DNS服务器,这些都是DNS域名称空间的组织方式。按其功能命名空间中用来描述 DNS 域名称的五个类别的介绍详见下表中,以及与每个名称类型的示例 - -| 名称类型 | 说明 | 示例 | -| :------: | :----------------------------------------------------------: | :-----------------------------: | -| 根域 | DNS域名中使用时,规定由尾部句点(.)来指定名称位于根或更高级的域名层次结构 | 单个句点(.)或句点用于末尾的名称 | -| 顶级域 | 用来指定某个国家/地区或组织使用的名称的类型名称 | .com | -| 第二层域 | 个人或组织Internet上使用的注册名称 | qq.com | -| 子域 | 已注册的二级域名派生的域名,通俗的讲就是网站名 | www.qq.com | -| 主机名 | 通常情况下,DNS域名的最左侧的标签标示网络上的特定计算机,如h1 | h1.www.qq.com | - -## DNS负载均衡 - -当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。处理办法就是用DNS负载均衡技术,它的原理是在DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器,从而达到负载均衡的目的。例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等。 - -## DNS几个问题 - -### 为什么域名解析用UDP协议? - -因为UDP快啊!UDP的DNS协议只要一个请求、一个应答就好了。而使用基于TCP的DNS协议要三次握手、发送数据以及应答、四次挥手。但是UDP协议传输内容不能超过512字节。不过客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。 - -### 为什么区域传送用TCP协议? - -因为TCP协议可靠性好啊!你要从主DNS上复制内容啊,你用不可靠的UDP? 因为TCP协议传输的内容大啊,你用最大只能传512字节的UDP协议?万一同步的数据大于512字节,你怎么办? \ No newline at end of file diff --git "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-http\351\202\243\344\272\233\344\272\213\345\204\277.md" "b/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-http\351\202\243\344\272\233\344\272\213\345\204\277.md" deleted file mode 100644 index e813a3c1..00000000 --- "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206-http\351\202\243\344\272\233\344\272\213\345\204\277.md" +++ /dev/null @@ -1,347 +0,0 @@ -## 引言 - -> 想必大家都用过浏览器吧? -> -> 在浏览器上输入url,居然能渲染出这么多信息,是不是感觉好6呀? -> -> 本文简单聊一下http,当然面试的时候也会经常问这种类型相关的题。 - - - -## 概念 - -- URI:Uniform Resource Identifier:统一资源标识符 -- URL:Uniform Resource Locator:统一资源定位符,eg:https://www.baidu.com -- URN:Uniform Resource Name:统一资源名称 - -### 请求和响应报文 - -#### 请求报文 - -简单来说: - -- 请求行:Request Line -- 请求头:Request Headers -- 请求体:Request Body - -#### 响应报文 - -简单来说: - -- 状态行:Status Line -- 响应头:Response Headers -- 响应体:Response Body - -## HTTP方法 - -客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。 - -### GET - -> 获取资源 - -当前网络请求中,绝大部分使用的是 GET 方法。 - -### HEAD - -> 获取报文头部 - -和 GET 方法类似,但是不返回报文实体主体部分。 - -主要用于确认 URL 的有效性以及资源更新的日期时间等。 - -### POST - -> 传输实体主体 - -POST 主要用来传输数据,而 GET 主要用来获取资源。 - -更多 POST 与 GET 的比较见后 - -### PUT - -> 上传文件 - -由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。 - -### PATCH - -> 对资源进行部分修改 - -PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。 - -### DELETE - -> 删除文件 - -与 PUT 功能相反,并且同样不带验证机制。 - -### OPTIONS - -> 查询支持的方法 - -查询指定的 URL 能够支持的方法。 - -会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容。 - -### CONNECT - -> 要求在与代理服务器通信时建立隧道 - -使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 - -## HTTP状态码 - -服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 - -| 状态码 | 类别 | 含义 | -| :----: | :------------------------------: | :------------------------: | -| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | -| 2XX | Success(成功状态码) | 请求正常处理完毕 | -| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | -| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | -| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | - -### 1XX 信息 - -- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 - -### 2XX 成功 - -- **200 OK** -- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 -- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 - -### 3XX 重定向 - -- **301 Moved Permanently** :永久性重定向 -- **302 Found** :临时性重定向 -- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 -- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 -- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 - -### 4XX 客户端错误 - -- **400 Bad Request** :请求报文中存在语法错误。 -- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 -- **403 Forbidden** :请求被拒绝。 -- **404 Not Found** - -### 5XX 服务器错误 - -- **500 Internal Server Error** :服务器正在执行请求时发生错误。 -- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 - -## HTTP首部 - -有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 - -各种首部字段及其含义如下(**不需要全记,仅供查阅):** - -### 通用首部字段 - -| 首部字段名 | 说明 | -| :---------------: | :----------------------------------------: | -| Cache-Control | 控制缓存的行为 | -| Connection | 控制不再转发给代理的首部字段、管理持久连接 | -| Date | 创建报文的日期时间 | -| Pragma | 报文指令 | -| Trailer | 报文末端的首部一览 | -| Transfer-Encoding | 指定报文主体的传输编码方式 | -| Upgrade | 升级为其他协议 | -| Via | 代理服务器的相关信息 | -| Warning | 错误通知 | - -### 请求首部字段 - - - -| 首部字段名 | 说明 | -| :-----------------: | :---------------------------------------------: | -| Accept | 用户代理可处理的媒体类型 | -| Accept-Charset | 优先的字符集 | -| Accept-Encoding | 优先的内容编码 | -| Accept-Language | 优先的语言(自然语言) | -| Authorization | Web 认证信息 | -| Expect | 期待服务器的特定行为 | -| From | 用户的电子邮箱地址 | -| Host | 请求资源所在服务器 | -| If-Match | 比较实体标记(ETag) | -| If-Modified-Since | 比较资源的更新时间 | -| If-None-Match | 比较实体标记(与 If-Match 相反) | -| If-Range | 资源未更新时发送实体 Byte 的范围请求 | -| If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) | -| Max-Forwards | 最大传输逐跳数 | -| Proxy-Authorization | 代理服务器要求客户端的认证信息 | -| Range | 实体的字节范围请求 | -| Referer | 对请求中 URI 的原始获取方 | -| TE | 传输编码的优先级 | -| User-Agent | HTTP 客户端程序的信息 | - -### 响应首部字段 - -| 首部字段名 | 说明 | -| :----------------: | :--------------------------: | -| Accept-Ranges | 是否接受字节范围请求 | -| Age | 推算资源创建经过时间 | -| ETag | 资源的匹配信息 | -| Location | 令客户端重定向至指定 URI | -| Proxy-Authenticate | 代理服务器对客户端的认证信息 | -| Retry-After | 对再次发起请求的时机要求 | -| Server | HTTP 服务器的安装信息 | -| Vary | 代理服务器缓存的管理信息 | -| WWW-Authenticate | 服务器对客户端的认证信息 | - -### 实体首部字段 - -| 首部字段名 | 说明 | -| :--------------: | :--------------------: | -| Allow | 资源可支持的 HTTP 方法 | -| Content-Encoding | 实体主体适用的编码方式 | -| Content-Language | 实体主体的自然语言 | -| Content-Length | 实体主体的大小 | -| Content-Location | 替代对应资源的 URI | -| Content-MD5 | 实体主体的报文摘要 | -| Content-Range | 实体主体的位置范围 | -| Content-Type | 实体主体的媒体类型 | -| Expires | 实体主体过期的日期时间 | -| Last-Modified | 资源的最后修改日期时间 | - -## 具体应用 - -举一点例子即可 - -### Cookies - -HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 - -Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 - -Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。 - -**用途** - -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) -- 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) - -### Session - -除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 - -Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 - -使用 Session 维护用户登录状态的过程如下: - -- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; -- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; -- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; -- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 - -应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 - -### Cookie与Session选择 - -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; -- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; -- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 - -## HTTPS - -HTTP 有以下安全性问题: - -- 使用明文进行通信,内容可能会被窃听; -- 不验证通信方的身份,通信方的身份有可能遭遇伪装; -- 无法证明报文的完整性,报文有可能遭篡改。 - -HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信。 - -通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 - -### 加密 - -#### 对称密钥加密 - -对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 - -- 优点:运算速度快 -- 缺点:无法安全地将密钥传输给通信方 - -#### 非对称密钥加密 - -非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 - -公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 - -非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 - -- 优点:可以更安全地将公开密钥传输给通信发送方; -- 缺点:运算速度慢。 - -### HTTPS采用的加密方式 - -HTTPS 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。 - -确保传输安全过程(其实就是rsa原理): - -1. Client给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。 -2. Server确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。 -3. Client确认数字证书有效,然后生成呀一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给Server。 -4. Server使用自己的私钥,获取Client发来的随机数(Premaster secret)。 -5. Client和Server根据约定的加密方法,使用前面的三个随机数,生成”对话密钥”(session key),用来加密接下来的整个对话过程。 - -![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121192634.png) - -### 认证 - -通过使用 **证书** 来对通信方进行认证。 - -数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。 - -服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 - -进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 - -### HTTPS的缺点 - -- 因为需要进行加密解密等过程,因此速度会更慢; -- 需要支付证书授权的高额费用。 - -## HTTP几个问题 - -### **代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开?** - -在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。所以虽然标准中没有设定,某些服务器对 Connection: keep-alive 的 Header 进行了支持。意思是说,完成这个 HTTP 请求之后,不要断开 HTTP 请求使用的 TCP 连接。这样的好处是连接可以被重新使用,之后发送 HTTP 请求的时候不需要重新建立 TCP 连接,以及如果维持连接,那么 SSL 的开销也可以避免。 - -持久连接:既然维持 TCP 连接好处这么多,HTTP/1.1 就把 Connection 头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。 - -默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。 - -### 一个 TCP 连接可以对应几个 HTTP 请求? - -如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。 - -### 一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)? - -HTTP/1.1 存在一个问题,单个 TCP 连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。 - -在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的。在 HTTP2 中由于 Multiplexing 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行。 - -那么在 HTTP/1.1 时代,浏览器是如何提高页面加载效率的呢?主要有下面两点: - -- 维持和服务器已经建立的 TCP 连接,在同一连接上顺序处理多个请求。 -- 和服务器建立多个 TCP 连接。 - -### 为什么有的时候刷新页面不需要重新建立 SSL 连接? - -TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。 - -### 浏览器对同一 Host 建立 TCP 连接到数量有没有限制? - -假设我们还处在 HTTP/1.1 时代,那个时候没有多路传输,当浏览器拿到一个有几十张图片的网页该怎么办呢?肯定不能只开一个 TCP 连接顺序下载,那样用户肯定等的很难受,但是如果每个图片都开一个 TCP 连接发 HTTP 请求,那电脑或者服务器都可能受不了,要是有 1000 张图片的话总不能开 1000 个TCP 连接吧,你的电脑同意 NAT 也不一定会同意。 - -有。Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。 - -如果图片都是 HTTPS 连接并且在同一个域名下,那么浏览器在 SSL 握手之后会和服务器商量能不能用 HTTP2,如果能的话就使用 Multiplexing 功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个 TCP 连接去获取,但是可以确定的是 Multiplexing 很可能会被用到。 - -如果发现用不了 HTTP2 呢?或者用不了 HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)。那浏览器就会在一个 HOST 上建立多个 TCP 连接,连接数量的最大限制取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求,如果所有的连接都正在发送请求呢?那其他的请求就只能等等了。 \ No newline at end of file diff --git "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 010d0293..00000000 --- "a/Interview/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,25 +0,0 @@ -- [网络7层和4层的区别](https://juejin.im/post/59a0472f5188251240632f92) -- **DNS** - - [DNS是什么?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#dns%E6%98%AF) - - [谈谈DNS解析过程](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B) - - [DNS查询方式?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#%E6%9F%A5%E8%AF%A2%E6%96%B9%E5%BC%8F) - - [DNS负载均衡](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#dns%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1) - - [为什么域名解析用UDP协议?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E7%94%A8udp%E5%8D%8F%E8%AE%AE) - - [为什么区域传送用TCP协议?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-DNS%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%9A%84.md#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8C%BA%E5%9F%9F%E4%BC%A0%E9%80%81%E7%94%A8tcp%E5%8D%8F%E8%AE%AE) -- **HTTP** - - [请求和响应报文](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E8%AF%B7%E6%B1%82%E5%92%8C%E5%93%8D%E5%BA%94%E6%8A%A5%E6%96%87) - - [HTTP请求方法](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#http%E6%96%B9%E6%B3%95) - - [HTTP状态码](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#http%E7%8A%B6%E6%80%81%E7%A0%81) - - [Cookies](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#cookies) - - [Session](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#session) - - [浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E4%BB%A3%E6%B5%8F%E8%A7%88%E5%99%A8%E5%9C%A8%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%BB%BA%E7%AB%8B%E4%BA%86%E4%B8%80%E4%B8%AA-tcp-%E8%BF%9E%E6%8E%A5%E5%90%8E%E6%98%AF%E5%90%A6%E4%BC%9A%E5%9C%A8%E4%B8%80%E4%B8%AA-http-%E8%AF%B7%E6%B1%82%E5%AE%8C%E6%88%90%E5%90%8E%E6%96%AD%E5%BC%80%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E4%B8%8B%E4%BC%9A%E6%96%AD%E5%BC%80) - - [一个 TCP 连接可以对应几个 HTTP 请求?]()如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。 - - [一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E4%B8%80%E4%B8%AA-tcp-%E8%BF%9E%E6%8E%A5%E4%B8%AD-http-%E8%AF%B7%E6%B1%82%E5%8F%91%E9%80%81%E5%8F%AF%E4%BB%A5%E4%B8%80%E8%B5%B7%E5%8F%91%E9%80%81%E4%B9%88%E6%AF%94%E5%A6%82%E4%B8%80%E8%B5%B7%E5%8F%91%E4%B8%89%E4%B8%AA%E8%AF%B7%E6%B1%82%E5%86%8D%E4%B8%89%E4%B8%AA%E5%93%8D%E5%BA%94%E4%B8%80%E8%B5%B7%E6%8E%A5%E6%94%B6) - - [为什么有的时候刷新页面不需要重新建立 SSL 连接?]()TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。 - - [浏览器对同一Host建立TCP连接到数量有没有限制?](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E5%90%8C%E4%B8%80-host-%E5%BB%BA%E7%AB%8B-tcp-%E8%BF%9E%E6%8E%A5%E5%88%B0%E6%95%B0%E9%87%8F%E6%9C%89%E6%B2%A1%E6%9C%89%E9%99%90%E5%88%B6) -- **HTTPS** - - [HTTPS是什么](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#https) - - [对称密钥加密](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E5%AF%B9%E7%A7%B0%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86) - - [非对称密钥加密](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86) - - [HTTPS采用的加密方式](https://github.com/DreamCats/JavaBooks/blob/master/Interview/network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86-http%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF.md#https%E9%87%87%E7%94%A8%E7%9A%84%E5%8A%A0%E5%AF%86%E6%96%B9%E5%BC%8F) - - [HTTPS的缺点]()因为需要进行加密解密等过程,因此速度会更慢;需要支付证书授权的高额费用。 \ No newline at end of file diff --git "a/Interview/redis/Redis-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" "b/Interview/redis/Redis-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 2ae3bb10..00000000 --- "a/Interview/redis/Redis-\351\235\242\350\257\225\345\270\270\350\247\201\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,261 +0,0 @@ -## 引言 - -> Redis在大型项目中也是经常用到的非关系型数据库,面试官也挺喜欢问的。 - - - -## 常见问题 - -### redis是什么玩意? - -简单来说redis就是一个数据库,不过与传统数据库不同的是redis的数据库是存在内存中,所以读写速度非常快,因此redis被广泛应用于缓存方向。另外,redis也经常用来做分布式锁,redis提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 - -### 为什么要用redis/为什么要用缓存? - -1. **高性能**:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可! -2. **高并发**:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。 - -### 使用Redis有哪些好处? - -1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) -2. 支持丰富数据类型,支持string,list,set,sorted set,hash -3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 -4. 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 -5. 等等... - -### 为什么要用 redis 而不用 map/guava 做缓存? - -缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是 轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓 存不具有一致性。 - -使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致 性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。 - -### redis相比memcached有哪些优势? - -1. memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 -2. redis的速度比memcached快很多 -3. redis可以持久化其数据 - -### redis的线程模型? - -redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。 - -文件事件处理器的结构包含 4 个部分: - -- 多个 socket -- IO多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。 - -### redis常见性能问题和解决方案 - -1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 -2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 -3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 -4. 尽量避免在压力很大的主库上增加从库 -5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... - -### redis 常见数据结构以及使用场景分析 - -#### String - -> 常用命令: set,get,decr,incr,mget 等。 - -String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。 - -#### Hash - -> 常用命令: hget,hset,hgetall 等。 - -Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅 仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: - -```json -key=JavaUser293847 -value={ “id”: 1, “name”: “SnailClimb”, “age”: 22, “location”: “Wuhan, Hubei” } -``` - -#### List - -> 常用命令: lpush,rpush,lpop,rpop,lrange等 - -list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表, 消息列表等功能都可以用Redis的 list 结构来实现。 - -Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 - -另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功 能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 - -#### Set - -> 常用命令: sadd,spop,smembers,sunion 等 - -set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 - -当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 - -比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:`sinterstore key1 key2 key3`将交集存在key1内 - -#### Sorted Set - -> 常用命令: zadd,zrange,zrem,zcard等 - -和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 - -举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维 度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 - -### redis 设置过期时间 - -Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库, 这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统 的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。 - -我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的 时间。 - -**定期删除+惰性删除。** - -- 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删 除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所 有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! -- 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这 就是所谓的惰性删除,也是够懒的哈! - -如果定期删除漏掉了很多过期 key,然后你也没及时去查, 也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题 呢? - -**redis 内存淘汰机制。** - -### MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 - -redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略: - -- voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 -- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 -- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 -- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 -- no-enviction(驱逐):禁止驱逐数据 - -### Memcache与Redis的区别都有哪些? - -1. 存储方式 - - 1. Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 - 2. Redis有部份存在硬盘上,这样能保证数据的持久性。 - -2. 数据支持类型 - - 1. Memcache对数据类型支持相对简单。String - 2. Redis有复杂的数据类型。Redis不仅仅支持简单的k/v类型的数据,同时还提供 list,set,zset,hash等数据结构的存储。 - -3. 使用底层模型不同 - - 1. 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 - 2. Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 - -4. 集群模式 - - memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前 是原生支持 cluster 模式的. - -5. Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。 - -### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以 进行恢复) - -很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机 器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 - -Redis不同于Memcached的很重一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**.这两种方法各有千 秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 - -**快照(snapshotting)持久化(RDB)** - -Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。 - -快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置: - -```yaml -save 900 1 #在900秒(15分钟)之后,如果至少有1个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参数开启:`appendonly yes` - -开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的 保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。 - -在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: - -```yaml -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 -appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 -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文件进行任伺读 入、分析或者写入操作。 - -在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期 间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容 追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的 AOF文件,以此来完成AOF文件重写操作 - -### Redis 事务 - -Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然 后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令 请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。 - -在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性 (Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务 也具有持久性(Durability)。 - -### Redis 常见的性能问题都有哪些?如何解决? - -1. Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。 -2. Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 -3. Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 -4. Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内 - -### Redis的同步机制了解么? - -主从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。 - -### 是否使用过Redis集群,集群的原理是什么? - -Redis Sentinel着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 - -Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 - -### 缓存雪崩和缓存穿透问题解决方案 - -#### 缓存雪崩 - -缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩 掉。 - -- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。 -- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉 -- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存 - -#### 缓存穿透 - -一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量 请求而崩掉。 - -解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈 希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压 力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存 在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 - -### 如何解决 Redis 的并发竞争 Key 问题 - -所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺 序不同,这样也就导致了结果的不同! - -推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问 题,不要使用分布式锁,这样会影响性能) - -基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的 与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有 序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁 无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。 - -在实践中,当然是从以可靠性为主。所以首推Zookeeper。 - -### 如何保证缓存与数据库双写时的数据一致性? - -你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如 何解决一致性问题? - -一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的 情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致 的情况 - -串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 \ No newline at end of file diff --git "a/Interview/spring/Spring\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Interview/spring/Spring\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 92042789..00000000 --- "a/Interview/spring/Spring\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,18 +0,0 @@ -- [参考这位大佬总结的,挺好的](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions) 其实专注一个参考资料,认真备面就完全ok -- [什么是Spring框架](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_1-%e4%bb%80%e4%b9%88%e6%98%af-spring-%e6%a1%86%e6%9e%b6) -- [列举一些重要的Spring模块](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_2-%e5%88%97%e4%b8%be%e4%b8%80%e4%ba%9b%e9%87%8d%e8%a6%81%e7%9a%84spring%e6%a8%a1%e5%9d%97%ef%bc%9f) -- [@RestController vs @Controller](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_3-restcontroller-vs-controller) -- **@ResponseBody的作用**:**注解的作用是将 Controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。** -- [谈谈自己对于Spring IoC和AOP的理解](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_41-%e8%b0%88%e8%b0%88%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-ioc-%e5%92%8c-aop-%e7%9a%84%e7%90%86%e8%a7%a3) -- [SpringAOP和AspectAOP有什么区别](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_41-%e8%b0%88%e8%b0%88%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-ioc-%e5%92%8c-aop-%e7%9a%84%e7%90%86%e8%a7%a3) -- [Spring中的bean的作用域有哪些](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_51-spring-%e4%b8%ad%e7%9a%84-bean-%e7%9a%84%e4%bd%9c%e7%94%a8%e5%9f%9f%e6%9c%89%e5%93%aa%e4%ba%9b) -- [Spring中的单例bean的线程安全问题了解吗](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_52-spring-%e4%b8%ad%e7%9a%84%e5%8d%95%e4%be%8b-bean-%e7%9a%84%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e9%97%ae%e9%a2%98%e4%ba%86%e8%a7%a3%e5%90%97%ef%bc%9f) -- [@Component和@bean的区别是什么](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_53-component-%e5%92%8c-bean-%e7%9a%84%e5%8c%ba%e5%88%ab%e6%98%af%e4%bb%80%e4%b9%88%ef%bc%9f) -- [将一个类声明为Spring的bean的注解有哪些](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_54-%e5%b0%86%e4%b8%80%e4%b8%aa%e7%b1%bb%e5%a3%b0%e6%98%8e%e4%b8%baspring%e7%9a%84-bean-%e7%9a%84%e6%b3%a8%e8%a7%a3%e6%9c%89%e5%93%aa%e4%ba%9b) -- [Spring中的bean生命周期-一](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_55-spring-%e4%b8%ad%e7%9a%84-bean-%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f) -- [Spring中的bean生命周期-二](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringBean) -- [说说自己对于SpringMVC了解](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_61-%e8%af%b4%e8%af%b4%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-mvc-%e4%ba%86%e8%a7%a3) -- [SpringMVC工作原理了解吗](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_62-springmvc-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86%e4%ba%86%e8%a7%a3%e5%90%97) -- [Spring框架中用到了哪些设计模型-一](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_7-spring-%e6%a1%86%e6%9e%b6%e4%b8%ad%e7%94%a8%e5%88%b0%e4%ba%86%e5%93%aa%e4%ba%9b%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%ef%bc%9f) -- [Spring框架中用到了哪些设计模型-二](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring-Design-Patterns) - \ No newline at end of file diff --git a/README.md b/README.md index e9eedb72..a97852e3 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ - [猿辅导面经](/Interview/mianjing/yuanfudao/my.md) ## 我是这样回答的 -> 能力有限,但又想去钻研,面试中该怎么回答较好。(持续总结...) +> 能力有限,但又想去钻研,面试中该怎么回答较好。(其中包含Java、数据库、计网、操作系统、分布式等持续总结...) - [知识分类](/Interview/classify/README.md) ## 刷题系列 @@ -131,27 +131,6 @@ - [JVM面试常见问题](/Jvm/JVM面试常见问题.md) - [JVM整个流程图](https://www.processon.com/view/link/5e1182afe4b009af4a5cc54d) -## Spring系列 -- [切换Spring仓库](https://github.com/DreamCats/spring-books) -- [Spring面试常见问题](/Interview/spring/Spring面试常见问题.md) - -## MyBatis系列 -- [MyBatis面试常见问题](/Interview/mybatis/MyBatis面试常见问题.md) - -## 计算机网络 -- [计算机网络面试常见问题](Interview/network/计算机网络面试常见问题.md) - -## 数据库 -- [MySQL面试常见问题](Interview/mysql/MySQL面试常见问题.md) -- [Redis-面试常见的问题](/Interview/redis/Redis-面试常见的问题.md) - -## 分布式 -- [Dubbo-面试常见问题](/Interview/crazy/个人吐血系列-总结Dubbo.md) -- [消息队列-RocketMQ面试常见问题](Interview/crazy/个人吐血系列-总结RocketMQ.md) - -## Linux -- [linux-基础](/Interview/linux/linux-基础.md) - ## 设计模式 - [面试必问设计模式](/Interview/mode/README.md) From 7d714b972bc549ace72b4058f01490992448051b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Thu, 31 Dec 2020 18:11:43 +0800 Subject: [PATCH 063/366] update README.md --- Interview/classify/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Interview/classify/README.md b/Interview/classify/README.md index 223b554e..15082b19 100644 --- a/Interview/classify/README.md +++ b/Interview/classify/README.md @@ -20,7 +20,7 @@ - [谈谈集合](col/谈谈集合.md) ## Java多线程 -- [进程与线程](thread/进程与线程.md) +- [进程与线程](thread/进程和线程.md) - [线程的创建方式](thread/线程的创建方式.md) - [死锁](thread/死锁.md) - [JMM内存模型](thread/JMM内存模型) From 23d5f02d5e7329879b7a158932b424f4af0c45af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=B0=E5=B3=B0?= Date: Sat, 2 Jan 2021 13:58:01 +0800 Subject: [PATCH 064/366] update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a97852e3..31238ba2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ ## 项目 - [微服务班车在线预约系统](/Interview/bus/README.md) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 +## 设计模式 +- [面试必问设计模式](/Interview/mode/README.md) + ## 基础 - [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) - [Java面试基础知识](/Basics/Java面试基础知识.md) @@ -132,7 +135,6 @@ - [JVM整个流程图](https://www.processon.com/view/link/5e1182afe4b009af4a5cc54d) -## 设计模式 -- [面试必问设计模式](/Interview/mode/README.md) + From ac3072c73d8a22de417d668bedb431e3685b9757 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sat, 2 Jan 2021 14:08:04 +0800 Subject: [PATCH 065/366] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 31238ba2..cfa9d7c9 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ ## 设计模式 - [面试必问设计模式](/Interview/mode/README.md) + ## 基础 - [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) - [Java面试基础知识](/Basics/Java面试基础知识.md) From 1e612f8c5603854c740379690844e16e31144cf8 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sat, 2 Jan 2021 14:45:16 +0800 Subject: [PATCH 066/366] update mianjing --- .DS_Store | Bin 10244 -> 10244 bytes Interview/.DS_Store | Bin 14340 -> 12292 bytes Interview/{mianjing/sql => bishi}/sql.md | 0 Interview/mianjing/.DS_Store | Bin 18436 -> 22532 bytes Interview/mianjing/README.md | 28 + Interview/mianjing/ali/.DS_Store | Bin 6148 -> 0 bytes Interview/mianjing/jingdong/jingdong01.md | 190 ----- Interview/mianjing/jingdong/jingdong02.md | 299 -------- Interview/mianjing/jingdong/jingdong03.md | 196 ----- Interview/mianjing/jingdong/jingdong04.md | 717 ------------------ Interview/mianjing/jingdong/jingdong05.md | 227 ------ Interview/mianjing/meituan/meituan01.md | 127 ---- Interview/mianjing/meituan/meituan02.md | 213 ------ Interview/mianjing/meituan/meituan03.md | 219 ------ Interview/mianjing/meituan/meituan04.md | 568 -------------- Interview/mianjing/meituan/meituan05.md | 508 ------------- .../mianjing/{shein/my.md => myshein.md} | 0 .../mianjing/my\344\272\254\344\270\234.md" | 0 ...my\344\275\234\344\270\232\345\270\256.md" | 0 .../mianjing/my\345\255\227\350\212\202.md" | 0 .../mianjing/my\346\213\233\351\223\266.md" | 0 ...my\347\214\277\350\276\205\345\257\274.md" | 0 .../mianjing/my\347\224\250\345\217\213.md" | 0 .../mianjing/my\347\231\276\345\272\246.md" | 0 .../mianjing/my\347\275\221\346\230\223.md" | 0 .../mianjing/my\350\205\276\350\256\257.md" | 0 .../mianjing/my\350\264\235\345\243\263.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 ...56\351\242\230\346\261\207\346\200\273.md" | 0 README.md | 25 +- 39 files changed, 29 insertions(+), 3288 deletions(-) rename Interview/{mianjing/sql => bishi}/sql.md (100%) create mode 100644 Interview/mianjing/README.md delete mode 100644 Interview/mianjing/ali/.DS_Store delete mode 100644 Interview/mianjing/jingdong/jingdong01.md delete mode 100644 Interview/mianjing/jingdong/jingdong02.md delete mode 100644 Interview/mianjing/jingdong/jingdong03.md delete mode 100644 Interview/mianjing/jingdong/jingdong04.md delete mode 100644 Interview/mianjing/jingdong/jingdong05.md delete mode 100644 Interview/mianjing/meituan/meituan01.md delete mode 100644 Interview/mianjing/meituan/meituan02.md delete mode 100644 Interview/mianjing/meituan/meituan03.md delete mode 100644 Interview/mianjing/meituan/meituan04.md delete mode 100644 Interview/mianjing/meituan/meituan05.md rename Interview/mianjing/{shein/my.md => myshein.md} (100%) rename Interview/mianjing/jingdong/my.md => "Interview/mianjing/my\344\272\254\344\270\234.md" (100%) rename Interview/mianjing/zuoyebang/my.md => "Interview/mianjing/my\344\275\234\344\270\232\345\270\256.md" (100%) rename Interview/mianjing/zijie/my.md => "Interview/mianjing/my\345\255\227\350\212\202.md" (100%) rename Interview/mianjing/zhaoyin/my.md => "Interview/mianjing/my\346\213\233\351\223\266.md" (100%) rename Interview/mianjing/yuanfudao/my.md => "Interview/mianjing/my\347\214\277\350\276\205\345\257\274.md" (100%) rename Interview/mianjing/yongyou/my.md => "Interview/mianjing/my\347\224\250\345\217\213.md" (100%) rename Interview/mianjing/baidu/my.md => "Interview/mianjing/my\347\231\276\345\272\246.md" (100%) rename Interview/mianjing/wangyi/my.md => "Interview/mianjing/my\347\275\221\346\230\223.md" (100%) rename Interview/mianjing/tx/my.md => "Interview/mianjing/my\350\205\276\350\256\257.md" (100%) rename Interview/mianjing/beike/my.md => "Interview/mianjing/my\350\264\235\345\243\263.md" (100%) rename "Interview/mianjing/jingdong/\344\272\254\344\270\234\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\344\272\254\344\270\234\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/zijie/\345\255\227\350\212\202\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\345\255\227\350\212\202\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/zhaoyin/\346\213\233\351\223\266\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\346\213\233\351\223\266\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/pinxx/\346\213\274\345\244\232\345\244\232\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\346\213\274\345\244\232\345\244\232\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/yuanfudao/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\214\277\350\276\205\345\257\274\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/baidu/\347\231\276\345\272\246\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\231\276\345\272\246\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/wangyi/\347\275\221\346\230\223\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\275\221\346\230\223\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/meituan/\347\276\216\345\233\242\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\347\276\216\345\233\242\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/tx/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\350\205\276\350\256\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/yuanjing/\350\277\234\346\231\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\350\277\234\346\231\257\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) rename "Interview/mianjing/ali/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" => "Interview/mianjing/\351\230\277\351\207\214\346\211\200\346\234\211\351\227\256\351\242\230\346\261\207\346\200\273.md" (100%) diff --git a/.DS_Store b/.DS_Store index 2023ea2acc66a7c539b2ffbb9da0f27c8cf0b037..97d45973f8010b8c6a0c9cea3c335fb26640d065 100644 GIT binary patch delta 78 zcmZn(XbIS$E5LYsvaL*mx>$9!u7Qq%g;}kRLbbUOkZEpVSzF7=A+Bm@>zR;SSyf$A hTQ`03bpd(C8I#{jh;8N-{KdDKRpAfIW@b@lW&pjw7?1z} delta 56 zcmZn(XbIS$D=^tgMud@bvcF6QKPP9rfJAk*iIK66g0Y3khe-UP80KDiBMF0Q* diff --git a/Interview/.DS_Store b/Interview/.DS_Store index b71c7eb5a33e7373d97499718958e31083ca0f0d..06ae7ad5195adc527a8273e7754aee242508f005 100644 GIT binary patch delta 405 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 DNfIHq diff --git a/Interview/mianjing/README.md b/Interview/mianjing/README.md new file mode 100644 index 00000000..f278de2a --- /dev/null +++ b/Interview/mianjing/README.md @@ -0,0 +1,28 @@ +> 说明:秋招后期,有些面试我没有总结,大同小异,仅供参考,可能自己想偷懒了,还有一些有2面3面的,我也懒的总结了,工作量不小,不过大概差不多,面经就是需要复盘,查漏补缺,面试官问的大部分跟你的简历有关系,从简历问的又深又广,不仅仅局限于简历哦,提示:这里的面经是关于Java后端的。 + +## 牛客网面经 + +- [拼多多所有问题汇总](拼多多所有问题汇总.md) +- [京东所有问题汇总](京东所有问题汇总.md) +- [美团所有问题汇总](拼多多所有问题汇总.md) +- [阿里所有问题汇总](阿里所有问题汇总.md) +- [百度所有问题汇总](百度所有问题汇总.md) +- [字节所有问题汇总](字节所有问题汇总.md) +- [招银所有问题汇总](招银所有问题汇总.md) +- [网易所有问题汇总](网易所有问题汇总.md) +- [远景所有问题汇总](远景所有问题汇总.md) +- [猿辅导所有问题汇总](猿辅导所有问题汇总.md) + +## 自己 + +- [shein](myshein.md) +- [京东](my京东.md) +- [作业帮](my作业帮.md) +- [字节](my字节.md) +- [招银](my招银.md) +- [猿辅导](my猿辅导.md) +- [用友](my用友.md) +- [百度](my百度.md) +- [网易](my网易.md) +- [腾讯](my腾讯.md) +- [贝壳](my贝壳.md) \ No newline at end of file diff --git a/Interview/mianjing/ali/.DS_Store b/Interview/mianjing/ali/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0SynchronizedList->CopyOnWriteArrayList -- SynchronizedSet->CopyOnWriteArraySet -- SynchronizedMap->ConcurrentHashMap - -### 线程的创建方式 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### 堆排序的实现过程(唯一的一道算法题) -```java -public class HeapSort { - public static void heapSort(int[] arr){ - if (arr == null || arr.length < 2) return; - for (int i = 0; i < arr.length; i++) { - heapInsert(arr, i); // 依次从0~i形成大根堆 - } - int heapSize = arr.length; - swap(arr, 0, --heapSize); - while (heapSize > 0) { - heapify(arr, 0, heapSize); - swap(arr, 0, --heapSize); - } - } - - public static void heapInsert(int[] arr, int index) { - // 建立大根堆 - while (arr[index] > arr[(index - 1) / 2]) { - swap(arr, index, (index - 1) / 2); - index = (index - 1) / 2; - } - } - - public static void heapify(int[] arr, int index, int heapSize) { - // 调整成大根堆 - int left = index * 2 + 1; - while (left < heapSize) { - int largest = left + 1 < heapSize && arr[left + 1] > arr[left] - ? left + 1 - : left; - largest = arr[largest] > arr[index] ? largest : index; - if (largest == index) { - break; // 自己已经是最大了,直接跳出 - } - swap(arr, largest, index); - index = largest; - left = index * 2 + 1; - } - } - - public static void swap(int[] arr, int x, int y) { - int temp = arr[x]; - arr[x] = arr[y]; - arr[y] = temp; - } -} -``` -![HeapSort](http://media.dreamcat.ink/uPic/HeapSort.png) - - -### 垃圾回收算法 各自优缺点 -- 标记-清除 -> 该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: -1. 效率问题 -2. 空间问题(标记清除后会产生大量的不连续碎片) - -- 标记-整理 -> 根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - -1. 慢 -2. 解决了碎片 - -- 复制 -> 为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - -1. 快 - -- 分代收集 -> 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。 - -1. 根据特点选择不同场景下的收集算法,更加灵活。 - -### 数据库的三大范式 -#### 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 -所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。 -如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。 - -#### 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。 -第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 -第二范式(2NF)要求实体的属性完全依赖于主关键字。 -所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。 - -### 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。 -简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。 -例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。 -那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。 -如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 对象存储为json文件,不借助第三方jar包,说说思路。 -递归迭代 -- [参考](https://blog.csdn.net/qq_35893120/article/details/84288551?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4) diff --git a/Interview/mianjing/jingdong/jingdong02.md b/Interview/mianjing/jingdong/jingdong02.md deleted file mode 100644 index 27e6b438..00000000 --- a/Interview/mianjing/jingdong/jingdong02.md +++ /dev/null @@ -1,299 +0,0 @@ -## 京东java实习面经 -- [原文链接](https://www.nowcoder.com/discuss/419373) - -## 一面 -### redis为什么可以这么快,持久化机制 -Redis性能如此高的原因,我总结了如下几点: -- 纯内存操作 -- 单线程 -- 高效的数据结构 -- 合理的数据编码 -- 其他方面的优化 - -在 Redis 中,常用的 5 种数据结构和应用场景如下: -- **String**:缓存、计数器、分布式锁等。 -- **List**:链表、队列、微博关注人时间轴列表等。 -- **Hash**:用户信息、Hash 表等。 -- **Set**:去重、赞、踩、共同好友等。 -- **Zset**:访问量排行榜、点击量排行榜等。 - -[https://zhuanlan.zhihu.com/p/57089960](https://zhuanlan.zhihu.com/p/57089960) - -### redis持久化机制 -Redis支持持久化,而且支持两种不同的持久化操作 -- **快照**(snapshotting,RDB) -- **只追加文件**(append-only file,AOF) - -#### RDB -> Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。 - -#### AOF -> 与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。 -``` -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 -appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 -appendfsync no #让操作系统决定何时进行同步 -``` - -#### Redis4.0 -> Redis 4.0 开始支持 RDB 和 AOF 的混合持久化 - -### spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -#### 大概总结 -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - - xxx - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - -- [http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/](http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/) -- [https://javadoop.com/post/spring-ioc](https://javadoop.com/post/spring-ioc) - -### spring中aop的原理 -> AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 -> Spring AOP就是基于动态代理的 - -大致流程: -1. @EnableAspectJAutoProxy 开启AOP功能 -2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator -3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器 -4. 容器的创建流程: - 1. registerBeanPostProcessors()注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator对象 - 2. finishBeanFactoryInitialization()初始化剩下的单实例bean - 1. 创建业务逻辑组件和切面组件 - 2. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 - 3. 组件创建完之后,判断组件是否需要增强,如果是:切面的通知方法,包装成增强器(advisor),给业务逻辑组件创建一个代理对象(cglib) -5. 执行目标方法: - 1. 代理对象执行目标方法 - 2. CglibAopProxy.intercept() - 1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) - 2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行 - 3. 效果: - - 正常执行:前置通知->目标方法->后置通知->返回通知 - - 出现异常:前置通知->目标方法->后置通知->异常通知 - -### spring中的单例模式和设计模式中的单例模式有什么区别 -- 单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。 -- 意味着: - - 在一个JVM进程中(理论上,一个运行的JAVA程序就必定有自己一个独立的JVM)仅有一个实例,于是无论在程序中的何处获取实例,始终都返回同一个对象。 - - 与此相比,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在一个JVM进程中,如果有多个Spring容器,即使是单例bean,也一定会创建多个实例。 - -## 二面 - -### redis为什么这么快 -略 - -### 介绍redis运行机制 -> redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。 - -文件事件处理器的结构包含 4 个部分: -- 多个 socket -- IO多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。 - -### hashmap多线程下安全问题,如何解决 -比如死循环分析: -- [https://zhuanlan.zhihu.com/p/67915754](https://zhuanlan.zhihu.com/p/67915754) - -### wait()和sleep()区别 -- 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁**。 -- 两者都可以暂停线程的执行。 -- wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 -- wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify或者notifyAll方法。sleep方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。 - -### synchronized和volatile区别 -synchronized的修饰范围: -- 修饰一个代码块 -- 修饰一个方法 -- 修饰一个类 -- 修饰一个静态的方法 - -> 个人的理解是:因为同步关键字Synchronized不能修饰变量(不能直接使用synchronized声明一个变量),不能使变量得到共享,故引入了轻量级的Volatie - -volatile: -- volatile可以修饰变量,共享变量。 -- 保障了共享变量对所有线程的可见性。即可保证在线程A将其修改时,线程B可以立刻得到。 -- 禁止指令重排序 - -### 线程池的参数定义,大小 -先放一波变量定义 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -ThreadPoolExecutor 3 个最重要的参数: -- `corePoolSize`:核心线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize`:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`:当新任务来的时候会先判断当前运行的线程数量是否达到了核心线程数,如果达到的话,信任就会被从存放到队列中中。 - -ThreadPoolExecutor 其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于`corePoolSize`的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了`keepAliveTime`才会被回收销毁 -- `unit`:`keepAliveTime`参数的时间单位 -- `threadFactory`:executor创建新线程的时候会用到。 -- `handle`:饱和策略。关于饱和策略下面单独介绍 -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor`定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出`RejectedExecutionException`来拒绝新任务的处理。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -大小: -> 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。 -> 但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。 - -- **CPU 密集型任务(N+1)**: 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 -- **I/O 密集型任务(2N)**: 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。 - -### java为何设计成单继承 -可以举个例子: -> 在这里有个A类,我们又编写了两个类B类和C类,并且B类和C类分别继承了A类,并且对A类的同一个方法进行了覆盖。 -> 如果此时我们再次编写了一个D类,并且D类以多继承的方式同时集成了B类和C类,那么D类也会继承B类和C类从A类中重载的方法,那么问题来了,D类也开始犯迷糊了,我到底应该哪个继承哪个类中的方法呢, -> 因为类是结构性的,这样就会造成结构上的混乱。这就是多继承的菱形继承问题。 - -可以实现对个接口: -> Java接口是行为性的,也就是说它只是定义某个行为的名称,而具体的行为的实现是集成接口的类实现的, -> 因此就算两个接口中定义了两个名称完全相同的方法,当某个类去集成这两个接口时, -> 类中也只会有一个相应的方法,这个方法的具体实现是这个类来进行编写的,所以并不会出现结构混乱的情况。 - -### 继承和组合有啥区别 -继承: -> 继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。 - -- 父类的内部细节对子类是可见的。 -- 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。 -- 子类与父类是一种高耦合,违背了面向对象思想。 -- 继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可维护性。 -- 不支持动态继承。在运行时,子类无法选择不同的父类。 - -组合: -- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立。 -- 具有较好的可扩展性。 -- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象。 - -组合是has a的关系, 继承是is a的关系 -> 引用一句老话应该更能分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。 - -总结: -- 除非考虑使用多态,否则优先使用组合。 -- 要实现类似”多重继承“的设计的时候,使用组合。 -- 要考虑多态又要考虑实现“多重继承”的时候,使用组合+接口。 - - diff --git a/Interview/mianjing/jingdong/jingdong03.md b/Interview/mianjing/jingdong/jingdong03.md deleted file mode 100644 index c5c2cf8e..00000000 --- a/Interview/mianjing/jingdong/jingdong03.md +++ /dev/null @@ -1,196 +0,0 @@ -## 京东一,二面面经,许愿offer!!! -- [原文](https://www.nowcoder.com/discuss/419687) - -### hashmap数据结构 -#### put -**1.7** -直接上图: -![hashmap-put-1.7](http://media.dreamcat.ink/uPic/hashmap-put-1.7.png) -**1.8** -直接上图: -![hashmap-put-1.8](http://media.dreamcat.ink/uPic/hashmap-put-1.8.png) - -#### 100个结点,hashmap初始容量多少合适? -> Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容,而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 - -#### 为什么1.8要引入红黑树,红黑树是平衡树吗? -- 当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。 -- 是 - -#### 红黑树为什么要分红节点和黑节点? -> 红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多 - -### JVM的内存模型 -1.8之前: -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -1.8: -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -按照1.8总结: -**线程私有**: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享**: -- 堆 -- 直接内存 - -**程序计数器**: -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪儿。 - -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束儿死亡。 - -**Java虚拟机栈**: -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛去`OutOfMemoryError`异常。 - -**本地方法栈**: -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -**堆**: -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 -> Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内部分区](https://user-gold-cdn.xitu.io/2020/3/31/17130c2c0c80892e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) -上图所示的 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"区被填满之后,会将所有对象移动到年老代中。 - -## 二面 -### Thread和Runnable开启线程有什么区别? -- 避免了Java单继承的局限性; -- 把线程代码和任务的代码分离,解耦合(解除线程代码和任务的代码模块之间的依赖关系)。代码的扩展性非常好; - -### 线程池 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -`ThreadPoolExecutor` 3 个最重要的参数: - -- `corePoolSize` : 核心线程数线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize` : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁; -- `unit` : `keepAliveTime` 参数的时间单位。 -- `threadFactory` :`executor` 创建新线程的时候会用到。 -- `handler` :饱和策略。关于饱和策略下面单独介绍一下. - -`ThreadPoolTaskExecutor` 定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出 `RejectedExecutionException来拒绝新任务的处理`。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -### new对象时的过程 -![Java创建对象的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) - -1. 类加载检查,虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 -2. 分配内存,在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 -3. 初始化零值,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 -4. 设置对象头,初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 -5. 执行init方法,在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - -**内存布局**: -在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域: -- 对象头 -- 实例数据 -- 对齐填充 -Hotspot 虚拟机的对象头包括两部分信息 -- 第一部分用于存储对象自身的自身运行时数据(哈希码、GC 分代年龄、锁状态标志等等) -- 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 -实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 - -> 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -**对象的访问方式**: -建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有 -- 使用句柄 -- 直接指针 - -### mysql的优化?索引??联合索引?? -#### 索引 -- 普通索引:仅加速查询 -- 唯一索引:加速查询+列值唯一(可以有null) -- 主键索引:加速查询+列值唯一(不可以有null)+表中只有一个 -- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- 全文索引:对文本的内容进行分词,进行搜索 -- 索引合并:使用多个单列索引组合搜索 -- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 - -#### 优化 -**索引优化**: -- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性 -- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因 -- 帮助服务器避免排序和临时表 -- 将随机IO变为顺序IO -- 可以加速表和表之间的连接,特别是咋实现数据的参考完整性方面的特别喲意义 - -**数据库表优化** -- 范式优化:比如消除冗余 -- 反范式优化:比如适当加冗余等(减少join) -- 限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- 读/写分离:经典的数据库拆分方案,主库负责写,从库负责读 -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间。 - -**联合索引** -> MySQL中的索引可以以一定顺序引用多个列,这种索引叫做联合索引一般的,一个联合索引是一个有序元组,其中各个元素均为数据表的一列。另外,单列索引可以看成联合索引元素数为1的特例。 - -索引匹配的最左原则具体是说,假如索引列分别为A,B,C,顺序也是A,B,C: -``` -- 那么查询的时候,如果查询【A】【A,B】 【A,B,C】,那么可以通过索引查询 -- 如果查询的时候,采用【A,C】,那么C这个虽然是索引,但是由于中间缺失了B,因此C这个索引是用不到的,只能用到A索引 -- 如果查询的时候,采用【B】 【B,C】 【C】,由于没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了 -- 如果查询的时候,采用范围查询,并且是最左前缀,也就是第一列索引,那么可以用到索引,但是范围后面的列无法用到索引 -``` - -### 5L,3L的杯子,如何装4L的水?? -5L,3L的杯子,如何装4L的水: -- 5L 桶装满水 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 -- 把 3L 桶的水倒掉 , 由 5L 桶向 3L 桶倒水 -- 把 5L 桶装满 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 diff --git a/Interview/mianjing/jingdong/jingdong04.md b/Interview/mianjing/jingdong/jingdong04.md deleted file mode 100644 index fbc667cc..00000000 --- a/Interview/mianjing/jingdong/jingdong04.md +++ /dev/null @@ -1,717 +0,0 @@ -## 京东Java春季实习 -- [原文链接](https://www.nowcoder.com/discuss/413662) - -## 一面 - -### hashcode和equals方法 -#### == -> 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。 - -#### equals -> 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖equals方法。则通过equals比较该类的两个对象时,等价于通过"=="比较这两个对象。 -- 情况2:类覆盖了equals方法。一般,我们都覆盖了equals方法来比较两个对象的内容呢是否相相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 -- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 -- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前你引用。如果没有就在常量池中重新创建哪一个String对象。 - -#### hashcode -> 它的作用是获取哈希吗,也称为散列码,它实际上是返回一个int整数。这个哈希吗的作用是确定该对象在哈希表中的索引位置。hashcode定义在jdk的object.java中,这就意味着Java中的任何类都包涵hashcode函数。 - -#### 为什么要用hashcode -我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: -> 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较, -> 如果没有相符的hashcode,HashSet会假设对象没有重复出现。 -> 但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。 -> 如果两者相同,HashSet 就不会让其加入操作成功。 -> 如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - -### 接口和抽象类的区别 -1. 接口的方法默认是public,所有方法在接口中不能有实现(除了java8),而抽象类可以有非抽象的方法。 -2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 -3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过implement关键字扩展多个接口。 -4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰) -5. 从设计层面来讲,抽象是对类的抽象,是一种能模板设计,而接口是对行为的抽象, 是一种行为的规范。 - -### 重写和重载的区别 - -#### 重载 -> 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 - -#### 重写 -> 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。 -> 另外,如果父类方法访问修饰符为private则子类就不能重写该方法。 -> 也就是说方法提供的行为改变,而方法的外貌并没有改变。 - -### 集合类有哪些,有什么区别 -> 容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 - -#### Set -- TreeSet -> 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂为O(1),TreeSet则为O(logN)。 - -- HashSet -> 基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator遍历HashSet得到的结构是不确定的。 - -- LinkedHashSet -> 具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。 - -#### List -- ArrayList -> 基于动态数组实现,支持随机访问。 - -- Vector -> 和ArrayList类似,但它是线程安全的。 - -- LinkedList -> 基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。 - -#### Queue -- LinkedList -> 可以用来实现双向队列。 - -- PriorityQueue -> 基于堆结构实现,可以用它实现优先队列。 - -#### Map -- TreeMap -> 基于红黑树实现 - -- HashMap -> 基于哈希表实现 - -- HashTable - - 和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。 - - 它是遗留类,不应该去使用它。 - - 现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁(1.8前)。 - -- LinkedHashMap -> 使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 - -### 如何实现多线程 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` - -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### JVM内存分布 -#### 1.8之前 -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -#### 1.8及之后 -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -#### 大概总结 -线程私有: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -线程共享: -- 堆 -- 直接内存 - -##### 程序计数器 -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪 -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 - -##### Java虚拟机栈 -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中的局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的祖达深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且线程请求栈时内存用完了,无法再动态扩展了,此时抛出`OutOfMemoryError`异常。 - -##### 本地方法栈 -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -##### 堆 -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 - Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内存分区](http://media.dreamcat.ink/uPic/堆内存分区.png) - -- 上图所示的 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"区被填满之后,会将所有对象移动到年老代中。 - -### 讲一讲Spring原理 -谈Spring原来,不得不谈Ioc了 -> IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC在其他语言中也有应用,并非Spring持有。 -> IoC容器是Spring用来实现IoC的载体,IC容器实际上就是个Map(key,value),Map中存放的是各种对象。 - -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。 -这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 -IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 -在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。 -如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 - -#### Spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -大概总结: -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - - -### mysql的引擎,索引结构,索引有哪些类型? -#### 数据库引擎innodb与myisam的区别 -InnoDB: -- 是MySQL默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其他存储引擎。 -- 实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+间隙锁(Next-Key Locking)防止幻影读。 -- 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 -- 支持真正的在线热备份。其他存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -MyISAM: -- 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括压缩表、空间数据索引等。 -- 不支持事务。 -- 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -#### 索引类型 -- FULLTEXT - 即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- HASH - 由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 - HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- BTREE - BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- RTREE - RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 - 相对于BTREE,RTREE的优势在于范围查找。 - -#### B+树 -B+ 树是一种树数据结构,通常用于关系型数据库(如Mysql)和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 -在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。 - -### mysql 的共享锁和排它锁 - -#### 共享锁 -> 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 - -#### 排它锁 -> 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。 - -### mybatis如何防止sql注入 -**首先看一下下面两个sql语句的区别:** -```mysql - - SELECT - sc.uuid , - sc.begin_date , - sc.begin_time , - sc.bus_id , - sc.bus_status, - sc.seat_status - FROM - sb_count sc - ${ew.customSqlSegment} - -``` - -### getCountDetailById -> 查询场次详情信息 -```java -// 自定义查询,涉及到联表查询 -QueryWrapper wrapper = new QueryWrapper<>(); -wrapper.eq("sc.uuid", request.getCountId()); -``` -**xml** -```xml - -``` - -### repeatSeats -> 判断座位是否已重复 -```java -public boolean repeatSeats(String seats, Long coundId) { - // 查查数据库, 找到座位字段 - boolean b = false; // false:不重复,true:重复 - try { - Count count = countMapper.selectById(coundId); - // 比如,selectedSeats 是1,2 - // dbSeats:"", - // dbSeats:"1,2,3", - // dbSeats: "4,5" - // 前端传来的selectedSeats, 前端判断是否为空,要不然后端也判断一下得了 - if (seats.equals("")) { - return true; - } - if (count.getSelectedSeats().equals("")) { - return false; - } - String[] ss = seats.split(","); - String[] cs = count.getSelectedSeats().split(","); - // 这里考虑并发问题 - HashSet hashSet = new HashSet<>(Arrays.asList(cs)); - for (String s : ss) { - if (hashSet.contains(s)) return true; - } - } catch (Exception e) { - e.printStackTrace(); - log.error("selectedSeats", e); - return true; // 异常就算是重复 - } - return b; -} -``` -### addSeats -```java -if (!StrUtil.isEmpty(selectedSeats)) { - // 这里可以优化,字符串拼接,这样的方式爆内存 - // StringBuffer - newSelectedSeats = selectedSeats + "," + newSelectedSeats; -} -``` - -### filterRepeatSeats -> 回退座位 -```java -Count count = countMapper.selectById(coundId); -String[] ss = seats.split(","); -String[] cs = count.getSelectedSeats().split(","); -// 并发问题,注意 -HashSet hashSet = new HashSet<>(Arrays.asList(cs)); -for (String s : ss) { - if (hashSet.contains(s)) { - hashSet.remove(s); - } -} -if (hashSet.isEmpty()) { - count.setSelectedSeats(""); -} -// 考虑了并发 -StringBuffer sb = new StringBuffer(); -for (String s : hashSet) { - sb.append(s); - sb.append(","); -} -// 上面的方案可以用String的replace的方法替换,遍历要回退的座位,依次替换即可 -count.setSelectedSeats(sb.toString()); -countMapper.updateById(count); -``` -看一下String的replace的源码(1.8),感觉遍历还挺多,但的确不用自己去写了 -```java -public String replace(char oldChar, char newChar) { - if (oldChar != newChar) { - int len = value.length; - int i = -1; - char[] val = value; /* avoid getfield opcode */ - - while (++i < len) { - if (val[i] == oldChar) { - break; - } - } - if (i < len) { - char buf[] = new char[len]; - for (int j = 0; j < i; j++) { - buf[j] = val[j]; - } - while (i < len) { - char c = val[i]; - buf[i] = (c == oldChar) ? newChar : c; - i++; - } - return new String(buf, true); - } - } - return this; -} -``` - -## BusSchedule定时器 -> 这里可以使用redis的延时队列或者RocketMQ的消息队列 - -### schedulChangeBusStatus - -```java -/** - * 每天上午7点到晚上21点,每隔30分钟执行一次 - */ -@Scheduled(cron = "0 0/30 7-21 * * ?") -private void schedulChangeBusStatus() { - log.warn("schedulChangeBusStatus执行"); - busService.schedulChangeBusStatus(); -} -``` -看一下业务逻辑 -```java -public void schedulChangeBusStatus() { - // 获取时间 - String currTime = DateUtil.getHours(); - // 获取日期 - String day = DateUtil.getDay(); - log.warn("schedulChangeBusStatus->目前时间:" + currTime); - log.warn("schedulChangeBusStatus->目前时间:" + day); - System.out.println("目前时间:"+ currTime); - System.out.println("目前时间:"+ day); - QueryWrapper queryWrapper = new QueryWrapper<>(); - // 先取出beingtime和now相等的表或者end_time和now相等到表 - // 1. 取出当天 - // 2. 取出开始时间或者结束时间符合当前时间 - queryWrapper - .eq("begin_date", day) // 取出当天 - .and(o -> o.eq("begin_time", currTime) // 当前时间 - .or() - .eq("end_time", currTime)); - List counts = countMapper.selectList(queryWrapper); - log.warn("schedulChangeBusStatus->查询到的:" + counts.toString()); - // 开始作妖 - for (Count count : counts) { - String busStatus = count.getBusStatus(); - String beginTime = count.getBeginTime(); - String endTime = count.getEndTime(); - if (currTime.equals(beginTime)) { - if (busStatus.equals("0")) { // 沙河空闲 - count.setBusStatus("2"); // 沙河->清水河 - } - if (busStatus.equals("1")) { // 清水河空闲 - count.setBusStatus("3"); // 清水河->沙河 - } - count.setSelectedSeats(""); // 清空座位 - } - if (currTime.equals(endTime)) { - if (busStatus.equals("2")) { // 沙河->清水河 - count.setBusStatus("1"); // 清水河空闲 - } - if (busStatus.equals("3")) { // 清水河->沙河 - count.setBusStatus("0"); // 沙河空闲 - } - } - System.out.println("修改的:" + count); - log.warn("schedulChangeBusStatus->修改的:" + count); - // 写入数据库 - countMapper.updateById(count); - } - // 删缓存,这里非常重要...不删后果很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} -``` -> 这里每隔30分钟扫库进行一次io,这里可以有优化... 假如我使用Redis的延迟队列 -1. 在用定时器添加场次的时候,可以将这些场次存入Redis的zset的队列中,元素为场次ID,元素对应的score是出发时间,这样就有17个(定时器每天凌晨添加17个) -2. 还是用定时器轮询,采用每隔半小时轮询一次,我们取出队列中当前时间大于队列中权重的时间的场次ID,开始进行业务逻辑判断(更改场次状态) -3. 更改过后,删除或者更改该场次的score的时间(结束时间) -4. 以此类推,如果是结束时间的话,直接删除该场次id和score。 - -### addCounts -> 这个项目,没有后台,因此场次需要定时器添加即可 -```java -/** - * 每天凌晨0点2分执行 - */ -@Scheduled(cron = "0 2 0 * * ? ") -private void addCounts(){ - log.warn("addCounts执行"); - busService.addCounts(); -} -``` -具体的业务逻辑: -```java -public void addCounts() { - // 获取日期 - String day = DateUtil.getDay(); - // 获取前17个场次 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.last("limit 17"); - List counts = countMapper.selectList(wrapper); - // 开始修改 这里可以用java8 的特性, 还不是很熟悉,后期优化一下 - for (Count count : counts) { - // 更改日期 - count.setBeginDate(day); - // 更改uuid - count.setUuid(UUIDUtils.flakesUUID()); - // 清空座位 - count.setSelectedSeats(""); - // 将走位状态清零 - count.setSeatStatus("0"); - // 插入 - countMapper.insert(count); - } - - // 删缓存,不删后果依然很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} - -``` diff --git "a/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" deleted file mode 100644 index be1fc864..00000000 --- "a/Java/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 用户服务 - - -## UserController - -### checkUsername -> 这个我就不需要再说了,这个没啥可说的 - - -### register -> 这个注意请求体的参数即可,也没什么可说的 - - -### getUserById -> 注意缓存是否存在,存在直接返回即可,没有就调用业务`getUserById`获取结果 - -### updateUserInfo -> 注意请求体的参数即可,并且若是更新数据,那么自然要删除UserInfo的缓存,保持数据一致,写不写缓存这一点都行,下次请求getUserById会写缓存。 - -### logout -> 注意redis删除缓存即可 - - -## UserServiceImpl - -### checkUsername -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 查询user_name列与getUsername相匹配的数据 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -``` - -### regsiter -```java -// 密码采用了md5加密 -String md5Password = MD5Util.encrypt(user.getUserPwd()); -user.setUserPwd(md5Password); -``` - -### login -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 根据user_name查询用户是否存在 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -if (user != null && user.getUuid() > 0) { - // 用户不为空,用户id > 0 - String md5Password = MD5Util.encrypt(request.getPassword()); - if (user.getUserPwd().equals(md5Password)) { - // 数据库密码和所加密的md5密码一致 - res.setUserId(user.getUuid()); - res.setCode(SbCode.SUCCESS.getCode()); - res.setMsg(SbCode.SUCCESS.getMessage()); - } -} -``` - -### getUserById -```java -//根据id查询用户 -User user = userMapper.selectById(request.getId()); -UserDto userDto = userConverter.User2Res(user); -``` - -### updateUserInfo -```java -// 这里一采用了mapstruct映射,有兴趣的可以去网上找资料学习学习 -User user = userConverter.res2User(request); -``` - -## 总结 - -用户服务,没有什么比较复杂的业务,复杂的业务在订单服务、场次服务、支付服务、 diff --git "a/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" "b/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" deleted file mode 100644 index 857d0fbe..00000000 --- "a/Java/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" +++ /dev/null @@ -1,282 +0,0 @@ -# 订单服务 -## OrderController - -### getNoTakeOrdersById -> 未乘坐订单 - -```java -if (redisUtils.hasKey(key)) { - // redis是否有该缓存 - Object obj = redisUtils.get(key); - NoTakeBusResponse response = (NoTakeBusResponse) obj; - for (NoTakeDto noTakeDto : response.getNoTakeDtos()) { - // 如果场次发车时间-当前时间是大0的,那么说明已经发车了 - String beginTime = noTakeDto.getBeginTime(); - if (beginTime.compareTo(DateUtil.getHours()) <= -1) { - // 删掉当前缓存 - redisUtils.del(key); - // 重新获取最新的数据 - response = orderService.getNoTakeOrdersById(request); - // 写缓存 - redisUtils.set(key, response, RedisConstants.NO_TAKE_OREDERS_EXPIRE.getTime()); - return new ResponseUtil().setData(response); - } - } - log.warn("getNoTakeOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` - -### getNoPayOrdersById -> 获取未支付订单接口 -```java -// 从redis中是否有缓存 -if (redisUtils.hasKey(key)) { - // 有就获取 - Object obj = redisUtils.get(key); - log.warn("getNoPayOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` -### getEvaluateOrdersById -> 根据评价状态获取用户订单接口 -略 - -### addOrder -> 添加订单接口 - -```java -String countKey = RedisConstants.COUNT_DETAIL_EXPIRE.getKey() + request.getCountId(); -// 座位缓存失效 -if (redisUtils.hasKey(countKey)) { - redisUtils.del(countKey); -} -String noPayKey = RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey() + userId; -// 未支付列表缓存失效 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -``` -### selectOrderById -> 根据订单id获取详情订单 -略 - -### updateOrderStatus -> 更改订单状态 -```java -// 删掉订单详情缓存 -if (redisUtils.hasKey(selectOrderKey)) { - redisUtils.del(selectOrderKey); -} -// 删除未乘坐缓存 -if (redisUtils.hasKey(noTakeKey)) { - redisUtils.del(noTakeKey); -} -// 删除未支付缓存 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -// 删除评价缓存 -if (redisUtils.hasKey(evaluateKey)) { - redisUtils.del(evaluateKey); -} -``` - -## OrderServiceImpl - -### getNoTakeOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -// 比如5.30 -String day = DateUtil.getDay(); -// 比如20:00 -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户id - .eq("order_status", "1")// 1:已经支付 - .ge("sc.begin_date", day) // 比如订单日期大于等于今天 - .ge("sc.begin_time", hours) // 订单时间大于等于当前时间 - .orderByAsc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getEvaluateOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户di - .eq("order_status", "1") // 状态为1 - // 两种情况: - // 1. 符合当天日期,并且订单场次发车时间小于当前日期 - // 2. 订单场次的发车日期小于当前日期 - .and(o -> o.eq("sc.begin_date", day) - .lt("sc.begin_time", hours) - .or().lt("sc.begin_date", day)) - .eq("evaluate_status", request.getEvaluateStatus()) // 评价状态 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getNoPayOrdersById -```java -IPage noPayDtoIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("so.user_id", request.getUserId()) // 用户id - .eq("so.order_status", "0") // 未支付状态 - .ge("sc.begin_date", day) // 比如,订单场次日期大于当前日期 - .ge("sc.begin_time", hours)// 订单场次时间大于当前日期 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 未支付 -``` - -### addOrder -```java -public AddOrderResponse addOrder(AddOrderRequest request) { - // 判断座位,如果重复,直接退出,否则更新场次的座位信息 - AddOrderResponse response = new AddOrderResponse(); - // 全局orderId - Long orderId = UUIDUtils.flakesUUID(); - // 1。 判断座位,如果重复,直接退出,否则下一步 - // 2。 更新座位,如果没有异常,这是写操作 - // 3。 计算总金额,如果没有异常 - // 4。 添加订单,如果异常,这是写操作 - try { - // 1。 判断座位,如果重复,直接退出,否则下一步 - tag = MqTags.ORDER_SEATS_CANCEL.getTag(); - boolean repeatSeats = busService.repeatSeats(request.getSeatsIds(), request.getCountId()); - if (repeatSeats) { - // b:true 说明重复 - response.setCode(SbCode.SELECTED_SEATS.getCode()); - response.setMsg(SbCode.SELECTED_SEATS.getMessage()); - return response; - } -// CastException.cast(SbCode.SYSTEM_ERROR); - // 2。 更新座位,如果没有异常,这是写操作 - // 用tags来过滤消息 - tag = MqTags.ORDER_ADD_SEATS_CANCLE.getTag(); - boolean addSeats = busService.addSeats(request.getSeatsIds(), request.getCountId()); - if (!addSeats) { - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - // 模拟系统异常 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 3。 计算总金额,如果没有异常 - tag = MqTags.ORDER_CALC_MONEY_CANCLE.getTag(); - String seatIds = request.getSeatsIds(); - Integer seatNumber = seatIds.split(",").length; - Double countPrice = request.getCountPrice(); - Double totalPrice = getTotalPrice(seatNumber, countPrice); - -// CastException.cast(SbCode.SYSTEM_ERROR); - // 4。 添加订单,如果异常,这是写操作 - Order order = orderConvertver.res2Order(request); - order.setOrderPrice(totalPrice); - order.setEvaluateStatus("0"); // 未评价 - order.setOrderStatus("0"); // 未支付 - order.setUuid(orderId); // 唯一id - tag = MqTags.ORDER_ADD_CANCLE.getTag(); - int insert = orderMapper.insert(order);// 插入 不判断了 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 这里就不读了,耗时 -// QueryWrapper wrapper = new QueryWrapper<>(); -// wrapper.eq("so.uuid", order.getUuid()); -// OrderDto orderDto = orderMapper.selectOrderById(wrapper); - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - response.setOrderId(orderId); -// response.setOrderDto(orderDto); - // 这里放redis 未支付缓存,时间前端给定 - redisUtils.set(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + orderId, orderId, request.getExpireTime()); - return response; - } catch (Exception e) { - // 以上操作如果程序都不发生异常的话, 是不会执行这里的代码的 - // 也就是说不会发送回退消息的。 - // 目的是在高并发的情况下,程序内部发生异常,依然高可用 -// e.printStackTrace(); - log.error("订单业务发生异常"); - // 发消息,将座位退回,将订单退回 - MQDto mqDto = new MQDto(); - mqDto.setOrderId(orderId); - mqDto.setCountId(request.getCountId()); - mqDto.setSeatsIds(request.getSeatsIds()); - try { - String key = RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(orderId); - sendCancelOrder(topic,tag, key, JSON.toJSONString(mqDto)); - log.warn("订单回退消息发送成功..." + mqDto); - } catch (Exception ex) { - ex.printStackTrace(); - } - response.setCode(SbCode.SYSTEM_ERROR.getCode()); - response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); - return response; - } -} -``` - -### selectOrderById -> 省略 - -### updateOrderStatus -```java -public OrderResponse updateOrderStatus(OrderRequest request) { - OrderResponse response = new OrderResponse(); - try { - // 获取orderDto - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("so.uuid", request.getUuid()); - OrderDto orderDto = orderMapper.selectOrderById(wrapper); - // 1, 检查状态是否为2 - if (request.getOrderStatus().equals("2")) { - // 说明关闭订单,回退座位 - busService.filterRepeatSeats(orderDto.getSeatsIds(), orderDto.getCountId()); - redisUtils.del(RedisConstants.COUNT_DETAIL_EXPIRE.getKey() - + orderDto.getCountId()); - // 清除场次详情的缓存 - } - if (request.getOrderStatus().equals("1")) { - // 说明已经支付,删掉5分钟的订单缓存 - redisUtils.del(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + request.getUuid()); - } - Order order = orderConvertver.res2Order(request); - // 更新状态 - orderMapper.updateById(order); - // 暂时就不获取了 - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - redisUtils.del(RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey()+order.getUserId()); - redisUtils.del(RedisConstants.SELECT_ORDER_EXPIRE.getKey() + request.getUuid()); - } catch (Exception e) { - log.error("updateOrderStatus", e); - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - return response; -} -``` - -### deleteOrderById -> 省略 - -### sendCancelOrder -> 发送订单回退消息 - -后边会单独介绍消息队列 \ No newline at end of file diff --git "a/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 7a8d9fa1..00000000 --- "a/Java/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,123 +0,0 @@ -# ArrayList -## 一些特点 - -- ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入`null`元素,底层通过**数组**实现。 -- 每个ArrayList都有一个容量(capacity),表示**底层数组的实际大小**,容器内存储元素的个数不能多于当前容量。 -- 当向容器中添加元素时,如果容量不足,**容器会自动增大底层数组的大小**。 -- Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。 - -## 底层数据结构 -```java -private static final int DEFAULT_CAPACITY = 10; // 默认容量 -transient Object[] elementData; // Object 数组 -private int size; // 大小 -``` - -## 构造函数 - -### 有参 -```java -public ArrayList(int initialCapacity) { - if (initialCapacity > 0) { - this.elementData = new Object[initialCapacity]; - } else if (initialCapacity == 0) { - this.elementData = EMPTY_ELEMENTDATA; // 默认10 - } else { - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); - } -} -``` - -### 无参 -```java -public ArrayList() { - this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 默认10 -} -``` - -## 扩容 -常问,看一下源码吧 - -```java -public void ensureCapacity(int minCapacity) { - int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) - // any size if not default element table - ? 0 - // larger than default for default empty table. It's already - // supposed to be at default size. - : DEFAULT_CAPACITY; - - if (minCapacity > minExpand) { - ensureExplicitCapacity(minCapacity); - } -} -``` -继续看ensureExplicitCapacity -```java -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); // 扩容最关键的方法 -} -``` -其实可以之和看grow(jdk1.8) - -```java - private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); // old + old * 2 就在这... - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -## add -还是看直接看源码方便一些 -```java -public boolean add(E e) { - // 扩容判断 - ensureCapacityInternal(size + 1); // Increments modCount!! 这个参数,起到并发异常作用。 - elementData[size++] = e; // 这一步非原子性,并发容易出错,好几种情况。 下次分析 - return true; -} -``` - -## get -```java -一般获取元素,第一步都要判断索引是否越界 -public E get(int index) { - rangeCheck(index); // 判断给定索引是否越界 - return elementData(index); -} -``` - -## 聊一下并发容易出现的问题 -### 首先是数组越界,举个例子 -1. 列表大小为9,即size=9 -2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 -3. 线程B此时也进入add方法,它和获取的size的值也为9,也开始调用ensureCapacityInternal方法。 -4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。 -5. 线程B也发现需要大小为10,也可以容纳,返回。 -6. 好了,**问题来了哈** -7. 线程A开始进行设置值操作,elementData[size++] = e操作。此时size变为10。 -8. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为9,于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 - -### null值,举个例子 -1. 列表大小为10,即size=0 -2. 线程A开始添加元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。也就是说,线程挂在了`element[0] = e`上。 -3. 接着线程B刚好也要开始天极爱一个值为B的元素,且走到了第一条的操作。此时线程B获取的size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 -4. **问题来了**,其实上面也是问题,覆盖了。。。 -5. 线程A将size的值增加为1 -6. 线程B开始将size的值增加为2 -7. 当你获取1索引的时候,那不就是null了? - -## Fail-Fast机制 -ArrayList也采用了快速失败的机制,**通过记录modCount参数来实现**。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 \ No newline at end of file diff --git "a/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index d8970013..00000000 --- "a/Java/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,158 +0,0 @@ -# BlockingQueue -看一下官方1.8的官方文档 -![已知实现类](https://imgkr.cn-bj.ufileos.com/1c08311d-77a7-4f60-a4dd-2e293f43c8d0.png) - -官方文档告诉我们它们有这些特点: -- BlockingQueue实现被设计为主要用于生产者 - 消费者队列 -- BlockingQueue实现是线程安全的。 所有排队方法使用内部锁或其他形式的并发控制在原子上实现其效果。 -- BlockingQueue方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足: - - 一个抛出异常 - - 返回一个特殊值( null或false ,具体取决于操作) - - 第三个程序将无限期地阻止当前线程,直到操作成功为止 - - 在放弃之前只有给定的最大时限。 - -## 看源码之旅 - -### ArrayBlockingQueue -> 看一下官方文档的解释->一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞。此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。 - -#### 常见变量 -```java -int count; // 队列元素数量 -final ReentrantLock lock; // 内部线程安全性用了ReentrantLock -private final Condition notEmpty; // takes方法的等待条件 -private final Condition notFull; // puts方法的等待条件 -``` -#### 构造方法 -```java -public ArrayBlockingQueue(int capacity, boolean fair) { - if (capacity <= 0) - throw new IllegalArgumentException(); // 这里就不说了 - this.items = new Object[capacity]; // 初始容量 - lock = new ReentrantLock(fair); // fair参数决定是否公平锁 - notEmpty = lock.newCondition(); // 上面已提到 - notFull = lock.newCondition(); // 上面已提到 -} -``` -#### add -```java -public boolean add(E e) { - return super.add(e); // 该方法添加失败抛出异常 -} -// AbstractQueue -public boolean add(E e) { - if (offer(e)) - return true; - else - throw new IllegalStateException("Queue full"); // 在这 -} -``` -**remove就不分析了** - -#### offer -```java -public boolean offer(E e) { - checkNotNull(e); - final ReentrantLock lock = this.lock; // 上面的add其实内部也调用了offer,当时我还觉得奇怪,add没上锁?。 原来offer上了锁的 - lock.lock(); - try { - if (count == items.length) - return false; // 满了,就false - else { - enqueue(e); // 否则,添加即可 - return true; // 返回true,并不会抛出异常 - } - } finally { - lock.unlock(); - } -} -``` -**poll不分析了** - -#### put -```java -public void put(E e) throws InterruptedException { - checkNotNull(e); // 检查是否为空,为空就抛出空异常 - final ReentrantLock lock = this.lock; // 上锁哦 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == items.length) // 满了,挂起阻塞 - notFull.await(); - enqueue(e); // 否则添加 - } finally { - lock.unlock(); - } -} -``` - -#### take -```java -public E take() throws InterruptedException { - final ReentrantLock lock = this.lock; // 上锁 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == 0) - notEmpty.await(); // 没有元素,挂起 - return dequeue(); - } finally { - lock.unlock(); - } -} -``` - -#### 带时间的offer和poll -> 其实就是用了`long nanos = unit.toNanos(timeout);` - - -### LinkedBlockingQueue -> 四个方法和ArrayBlockingQueue的差不多,就不多分析了。它的特点之一在于,如果不指定容量,那么默认是等于Integer.MAX_VALUE。可以看一下源码 - -#### 常见的参数 -```java -/** The capacity bound, or Integer.MAX_VALUE if none */ -private final int capacity; // 一看注释,就晓得了 -// 还有一些常见的和上面的差不多 -``` - -#### 构造方法 -```java -public LinkedBlockingQueue() { - this(Integer.MAX_VALUE); // 在这里, 嘿嘿嘿。 -} -``` - -### LinkedTransferQueue -> 基于链接节点的无界TransferQueue 。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。 - -**四个方法就暂时不提了,大部分操作用了cas并且要关注transfer方法** - -先介绍几个标志参数: -```java -private static final int NOW = 0; // for untimed poll, tryTransfer -private static final int ASYNC = 1; // for offer, put, add -private static final int SYNC = 2; // for transfer, take -private static final int TIMED = 3; // for timed poll, tryTransfer -``` - -```java -// 如果可能,立即将元素转移到等待的消费者。 -public boolean tryTransfer(E e) { - return xfer(e, true, NOW, 0) == null; // xfer的now参数 -} -// 还有一个重载参数的,带时间的。 -``` - -```java -// 将元素传输到消费者,必要时等待。 -public void transfer(E e) throws InterruptedException { - if (xfer(e, true, SYNC, 0) != null) { // SYNC - Thread.interrupted(); // failure possible only due to interrupt - throw new InterruptedException(); - } -} -``` -### PriorityBlockingQueue -> 一看名字就是优先级阻塞队列,它的优先级是由堆实现的,所以该类中有很多堆的方法。源码暂时就不看了,很多都是差不多的。 - -### SynchronousQueue -> 官方文档:每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 个人感觉是生产者生产一个元素,消费者必须消费,生产者才能继续生产。内部也维护了一个TransferQueue,其中部分操作是利用cas。源码就不贴了。有兴趣的可以进去看看哦。 diff --git "a/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 608032e8..00000000 --- "a/Java/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,238 +0,0 @@ -# ConcurrentHashMap -> 1.7和1.8的源码还不太一样,也就是说加锁的方式不一样。 - -## 1.7 -> 分段锁 - -![图示意图](https://i.loli.net/2019/05/08/5cd1d2c5ce95c.jpg) - -### Segment的数据结构 -```java -/** - * Segment 数组,存放数据时首先需要定位到具体的 Segment 中。 - */ -final Segment[] segments; -transient Set keySet; -transient Set> entrySet; -``` - -Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下: - -```java -static final class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - // 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶 - transient volatile HashEntry[] table; // volatile修饰 - transient int count; - transient int modCount; - transient int threshold; - final float loadFactor; - -} -``` -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 `ReentrantLock`。 -- 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 -- **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** - -### put -```java -public V put(K key, V value) { - Segment s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - s = ensureSegment(j); - return s.put(key, hash, value, false); -} -``` -通过key定位到Segment,之后在对应的Segment中进行具体的put -```java -final V put(K key, int hash, V value, boolean onlyIfAbsent) { - HashEntry node = tryLock() ? null : - scanAndLockForPut(key, hash, value); // 1. 加锁处理 - V oldValue; - try { - HashEntry[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry first = entryAt(tab, index); - for (HashEntry e = first;;) { - if (e != null) { - K k; - if ((k = e.key) == key || // 2. 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { // 3. 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 - if (node != null) - node.setNext(first); - else - node = new HashEntry(hash, key, value, first); - int c = count + 1; - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); // 4. 解锁 - } - return oldValue; -} -``` -- 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 - - 尝试获取自旋锁 - - 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 - -总的来说: -- 将当前的Segment中的table通过key的hashcode定位到HashEntry -- 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value -- 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 -- 最后会解除在1中所获取当前Segment的锁。 - -### get -```java -public V get(Object key) { - Segment s; // manually integrate access methods to reduce overhead - HashEntry[] tab; - int h = hash(key); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; - } - } - return null; -} -``` -- 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 -- 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 -- ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** - -## 1.8 -> 1.7 查询遍历链表效率太低。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性 - -### put -```java - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table;;) { // 1. 根据key计算出hashcode - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) // 2. 判断是否需要进行初始化 - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 3. f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功。 - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } - else if ((fh = f.hash) == MOVED) // 4. 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 - tab = helpTransfer(tab, f); - else { - V oldVal = null; - synchronized (f) { // 5. 如果都不满足,则利用synchronized锁写入数据 - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) // 6. 如果数量大于`TREEIFY_THRESHOLD` 则要转换为红黑树。 - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; - } - -``` -- 根据key计算出hashcode -- 判断是否需要进行初始化 -- f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,**利用CAS尝试写入,失败则自旋保证成功。** -- 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 -- 如果都不满足,则利用`synchronized`锁写入数据 -- 如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。 - -### get -```java - public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - int h = spread(key.hashCode()); - if ((tab = table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) - return e.val; - } - else if (eh < 0) - return (p = e.find(h, key)) != null ? p.val : null; - while ((e = e.next) != null) { - if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) - return e.val; - } - } - return null; - } -``` -- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 -- 如果是红黑树那就按照树的方式获取值。 -- 就不满足那就按照链表的方式遍历获取值。 - -## 总结 -1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。 \ No newline at end of file diff --git "a/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a1905e14..00000000 --- "a/Java/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,173 +0,0 @@ -# HashMap -> 谈到这个,真的是面试高频呀,处处问,关键HashMap在jdk1.7和1.8的源码是我不一样的我这边就只放1.8的源码。 - -## 底层结构 - -> HashMap的底层结构是是**数组+链表**。关于为什么是链表,那是因为哈希冲突,采取链表一种方式。 - -## 常见参数 -```java -static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始容量 -static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子0.75 -// 给定的默认容量为16,负载因子为0.75. -// Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容, -// 而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 -static final int TREEIFY_THRESHOLD = 8;// 成为红黑树的阈值,为什么是8? -static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值6 -static final int MIN_TREEIFY_CAPACITY = 64; -// 个人感觉,8是经验值,或者链表为8的时候,性能开始下降,大家知道链表查找慢,当元素过多的时候,非常影响性能的。 -// 因此采取为8的时候转成红黑树。元素过少,其实没必要直接红黑树,元素过少,链表查找的时间也不是很能影响性能。 -// 在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。 -// 6是因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。 -``` - -## hash -```java -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -``` -hash 函数是先拿到通过 key 的 hashcode,是 32 位的 int 值,然后让 hashcode 的高 16 位和低 16 位进行异或操作。这个也叫扰动函数,这么设计有二点原因: -- 一定要尽可能降低 hash 碰撞,越分散越好; -- 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算; - -## put -> 这里介绍1.8的,1.7的其实很简单,被1.8包括了 -```java -final V putVal(int hash, K key, V value, boolean onlyIfAbsent, - boolean evict) { - Node[] tab; Node p; int n, i; - if ((tab = table) == null || (n = tab.length) == 0) // 1. 判断当前桶是否为空,空的就需要初始化(resize中会判断是否进行初始化) - n = (tab = resize()).length; - if ((p = tab[i = (n - 1) & hash]) == null) // 2. 根据当前key的hashcode定位到具体的桶中并判断是否为空,为空则表明没有Hash冲突,就直接在当前位置创建一个新桶 - tab[i] = newNode(hash, key, value, null); - else { - Node e; K k; - if (p.hash == hash && // 3. 如果当前桶有值(Hash冲突),那么就要比较当前桶中的key、key的hashcode与写入的key是否相等,相等就赋值给e,在第8步的时候会统一进行赋值及返回 - ((k = p.key) == key || (key != null && key.equals(k)))) - e = p; - else if (p instanceof TreeNode) // 4. 如果当前桶为红黑树,那就要按照红黑树的方式写入数据 - e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); - else { // 5. 如果是个链表,就需要将当前的key、value封装称一个新节点写入到当前桶的后面形成链表。 - for (int binCount = 0; ; ++binCount) { - if ((e = p.next) == null) { - p.next = newNode(hash, key, value, null); - if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 6. 接着判断当前链表的大小是否大于预设的阈值,大于就要转换成为红黑树 -- - treeifyBin(tab, hash); - break; - } - if (e.hash == hash && // 7. 如果在遍历过程中找到key相同时直接退出遍历。 - ((k = e.key) == key || (key != null && key.equals(k)))) - break; - p = e; - } - } - if (e != null) { // existing mapping for key 8. 如果`e != null`就相当于存在相同的key,那就需要将值覆盖。 - V oldValue = e.value; - if (!onlyIfAbsent || oldValue == null) - e.value = value; - afterNodeAccess(e); - return oldValue; - } - } - ++modCount; - if (++size > threshold) // 9. 最后判断是否需要进行扩容。 - resize(); - afterNodeInsertion(evict); - return null; -} -``` -看代码有点晕乎! - -![put过程](https://imgkr.cn-bj.ufileos.com/b2206341-08ce-4e67-87bd-3a467ccac31e.png) - -1. 判断数组是否为空,为空进行初始化; -2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index; -3. 查看 table[index] 是否存在数据,没有数据就构造一个 Node 节点存放在 table[index] 中; - -4. 存在数据,说明发生了 hash 冲突(存在二个节点 key 的 hash 值一样), 继续判断 key 是否相等,相等,用新的 value 替换原数据(onlyIfAbsent 为 false); - -5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中; -6. 如果不是树型节点,创建普通 Node 加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树; - -7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。 - -## get -```java -public V get(Object key) { - Node e; - return (e = getNode(hash(key), key)) == null ? null : e.value; -} -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - return first; - if ((e = first.next) != null) { - if (first instanceof TreeNode) - return ((TreeNode)first).getTreeNode(hash, key); - do { - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; -} -``` -- 首先将key hash之后取得所定位的桶 -- 如果桶为空,则直接返回null -- 否则判断桶的第一个位置(有可能是链表、红黑树)的key是否为查询的key,是就直接返回value -- 如果第一个不匹配,则判断它的下一个是红黑树还是链表 -- 红黑树就按照树的查找方式返回值 -- 不然就按照链表的方式遍历匹配返回值 - -## 1.8的优化 -1. 数组+链表改成了数组+链表或红黑树; -2. 链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7 将新元素放到数组中,原始节点作为新节点的后继节点,1.8 遍历链表,将元素放置到链表的最后; -3. 扩容的时候 1.7 需要对原数组中的元素进行重新 hash 定位在新数组的位置,1.8 采用更简单的判断逻辑,位置不变或索引+旧容量大小; -4. 在插入时,1.7 先判断是否需要扩容,再插入,1.8 先进行插入,插入完成再判断是否需要扩容; - -## 并发问题 -- HashMap扩容的时候会调用resize()方法,就是这里的并发操作容易在一个桶上形成环形链表 -- 这样当获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。 -- **但是1.7的头插法造成的问题,1.8改变了插入顺序,就解决了这个问题,但是为了内存可见性等安全性,还是需要ConCurrentHashMap** - -### Java 中有 HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全的 Map。 -- HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个数组,粒度比较大 -- Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现 -- ConcurrentHashMap 使用分段锁,降低了锁粒度,让并发度大大提高。(jdk1.8 CAS+ synchronized) - -[死循环分析](https://zhuanlan.zhihu.com/p/67915754) - -## 哈希遍历 - -```java -Iterator> entryIterator = map.entrySet().iterator(); - while (entryIterator.hasNext()) { - Map.Entry next = entryIterator.next(); - System.out.println("key=" + next.getKey() + " value=" + next.getValue()); - } - -Iterator iterator = map.keySet().iterator(); - while (iterator.hasNext()){ - String key = iterator.next(); - System.out.println("key=" + key + " value=" + map.get(key)); - } -``` -- 建议使用第一种,同时可以把key value取出。 -- 第二种还需要通过key取一次key,效率较低。 - -# HashSet -> HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个`private static final Object PRESENT = new Object();`。 HashSet跟HashMap一样,都是一个存放链表的数组。 - -# TreeMap -> TreeMap 是按照 Key 的自然顺序或者 Comprator 的顺序进行排序,内部是通过红黑树来实现。所以要么 key 所属的类实现 Comparable 接口,或者自定义一个实现了 Comparator 接口的比较器,传给 TreeMap 用户 key 的比较。 - -# LinkedHashMap -> LinkedHashMap 内部维护了一个单链表,有头尾节点,同时 LinkedHashMap 节点 Entry 内部除了继承 HashMap 的 Node 属性,还有 before 和 after 用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。 \ No newline at end of file diff --git "a/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 4a9ae73a..00000000 --- "a/Java/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,109 +0,0 @@ -# LinkedList -> LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。 关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。 关于特点,想必大家都知道了。 - -## 底层数据 -> LinkedList底层通过双向链表实现。双向链表的每个节点用内部类Node表示。LinkedList通过**first和last引用分别指向链表的第一个和最后一个元素**。注意这里没有所谓的哑元,当链表为空的时候**first和last都指向null**。 -```java -transient int size = 0; -transient Node first; // 经常用 -transient Node last; // 经常用 -// Node是私有的内部类 -private static class Node { - E item; - Node next; // 这里就不多说了 - Node prev; // - - Node(Node prev, E element, Node next) { - this.item = element; - this.next = next; - this.prev = prev; - } -} -``` -构造函数就不用看了... - -## getFirst和getLast -> 这个挺简单的 -```java -public E getFirst() { - final Node f = first; // 第一个节点 - if (f == null) - throw new NoSuchElementException(); - return f.item; -} -public E getLast() { - final Node l = last; // 最后一个节点 - if (l == null) - throw new NoSuchElementException(); - return l.item; -} -``` - -**removeFirst和removeLast**和上面的源码差不多,挺简单。链表的删除,脑子里画图思考思考就明白了,添加也是一样。 - -## add调用的是`linkLast` -```java -void linkLast(E e) { - final Node l = last; // 要在尾部添加,所以要找last - final Node newNode = new Node<>(l, e, null); // last->e->null - last = newNode; - if (l == null) - first = newNode; // 如果l为空,说明没有链表没有元素,那么first和last都指向e, - else - l.next = newNode; // 否则就将l.next指向新节点 - size++; - modCount++; // 依然存在并发危险哦 -} -``` - -## 看一下remove -```java -public boolean remove(Object o) { - if (o == null) { // 将链表存在null的给移除掉 - for (Node x = first; x != null; x = x.next) { - if (x.item == null) { - unlink(x); - return true; - } - } - } else { - for (Node x = first; x != null; x = x.next) { - if (o.equals(x.item)) { // 否则直接遍历与符合当前对象,unlink即可。 - unlink(x); - return true; - } - } - } - return false; -} -``` -## unlink得看 -> 这个脑子思考一下那个图,其实就明白,很源码的话,也没问题的。 -```java -E unlink(Node x) { - // assert x != null; - final E element = x.item; // 当前值 - final Node next = x.next; // 后 - final Node prev = x.prev; // 前 - - if (prev == null) { // 如果x的前是null,说明,x是首节点,直接让first指向next - first = next; - } else { // x不是首节点,意味着pre是有值哦。 - prev.next = next; // prev.next的箭头指向x的next - x.prev = null; // 将x.prev指向null 双向链表? - } - - if (next == null) { // 同上,说明x是最后节点了 - last = prev; // 让last指向x的prev - } else { - next.prev = prev; // 否则x不是最后节点, 让next.prev 指向prev - x.next = null; // x.next 指向null , 双向链表 - } - - x.item = null; // GC回收 - size--; - modCount++; - return element; -} -``` -**其他几个方法的源码中核心都有这些...** \ No newline at end of file diff --git "a/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" "b/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" deleted file mode 100644 index 8995e5ea..00000000 --- "a/Java/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" +++ /dev/null @@ -1,73 +0,0 @@ -# LRU算法 -> LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 - -## 一般过程 -![过程图](https://pic4.zhimg.com/80/v2-71b21233c615b1ce899cd4bd3122cbab_1440w.jpg) - -- 新数据插入到链表头部; -- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; -- 当链表满的时候,将链表尾部的数据丢弃。 - -再来来个具体的过程 -![具体过程](https://pic3.zhimg.com/80/v2-998b52e7534278b364e439bbeaf61d5e_1440w.jpg) - -1. 最开始时,内存空间是空的,因此依次进入A、B、C是没有问题的 -2. 当加入D时,就出现了问题,内存空间不够了,因此根据LRU算法,内存空间中A待的时间最为久远,选择A,将其淘汰 -3. 当再次引用B时,内存空间中的B又处于活跃状态,而C则变成了内存空间中,近段时间最久未使用的 -4. 当再次向内存空间加入E时,这时内存空间又不足了,选择在内存空间中待的最久的C将其淘汰出内存,这时的内存空间存放的对象就是E->B->D - -## 伪代码实现 - -### 思路 -> 双向链表+HashMap,Java中的LinkedHashMap就实现了该算法。 - -### 定义节点 -```java -class Node { - int key; - int value; - Node pre; - Node next; -} -``` -### 定义HashMap -`HashMap map = new HashMap<>();` - -### get -> 其实挺简单的。 -```java -public int get(int key) { - if (map.containsKey(key)) { - Node n = map.get(key); // 获取内存中存在的值,比如A - remove(n); //使用链表的方法,移除该节点 - setHead(n); //依然使用链表的方法,将该节点放入头部 - return n.value; - } - return -1; -} -``` -- 由于当一个节点通过key来访问到这个节点,那么这个节点就是刚被访问了, -- 就把这个节点删除掉,然后放到队列头,这样队列的头部都是最近访问的, -- 队列尾部是最近没有被访问的。 - -### set -```java -public void set(int key, int value) { - if (map.containsKey(key)) { - Node old = map.get(key); - old.value = value; - remove(old); // 移除旧节点 - setHead(old); // 放到队头 - } else { - Node created = new Node(key, value); - if (map.size() >= capacity) { - map.remove(end.key); // clear该key - remove(end); //链表也是依次 - setHead(created); // 将created放入队头 - } else { - setHead(created); // 如果没满,直接放入队头 - } - map.put(key,created); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" "b/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" deleted file mode 100644 index b0b28954..00000000 --- "a/Java/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 集合总述 -通过查看JDK8的Collection的文档源码: - -![有点凶](https://imgkr.cn-bj.ufileos.com/98753cd6-dba3-4291-83c3-7964b39c5da3.png) - -再看一Map的: - - -![也挺凶](https://imgkr.cn-bj.ufileos.com/0af46521-0eb6-4666-898e-3536cc3b7c4e.png) - -不说了,来个更清晰一点的: - -![清晰uml图](https://www.pdai.tech/_images/java_collections_overview.png) - -因此,容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。关于各自有什么特点,待后边带着源码详细介绍先。 \ No newline at end of file diff --git "a/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 568af31e..00000000 --- "a/Java/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 堆溢出例子 -> 启动参数:` -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` - -```java -public class Test { - public static void main(String[] args) { - List list = new ArrayList<>(); - int i = 0; - while (true) { - list.add(new byte[5*1024*1024]); - System.out.println("分配次数:" + (++i)); - } - } -} -``` -结果: -``` -分配次数:1 -分配次数:2 -分配次数:3 -java.lang.OutOfMemoryError: Java heap space -Dumping heap to java_pid39413.hprof ... -Heap dump file created [16921133 bytes in 0.013 secs] -Exception in thread "main" java.lang.OutOfMemoryError: Java heap space - at Test.Test.main(Test.java:18) -``` \ No newline at end of file diff --git "a/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 90dbf83d..00000000 --- "a/Java/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,14 +0,0 @@ -# 引用计数循环引用小例子 - -```java -public class Test { - public static void main(String[] args) { - Object object1 = new Object(); - Object object2 = new Object(); - object1 = object2; // obj1 引用obj2 - object2 = object1; // obj2 引用obj1 循环引用 - object1 = null; - object2 = null; - } -} -``` \ No newline at end of file diff --git "a/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" "b/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" deleted file mode 100644 index 10e93658..00000000 --- "a/Java/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" +++ /dev/null @@ -1,68 +0,0 @@ -# 查看反编译源码 - -## 例子 -JavaBean.java -```java -public class JavaBean { - int value = 3; - final int value2 = 5; - static int value3 = 8; - static int value4 = -1; - String s = "买"; - - void getValue() { - int temp = 4; - } -} -``` -- 终端输入`javac JavaBean.java`得到JavaBean.class文件 -- 终端继续输入`javap -c JavaBean > JavaBean.txt` - -``` -Compiled from "JavaBean.java" -public class JavaBean { - int value; - - final int value2; - - static int value3; - - static int value4; - - java.lang.String s; - - public JavaBean(); - Code: - 0: aload_0 - 1: invokespecial #1 // Method java/lang/Object."":()V - 4: aload_0 - 5: iconst_3 - 6: putfield #2 // Field value:I - 9: aload_0 - 10: iconst_5 - 11: putfield #3 // Field value2:I - 14: aload_0 - 15: ldc #4 // String 买 - 17: putfield #5 // Field s:Ljava/lang/String; - 20: return - - void getValue(); - Code: - 0: iconst_4 - 1: istore_1 - 2: return - - static {}; - Code: - 0: bipush 8 - 2: putstatic #6 // Field value3:I - 5: iconst_m1 - 6: putstatic #7 // Field value4:I - 9: return -} -``` -暂时就不详细解释了。 - -当然如果查看更信息反编译文件,终端可以输入`javap -p JavaBean > JavaBean.txt` -内容我就不放了,它可以查看更多的字面量等,内容比较多。 - diff --git "a/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 829e98fa..00000000 --- "a/Java/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 模拟栈溢出的小例子 -> 启动参数: `-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` -```java -public class Test { - - public void m() { - System.out.println("stack test overflow..."); - m(); - } - - public static void main(String[] args) { - new T1().m(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" deleted file mode 100644 index afaa720e..00000000 --- "a/Java/codes/modes/\344\273\243\347\220\206\346\250\241\345\274\217.md" +++ /dev/null @@ -1,86 +0,0 @@ -## 代理模式 - -> 所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 - 一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。 - -### 静态代理 - -> 定义主题 - -```java -interface Subject { - void visit(); -} -``` - -> 实现subject的两个类 - -```java -class RealSubject implements Subject { - private String name = "dreamcat"; - @Override - public void visit() { - System.out.println(name); - } -} -``` -> 代理类 -```java -class ProxySubject implements Subject { - - private Subject subject; - - public ProxySubject(Subject subject) { - this.subject = subject; - } - - @Override - public void visit() { - subject.visit(); - } -} -``` -缺点: -> 代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。 - 每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。 - 代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。 - -### 动态代理 -> 动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 - -> 动态代理 - -```java -class DynamicProxy implements InvocationHandler { - - private Object object; - - public DynamicProxy(Object object) { - this.object = object; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Object result = method.invoke(object, args); - return result; - } -} -``` - -### 测试 -```java -public class ProxyMode { - public static void main(String[] args) { - // 静态代理 - ProxySubject proxySubject = new ProxySubject(new RealSubject()); - proxySubject.visit(); - - // 动态代理 - RealSubject realSubject = new RealSubject(); - DynamicProxy dynamicProxy = new DynamicProxy(realSubject); - ClassLoader classLoader = realSubject.getClass().getClassLoader(); - Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy); - subject.visit(); - } -} -``` diff --git "a/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" "b/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" deleted file mode 100644 index d74b210b..00000000 --- "a/Java/codes/modes/\345\215\225\344\276\213\346\250\241\345\274\217.md" +++ /dev/null @@ -1,87 +0,0 @@ -## 单例模式 -> 单例就不多说了 - -### 饿汉 - -```java -public class Singleton { - private static Singleton instance = new Singleton(); - private Singleton(){} - - public static Singleton getInstance() { - return instance; - } -} -``` -### 饿汉变种 - -```java -class Singleton { - private static Singleton instance = null; - static { - instance = new Singleton(); - } - private Singleton() {} - - public static Singleton getInstance() { - return instance; - } -} -``` - -### 懒汉(线程不安全) - -```java -class Singleton { - private static Singleton instance = null; - private Singleton(){} - - public static Singleton getInstance() { - if (instance == null){ - instance = new Singleton(); - } - return instance; - - } -} -``` - -### 懒汉(线程安全,但消耗资源较为严重) - -```java -class Singleton { - private static Singleton instance = null; - - private Singleton() { - } - - public static synchronized Singleton getInstance() { - if (instance == null) { - instance = new Singleton(); - } - return instance; - } -} -``` - -### 懒汉(线程安全,双重校验) - -```java -class Singleton { - private static volatile Singleton instance = null; - - private Singleton() { - } - - public static Singleton getInstance() { - if (instance == null) { - synchronized (Singleton.class) { - if (instance == null) { - instance = new Singleton(); - } - } - } - return instance; - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" "b/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" deleted file mode 100644 index 3ef26dba..00000000 --- "a/Java/codes/modes/\345\267\245\345\216\202\346\250\241\345\274\217.md" +++ /dev/null @@ -1,81 +0,0 @@ -## 工厂模式 -> 大概意思就不要说了,直接举个例子,看例子讲解就知道是什么意思了。 - -### 举例子 - -> 定义一个面条抽象类 - -```java -abstract class INoodles { - /** - * 描述每种面条长什么样的... - */ - public abstract void desc(); -} -``` - -> 定义一份兰州拉面(具体产品) - -```java -class LzNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("兰州拉面,成都的好贵 家里的才5-6块钱一碗"); - } -} -``` - -> 定义一份泡面(程序员挺喜欢的) - -```java -class PaoNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("泡面可还行..."); - } -} -``` - -> 不得不说家乡的杂酱面了,好吃得不得了 - -```java -class ZaNoodles extends INoodles { - - @Override - public void desc() { - System.out.println("杂酱面,嗯? 真香..."); - } -} -``` - -> 重头戏,开面条馆了。(工厂) - -```java -class SimpleNoodlesFactory { - public static final int TYPE_LZ = 1; // 兰州拉面 - public static final int TYPE_PAO = 2; // 泡面撒 - public static final int TYPE_ZA = 3; // 杂酱面 - // 提供静态方法 - public static INoodles createNoodles(int type) { - switch (type) { - case TYPE_LZ:return new LzNoodles(); - case TYPE_PAO:return new PaoNoodles(); - case TYPE_ZA:return new ZaNoodles(); - default:return new ZaNoodles(); - } - } -} -``` - -> 测试 - -```java -public class FactoryMode { - public static void main(String[] args) { - INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_ZA); - noodles.desc(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" deleted file mode 100644 index cb19467b..00000000 --- "a/Java/codes/modes/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" +++ /dev/null @@ -1,63 +0,0 @@ -## 模板方法模式 - -> 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 - -例如: -> 去银行办业务,银行给我们提供了一个模板就是:先取号,排对,办理业务(核心部分我们子类完成),给客服人员评分,完毕。 - 这里办理业务是属于子类来完成的,其他的取号,排队,评分则是一个模板。 - -再例如: -> 去餐厅吃饭,餐厅给提供的一套模板就是:先点餐,等待,吃饭(核心部分我们子类完成),买单 - 这里吃饭是属于子类来完成的,其他的点餐,买单则是餐厅提供给我们客户的一个模板。 - -所以: -> 实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分容易变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。 - - -### 举例子 -> 银行办理业务抽象类 - -```java -abstract class BankTemplateMethod { - // 1. 取号排队 - public void takeNumber() { - System.out.println("取号排队..."); - } - - // 2. 每个子类不同的业务实现,各由子类来实现 - abstract void transact(); - - // 3. 评价 - public void evaluate() { - System.out.println("反馈评价..."); - } - - public void process() { - takeNumber(); - transact(); - evaluate(); - } -} -``` - -> 具体的业务,比如取钱 -```java -class DrawMoney extends BankTemplateMethod { - - @Override - void transact() { - System.out.println("我要取款..."); - } -} -``` - -### 测试 - -```java -public class TemplateMode { - public static void main(String[] args) { - BankTemplateMethod drawMoney = new DrawMoney(); - drawMoney.process(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" "b/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" deleted file mode 100644 index 1513ee9d..00000000 --- "a/Java/codes/modes/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217.md" +++ /dev/null @@ -1,136 +0,0 @@ -## 装饰器模式 - -> 按照单一职责原则,某一个对象只专注于干一件事,而如果要扩展其职能的话,不如想办法分离出一个类来“包装”这个对象,而这个扩展出的类则专注于实现扩展功能。 - 装饰器模式就可以将新功能动态地附加于现有对象而不改变现有对象的功能。 - -> 代理模式专注于对被代理对象的访问; - 装饰器模式专注于对被装饰对象附加额外功能。 - -### 举例子 -> 假设我去买咖啡,首先服务员给我冲了一杯原味咖啡,我希望服务员给我加些牛奶和白糖混合入原味咖啡中。使用装饰器模式就可以解决这个问题。 - -> 咖啡接口 - -```java -interface Coffee { - // 获取价格 - double getCost(); - - // 获取配料 - String getIngredients(); -} -``` - -> 原味咖啡 - -```java -class SimpleCoffee implements Coffee { - - @Override - public double getCost() { - return 1; - } - - @Override - public String getIngredients() { - return "Coffee"; - } -} -``` -> 咖啡对象的装饰器类 -```java -abstract class CoffeeDecorator implements Coffee { - protected final Coffee decoratedCoffee; - - /** - * 在构造方法中,初始化咖啡对象的引用 - */ - - protected CoffeeDecorator(Coffee coffee) { - this.decoratedCoffee = coffee; - } - - /** - * 装饰器父类中直接转发"请求"至引用对象 - */ - public double getCost() { - return decoratedCoffee.getCost(); - } - - public String getIngredients() { - return decoratedCoffee.getIngredients(); - } -} -``` - -注意: -> 具体的装饰器类,负责往咖啡中“添加”牛奶,注意看getCost()方法和getIngredients()方法,可以在转发请求之前或者之后,增加功能。 - 如果是代理模式,这里的结构就有所不同,通常代理模式根据运行时的条件来判断是否转发请求。 - - -> 混合牛奶 - -```java -class WithMilk extends CoffeeDecorator { - - public WithMilk(Coffee coffee) { - super(coffee); - } - - @Override - public double getCost() { - double additionalCost = 0.5; - return super.getCost() + additionalCost; - } - - @Override - public String getIngredients() { - String additionalIngredient = "milk"; - return super.getIngredients() + ", " + additionalIngredient; - } -} -``` - -```java -class WithSugar extends CoffeeDecorator { - - public WithSugar(Coffee coffee) { - super(coffee); - } - - @Override - public double getCost() { - return super.getCost() + 1; - } - - @Override - public String getIngredients() { - return super.getIngredients() + ", Sugar"; - } -} -``` - -### 测试 -```java -public class DecoratorMode { - static void print(Coffee c) { - System.out.println("花费了: " + c.getCost()); - System.out.println("配料: " + c.getIngredients()); - System.out.println("============"); - } - - public static void main(String[] args) { - //原味咖啡 - Coffee c = new SimpleCoffee(); - print(c); - - //增加牛奶的咖啡 - c = new WithMilk(c); - print(c); - - //再加一点糖 - c = new WithSugar(c); - print(c); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" deleted file mode 100644 index 4e209295..00000000 --- "a/Java/codes/modes/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" +++ /dev/null @@ -1,113 +0,0 @@ -## 观察者模式 -> 在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。 - 其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。 -> 四个角色 -> - 抽象被观察者角色 -> - 抽象观察者角色 -> - 具体被观察者角色 -> - 具体观察者角色 - -### 举例子(如:微信公众号) - -> 定义一个抽象被观察者接口,声明了添加、删除、通知观察者方法。 - -```java -interface Observerable { - void registerObserver(Observer o); - void removeObserver(Observer o); - void notifyObserver(); -} -``` - -> 定义一个抽象观察者接口,定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。 - -```java -interface Observer { - void update(String message); -} -``` - -> 定义被观察者,实现三个接口,并且list保存注册的Observer - -```java -class WechatObserver implements Observerable { - - private List list; - private String message; - - public WechatObserver() { - list = new ArrayList(); - } - - @Override - public void registerObserver(Observer o) { - list.add(o); - } - - @Override - public void removeObserver(Observer o) { - list.remove(o); - } - - @Override - public void notifyObserver() { - for (Observer observer : list) { - observer.update(message); - } - } - - public void setMessage(String message) { - this.message = message; - System.out.println("微信服务更新消息:" + message); - // 消息更新,通知所有观察者 - notifyObserver(); - } -} -``` -> 定义具体观察者,User - -```java -class User implements Observer { - - private String name; - private String message; - - public User(String name) { - this.name = name; - } - - - @Override - public void update(String message) { - this.message = message; - read(); - } - - void read() { - System.out.println(name+ " 收到推送消息:" + message); - } -} -``` - -### 测试 - -```java -public class ObserverMode { - public static void main(String[] args) { - WechatObserver wechatObserver = new WechatObserver(); - User maifeng = new User("Maifeng"); - User xiaofeng = new User("Xiaofeng"); - User fengfeng = new User("Fengfeng"); - - wechatObserver.registerObserver(maifeng); - wechatObserver.registerObserver(xiaofeng); - wechatObserver.registerObserver(fengfeng); - wechatObserver.setMessage("PHP是世界上最好的语言..."); - - System.out.println("---------------"); - - wechatObserver.removeObserver(fengfeng); - wechatObserver.setMessage("JAVA是世界上最好的语言..."); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" "b/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" deleted file mode 100644 index 2e0e9b92..00000000 --- "a/Java/codes/mysql/InnoDB\345\222\214MyISAM.md" +++ /dev/null @@ -1,22 +0,0 @@ -# InnoDB和MyISAM - -## InnoDB -- 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 -- 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ **间隙锁**(Next-Key Locking)**防止幻影读**。 -- 主索引是**聚簇索引**,在**索引中保存了数据**,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 -- 支持真正的在**线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -## MyISAM -- 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括**压缩表、空间数据索引**等。 -- **不支持事务**。 -- **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -## 简单对比 -- **事务**: InnoDB 是事务型的,可以使用 `Commit` 和 `Rollback` 语句。 -- **并发**: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -- **外键**: InnoDB 支持外键。 -- **备份**: InnoDB 支持在线热备份。 -- **崩溃恢复**: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- **其它特性**: MyISAM 支持压缩表和空间数据索引。 \ No newline at end of file diff --git "a/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" "b/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" deleted file mode 100644 index 672aea39..00000000 --- "a/Java/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" +++ /dev/null @@ -1,8 +0,0 @@ -# MVCC的缺点 -> MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。 要知道的,MVCC机制下,会在更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本。 而undo log这个关键的东西,记载的内容是串行化的结果,记录了多个事务的过程,不属于多版本共存。 这么一看,似乎mysql的mvcc也并没有所谓的多版本共存 - -## InnoDB的实现 -1. 事务以**排他锁**的形式修改原始数据 -2. 把修改前的数据存放于**undo log**,通过回滚指针与主数据关联 -3. 修改成功(commit),数据放到**redo log**中,失败则恢复**undo log**中的数据(rollback) - diff --git "a/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" "b/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" deleted file mode 100644 index 92c0c654..00000000 --- "a/Java/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" +++ /dev/null @@ -1,20 +0,0 @@ -# MySQL是如何执行一条SQL的 - -先附上一张图: - -![执行过程图](http://media.dreamcat.ink/uPic/SQL%E6%89%A7%E8%A1%8C%E7%9A%84%E5%85%A8%E9%83%A8%E8%BF%87%E7%A8%8B.png) - -**MySQL内部可以分为服务层和存储引擎层两部分:** -1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 -2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 - -**Server层按顺序执行sql的步骤为**: -客户端请求->**连接器**(验证用户身份,给予权限) -> **查询缓存**(存在缓存则直接返回,不存在则执行后续操作)->**分析器**(对SQL进行词法分析和语法分析操作) -> **优化器**(主要对执行的sql优化选择最优的执行方案方法) -> **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口)->**去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) - -简单概括: -- **连接器**:管理连接、权限验证; -- **查询缓存**:命中缓存则直接返回结果; -- **分析器**:对SQL进行词法分析、语法分析;(判断查询的SQL字段是否存在也是在这步) -- **优化器**:执行计划生成、选择索引; -- **执行器**:操作引擎、返回结果; -- **存储引擎**:存储数据、提供读写接口。 \ No newline at end of file diff --git "a/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" "b/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" deleted file mode 100644 index db848b0d..00000000 --- "a/Java/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" +++ /dev/null @@ -1,74 +0,0 @@ -# MySQL的ACID原理 - -> ACID嘛,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)! - -## 举例子 -我们以从A账户转账50元到B账户为例进行说明一下ACID,四大特性。 - -### 原子性 -> 根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态! - -**如果无法保证原子性会怎么样?** - -OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~ - -### 隔离性 -> 根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 - -**如果无法保证隔离性会怎么样?** - -OK,假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,会出现下面的情形 - - -![事务隔离](https://imgkr.cn-bj.ufileos.com/b3d04d1e-6bf1-4d49-b670-7e8a4801b385.png) - -如图所示,如果不保证隔离性,A扣款两次,而B只加款一次,凭空消失了50元,依然出现了数据不一致的情形! - -### 持久性 -> 根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。 - -**如果无法保证持久性会怎么样?** - -在MySQL中,为了解决CPU和磁盘速度不一致问题,MySQL是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。 - -设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是**数据不一致**的情形。 - -### 一致性 -> 根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!** - -**如果无法保证一致性会怎么样?** - -- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。 -- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。 - -## 如何保证 - -### 保证一致性 -OK,这个问题分为两个层面来说。 - -- **从数据库层面**,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。**数据库必须要实现AID三大特性,才有可能实现一致性**。例如,原子性无法保证,显然一致性也无法保证。 -- **从应用层面**,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据! - -### 保证原子性 -OK,是利用Innodb的**undo log**。 **undo log**名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如 -- 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据 -- 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作 -- 当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作 - -**undo log**记录了这些回滚需要的信息,当事务执行失败或调用了**rollback**,导致事务需要回滚,便可以利用**undo log**中的信息将数据回滚到修改之前的样子。 - -### 保证持久性 -OK,是利用Innodb的**redo log**。 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题? -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用**redo log**解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在**redo log**中记录这次操作。当事务提交的时候,会将**redo log**日志进行刷盘(**redo log**一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据**undo log**和**binlog**内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将**redo log**进行刷盘比对数据页刷盘效率高,具体表现如下: -- **redo log**体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- **redo log**是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -### 保证隔离 -**利用的是锁和MVCC机制。** \ No newline at end of file diff --git "a/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" "b/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" deleted file mode 100644 index acd6d144..00000000 --- "a/Java/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" +++ /dev/null @@ -1,11 +0,0 @@ -# MySQL读写分离主从复制原理 - -主库(master)将变更写**binlog**日志,然后从库(slave)连接到主库之后,从库有一个**IO线程**,将主库的binlog日志**拷贝到自己本地**,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。 - -这里有一个非常重要的一点,就是从库同步主库数据的过程是**串行化**的,也就是说**主库上并行**的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 - -而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 - -所以mysql实际上在这一块有两个机制,一个是**半同步复制**,用来解决主库数据丢失问题;一个是**并行复制**,用来解决主从同步延时问题。 - -所谓并行复制,指的是从库**开启多个线程,并行读取relay log中不同库的日志**,然后并行重放不同库的日志,这是库级别的并行。 \ No newline at end of file diff --git "a/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" "b/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 7f910eab..00000000 --- "a/Java/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,51 +0,0 @@ -# 并发事务带来的问题 - -## 脏读 - -![脏读](https://www.pdai.tech/_images/pics/dd782132-d830-4c55-9884-cfac0a541b8e.png) - -第一个事务首先读取var变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取var为100,此时第一个事务做了回滚。最终第二个事务读取的var和数据库的var不一样。 - -## 丢弃修改 -![丢弃修改](https://www.pdai.tech/_images/pics/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png) - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 - -## 不可重复读 - -![不可重复读](https://www.pdai.tech/_images/pics/c8d18ca9-0b09-441a-9a0c-fb063630d708.png) - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -## 幻读 - -![幻读](https://www.pdai.tech/_images/pics/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png) - -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条,这样就导致了幻读。 - -## 隔离级别 -- **未提交读**,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** -- **提交读**,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **重复读**,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** -- **可串行化读**,在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| ---------------- | ---- | ---------- | ------ | -| READ-UNCOMMITTED | √ | √ | √ | -| READ-COMMITTED | × | √ | √ | -| REPEATABLE-READ | × | × | √ | -| SERIALIZABLE | × | × | × | - -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ**(可重读) - -> **这里需要注意的是**:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是**Next-Key Lock 锁**算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 - -InnoDB 存储引擎在分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 \ No newline at end of file diff --git "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" "b/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" deleted file mode 100644 index 0649118a..00000000 --- "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 数据库结构优化 -- **范式优化**: 比如消除冗余(节省空间。。) -- **反范式优化**:比如适当加冗余等(减少join) -- **限定数据的范围**: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- **读/写分离**: 经典的数据库拆分方案,主库负责写,从库负责读; -- **拆分表**:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 - -## 拆分表 -**拆分其实又分垂直拆分和水平拆分**: - -举例子: - -简单购物系统暂设涉及如下表: -1. 产品表(数据量10w,稳定) -2. 订单表(数据量200w,且有增长趋势) -3. 用户表 (数据量100w,且有增长趋势) - -### 垂直拆分 -- 解决问题:表与表之间的io竞争 -- 不解决问题:单表中数据量增长出现的压力 -- 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - -### 水平拆分 -- 解决问题:单表中数据量增长出现的压力 -- 不解决问题:表与表之间的io争夺 -- 方案:**用户表**通过性别拆分为**男用户表**和**女用户表**,**订单表**通过已完成和完成中拆分为**已完成订单**和**未完成订单**,**产品表未完成订单放一个server上**,**已完成订单表盒男用户表放一个server上**,**女用户表放一个server上**(女的爱购物 哈哈)。 \ No newline at end of file diff --git "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" "b/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" deleted file mode 100644 index 6a3b7eb6..00000000 --- "a/Java/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" +++ /dev/null @@ -1,10 +0,0 @@ -# 数据库范式 - -## 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -## 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是**非主属性非部分依赖于主关键字**。 - -## 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余)**外键** \ No newline at end of file diff --git "a/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" "b/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" deleted file mode 100644 index f03a8639..00000000 --- "a/Java/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" +++ /dev/null @@ -1,30 +0,0 @@ -# 索引种类 - -## 索引类型 -- **FULLTEXT** :即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- **HASH** :由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- **BTREE** :BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- **RTREE** :RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 - -## 索引种类 -- **普通索引**:仅加速查询 -- **唯一索引**:加速查询 + 列值唯一(可以有null) -- **主键索引**:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 -- **组合索引**:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- **全文索引**:对文本的内容进行分词,进行搜索 -- **索引合并**:使用多个单列索引组合搜索 -- **覆盖索引**:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 -- **聚簇索引**:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) - - -## 为什么使用索引 -- 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 -- 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 -- 帮助服务器**避免排序和临时表**。 -- 将**随机IO变为顺序IO**。 -- 可以**加速表和表之间的连接**,特别是在实现数据的参考完整性方面特别有意义。 - -## 索引缺点 -- 当对表中的数据进行增加、删除和修改的时候,**索引也要动态的维护**,这样就降低了数据的维护速度。 -- 索引需要**占物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 -- **创建索引和维护索引要耗费时间**,这种时间随着数据量的增加而增加 \ No newline at end of file diff --git "a/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" "b/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" deleted file mode 100644 index 4282598a..00000000 --- "a/Java/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 红黑树、B树和B+树 - -## 红黑树 -> 红黑树也是一种自平衡的二叉查找树。 - -![红黑树](https://www.pdai.tech/_images/alg/alg-tree-14.png) - -### 特点 -- 每个结点要么是红的要么是黑的。(红或黑) -- 根结点是黑的。 (根黑) -- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 (叶黑) -- 如果一个结点是红的,那么它的两个儿子都是黑的。 (红子黑) -- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。(路径下黑相同) - -### 适用场景 -- Java `ConcurrentHashMap` & `TreeMap` -- C++ STL: map & set -- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块 -- epoll在内核中的实现,用红黑树管理事件块 -- nginx中,用红黑树管理timer等 - -## B树 -> B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让**查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成**。B树,概括来说是一个**一般化的二叉查找树**(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。其中,概念较为复杂,给个简单的图理解: - - -![简单B树](https://imgkr.cn-bj.ufileos.com/e30bb7ee-f0ec-4fd6-a4f2-8f82405dfe9a.png) - - - -## B+树 -- 在B树基础上,为**叶子结点增加链表指针**(B树+叶子有序链表),所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -- B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 - -给个图: - - -![B+](https://imgkr.cn-bj.ufileos.com/0dd56365-0cc0-4f95-973b-5465c9664f71.png) - - -## 为什么用B+树而不用hash和B-Tree -- 利用Hash需要把数据全部**加载到内存中**,如果数据量大,是一件很**消耗内存**的事,而采用B+树,是基于**按照节点分段加载,由此减少内存消耗**。 -- 和业务场景有段,**对于唯一查找**(查找一个值),Hash确实更快,**但数据库中经常查询多条数据**,这时候由于B+数据的有序性,与叶子节点又有链表相连,他的查询效率会比Hash快的多。 -- b+树的**非叶子节点不保存数据**,**只保存子树的临界值**(最大或者最小),所以同样大小的节点,**b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 \ No newline at end of file diff --git "a/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" "b/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" deleted file mode 100644 index a6437463..00000000 --- "a/Java/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" +++ /dev/null @@ -1,24 +0,0 @@ -# Sentinel? -> Sentinel是一个面试分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 - -## 服务限流 -> 当系统资源不够,不足以应对大量请求,对系统按照预设的规则进行流量限制或功能限制 - -## 服务熔断 -> 当调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 - -## 服务降级 -> 为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级,有策略的不处理或用简单的方式处理 - -## 为什么熔断降级 -> 系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 - -## 工作原理 -![](https://www.javazhiyin.com/wp-content/uploads/2019/10/java6-1572258892.png) - -## 对比 -![](https://imgkr.cn-bj.ufileos.com/8f2cf909-4c41-47c3-89d9-d6eb2676a519.png) - -**值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。 - -Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离 diff --git "a/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" "b/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" deleted file mode 100644 index c8a69ffd..00000000 --- "a/Java/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" +++ /dev/null @@ -1,218 +0,0 @@ -# Redis做延迟队列 - -## 引言 -> 班车预约平台,订单自动取消任务,采用的是监听Redis的key键消失的策略,属于被动轮询。而用Redis做延迟队列,需要主动轮询。班车平台的github:[https://github.com/DreamCats/school-bus](https://github.com/DreamCats/school-bus) - -## 实现思路 -1. 将整个Redis当做消息池,以kv形式存储消息,key为id,value为具体的消息body -2. 使用ZSET做优先队列,按照score维持优先级(用当前时间+需要延时的时间作为score) -3. 轮询ZSET,拿出score比当前时间戳大的数据(已过期的) -4. 根据id拿到消息池的具体消息进行消费 -5. 消费成功,删除改队列和消息 -6. 消费失败,让该消息重新回到队列 - - -## 代码实现 - -### RedisMessage - -```java -public class RedisMessage { - - /** - * 消息id - */ - private String id; - - /** - * 消息延迟/毫秒 - */ - private long delay; - - /** - * 消息存活时间 - */ - private int ttl; - - /** - * 消息体,对应业务内容 - */ - private String body; - - /** - * 创建时间,如果只有优先级没有延迟,可以设置创建时间为0 - * 用来消除时间的影响 - */ - private long createTime; - -} -``` - -### RedisMQ队列 -```java -@Component -public class RedisMQ { - - /** - * 消息池前缀,以此前缀加上传递的消息id作为key,以消息{@link RedisMessage} - * 的消息体body作为值存储 - */ - public static final String MSG_POOL = "Message:Pool:"; - /** - * zset队列 名称 queue - */ - public static final String QUEUE_NAME = "Message:Queue:"; - - private static final int SEMIH = 30*60; - - @Autowired - private RedisUtils redisUtils; - - /** - * 存入消息池 - * @param message - * @return - */ - public boolean addMsgPool(RedisMessage message) { - - if (null != message) { - return redisUtils.set(MSG_POOL + message.getId(), message.getBody(), Long.valueOf(message.getTtl() + SEMIH)); - } - return false; - } - - /** - * 从消息池中删除消息 - * @param id - */ - public void deMsgPool(String id) { - redisUtils.del(MSG_POOL + id); - } - - /** - * 向队列中添加消息 - * @param key - * @param score - * @param val - */ - public void enMessage(String key, long score, String val) { - redisUtils.zsset(key, val, score); - } - - /** - * 从队列额删除消息 - * @param key - * @param id - * @return - */ - public boolean deMessage(String key, String id) { - return redisUtils.zdel(key, id); - } -} -``` - -### Redis的工具类 -可以去[github地址](https://github.com/DreamCats/school-bus/blob/master/school-bus/guns-order/src/test/java/com/stylefeng/guns/rest/RedisUtils.java) - -### 生产者(用户) -```java -@Component -public class MessageProvider { - - private static int delay = 30;//30秒,可自己动态传入 - - @Resource - private RedisMQ redisMQ; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - //改造成redis - public void sendMessage(String messageContent) { - try { - if (messageContent != null){ - String seqId = UUID.randomUUID().toString(); - // 将有效信息放入消息队列和消息池中 - RedisMessage message = new RedisMessage(); - // 可以添加延迟配置 - message.setDelay(delay*1000); - message.setCreateTime(System.currentTimeMillis()); - message.setBody(messageContent); - message.setId(seqId); - // 设置消息池ttl,防止长期占用 - message.setTtl(delay + 360); - redisMQ.addMsgPool(message); - //当前时间加上延时的时间,作为score - Long delayTime = message.getCreateTime() + message.getDelay(); - String d = sdf.format(message.getCreateTime()); - System.out.println("当前时间:" + d+",消费的时间:" + sdf.format(delayTime)); - redisMQ.enMessage(RedisMQ.QUEUE_NAME,delayTime, message.getId()); - }else { - System.out.println("消息内容为空"); - } - }catch (Exception e){ - e.printStackTrace(); - } - } -} -``` - -### 消费者 -```java -@Component -public class RedisMQConsumer { - - @Resource - private RedisMQ redisMQ; - - @Autowired - private RedisUtils redisUtils; - - @Autowired - private MessageProvider provider; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - - /** - * 消息队列监听器 - * 也可以开个线程池 - * 主动轮询 - */ - @Scheduled(cron = "*/1 * * * * *") - public void monitor() { - // 取出0到当前时间的权重值 - Set set = redisUtils.rangeByScore(RedisMQ.QUEUE_NAME, 0, System.currentTimeMillis()); - if (null != set) { - // 如果不为空 - // 获取当前时间 - long current = System.currentTimeMillis(); - for (Object id : set) { - long score = redisUtils.getScore(RedisMQ.QUEUE_NAME, (String) id).longValue(); - if (current >= score) { - // 已超时的消息拿出来消费 - String str = ""; - try { - // 根据id取出消息 - str = (String) redisUtils.get(RedisMQ.MSG_POOL + id); - System.out.println("消费了:" + str+ ",消费的时间:" + sdf.format(System.currentTimeMillis())); - } catch (Exception e) { - e.printStackTrace(); - //如果取消息发生了异常,则将消息重新放回队列 - System.out.println("消费异常,重新回到队列"); - provider.sendMessage(str); - } finally { - // 不管消费成功与非,都要删除当前消息 - redisMQ.deMessage(RedisMQ.QUEUE_NAME, (String) id); - redisMQ.deMsgPool((String) id); - } - } - } - } - } -} -``` - -### 结合班车预约平台思路 -1. 当前端发一个带延迟时间戳的请求,我们后端可以在zset当做延迟队列,订单ID为元素的key,时间戳为当前时间+延迟时间戳当做score -2. 可以使用Springboot定时器或者线程池延迟主动轮询拉取队列中符合条件的score,根据符合条件score的订单id -3. 根据符合条件的订单id,进行回退座位。 \ No newline at end of file diff --git "a/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" "b/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" deleted file mode 100644 index dec79def..00000000 --- "a/Java/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" +++ /dev/null @@ -1,185 +0,0 @@ -# 深究Redis的底层结构 - -## 简单动态字符串(SDS) -> Redis没有直接使用C语言传统的字符串(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(Simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。 - -### 看一下SDS的定义 -```c -struct sdshdr { - // 记录buf数组中已使用字节的数量 - // 等于sds所保存字符串的长度 - int len; - - // 记录buf数组中未使用字节的数量 - int free; - - // 字节数组,用于保存字符串 - char buf[]; -} -``` - -### 有何优点 -1. 获取字符串长度的复杂度为O(1)。 -2. 杜绝缓冲区溢出。 -3. 减少修改字符串长度时所需要的内存重分配次数。 -4. 二进制安全。 -5. 兼容部分C字符串函数。 - -## 链表 -> 毫无疑问,Redis也不会不适用链表的,毕竟链表这么好的结构,用起来很香的。当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的额字符串时,Redis就会使用链表作为列表建的底层实现。 - -### 节点底层结构 -```c -typedef struct listNode { - // 前置节点 - struct listNode *prev; - // 后置节点 - struct listNode *next; - // 节点的值 - void *value; -} listNode; -``` -和其他的节点结构没啥区别... - -### list底层结构 -```c -typedef struct list { - // 表头节点 - listNode *head; - // 表尾节点 - listNode *tail; - // 链表所包含的节点数量 - unsigned long len; - // 节点值复制函数 - void *(*dup)(void *ptr); - // 节点值是放过函数 - void (*free)(void *ptr); - // 节点值对比函数 - int(*match)(void *ptr, void *key); -} list; -``` - -### 特性 -1. 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。 -2. 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。 -3. 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。 -4. 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。 -5. 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。 - -## 字典 -> 说白了, 字典就是键值对。Redis的字典的底层就是哈希表而已。 - -### 哈希表 -```c -typedef struct dictht { - // 哈希表数组 - dictEntry **table; - // 哈希表大小 - unsigned long size; - // 哈希表大小掩码,用于计算索引值 - // 总是等于size-1 - unsigned long sizemark; - // 该哈希表已有节点的数量 - unsigned long used; -} dichht; -``` - -那么,哈希表数组的结构table呢? - -### dictEntry的结构 -```c -typedef struct dictEntry { - // 键 - void *key; - // 值 - union { - void *val; - uint64_t u64; - int64_t s64; - } v; - // 指向下个哈希表节点,形成链表 - struct dictEntry *nect; -} dictEntry; -``` -dict的底层结构省略。 - -### 哈希算法 -当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash算法。这种算法的优点在于即使输入的键是规律的,算法仍能给出一个个很好的随机分布性,并且算法的计算速度非常快。 - -### 哈希冲突 -Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用这个单向链表连接起来,这就解决了键冲突的问题。 - -### 特性 -1. 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 -2. Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 -3. Redis使用MurmurHash2算法来计算键的哈希值。 -4. 哈希表使用链地址法来解决键冲突。 - -## 跳跃表 - -先看这样一张图: -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4a17c6280?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -如上图,我们要查找一个元素,就需要从头节点开始遍历,直到找到对应的节点或者是第一个大于要查找的元素的节点(没找到)。时间复杂度为O(N)。 - -这个查找效率是比较低的,但如果我们把列表的某些节点拔高一层,如下图,例如把每两个节点中有一个节点变成两层。那么第二层的节点只有第一层的一半,查找效率也就会提高。 - -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4aa58e4bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -查找的步骤是从头节点的顶层开始,查到第一个大于指定元素的节点时,退回上一节点,在下一层继续查找。 - -比如我们要查找16: -1. 从头节点的最顶层开始,先到节点7。 -2. 7的下一个节点是39,大于16,因此我们退回到7 -3. 从7开始,在下一层继续查找,就可以找到16。 - -这个例子中遍历的节点不比一维列表少,但是当节点更多,查找的数字更大时,这种做法的优势就体现出来了。还是上面的例子,如果我们要**查找的是39**,那么只需要访问两个节点(7、39)就可以找到了。这比一维列表要减少一半的数量。 - -为了避免插入操作的时间复杂度是O(N),skiplist每层的数量不会严格按照2:1的比例,而是对每个要插入的元素随机一个层数。 - -随机层数的计算过程如下: -1. 每个节点都有第一层 -2. 那么它有第二层的概率是p,有第三层的概率是p*p -3. 不能超过最大层数 - -### zskiplistNode -```c -typedef struct zskiplistNode { - // 后退指针 - struct zskiplistNode *backward; - // 分值 权重 - double score; - // 成员对象 - robj *obj; - // 层 - struct zskiplistLevel { - // 前进指针 - struct zskiplistNode *forward; - // 跨度 - unsigned int span; - } leval[]; -} zskiplistNode; -``` -一般来说,层的数量越多,访问其他节点的速度越快。 - -### zskipList -```c -typedef struct zskiplist { - // 表头节点和表尾节点 - struct zskiplistNode *header, *tail; - // 表中节点的数量 - unsigned long length; - // 表中层数最大的节点的层数 - int leval; -} zskiplist; -``` - -### 特性 -1. 跳跃表是有序集合的底层实现之一 -2. Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点 -3. 每个跳跃表节点的层高都是1至32之间的随机数 -4. 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。 -5. 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。 - -## 压缩列表 -> 一看名字,就是为了节省内存造的列表结构。压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 \ No newline at end of file diff --git "a/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" "b/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" deleted file mode 100644 index 284133a7..00000000 --- "a/Java/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" +++ /dev/null @@ -1,116 +0,0 @@ -# 手写SpringAOP - -## 定义advice接口 - -```java -public interface Advice extends InvocationHandler { -} -``` - -## 定义方法回调接口 -```java -public interface MethodInvocation { - void invoke(); -} -``` - -## 定义前置通知 -```java -public class BeforeAdvice implements Advice { - - private Object bean; // bean对象 - - private MethodInvocation methodInvocation; // 方法调用 - - public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 在目标方法前调用,前置 - methodInvocation.invoke(); - return method.invoke(bean, args); - } -} -``` - -## 定义后置通知 -```java -public class AfterAdvice implements Advice { - - private Object bean; - - private MethodInvocation methodInvocation; - - public AfterAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 业务逻辑调用 - Object invoke = method.invoke(bean, args); - // 后置通知调用 - methodInvocation.invoke(); - return invoke; - } -} -``` - -## 定义目标方法接口 -```java -public interface HelloService { - void sayHelloWorld(); -} -``` - -## 实现目标方法 -```java -public class HelloServiceImpl implements HelloService { - - @Override - public void sayHelloWorld() { - System.out.println("hello world..."); - } -} -``` - -## SpringAOP的实现 -```java -public class SimpleAOP { - // 反射代理 - public static Object getProxy(Object bean, Advice advice) { - return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader() - , bean.getClass().getInterfaces(), advice); - } -} -``` - -## 简单测试 -```java -public class SimpleAOPTest { - public static void main(String[] args) { - // 1. 创建一个 MethodInvocation 实现类 切面逻辑类 - MethodInvocation logTask = () -> System.out.println("log task start"); - MethodInvocation logTaskEnd = () -> System.out.println("log task end"); - - // 业务逻辑类 - HelloServiceImpl helloService = new HelloServiceImpl(); - - // 2. 创建一个Advice 切入点 - BeforeAdvice beforeAdvice = new BeforeAdvice(helloService, logTask); - AfterAdvice afterAdvice = new AfterAdvice(helloService, logTaskEnd); - - // 3. 为目标对象生成代理 - HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloService, beforeAdvice); - HelloService helloServiceImplProxyAfter = (HelloService) SimpleAOP.getProxy(helloService, afterAdvice); - - helloServiceImplProxy.sayHelloWorld(); - helloServiceImplProxyAfter.sayHelloWorld(); - - } -} -``` diff --git "a/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" "b/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" deleted file mode 100644 index 95863271..00000000 --- "a/Java/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" +++ /dev/null @@ -1,188 +0,0 @@ -# 手写SpringIoc代码 - -## 举个例子 - -> 容器注册bean(Car和Wheel) - -### Wheel -```java -public class Wheel { - - private String brand; - - private String specification; - - public String getBrand() { - return brand; - } - - public void setBrand(String brand) { - this.brand = brand; - } - - public String getSpecification() { - return specification; - } - - public void setSpecification(String specification) { - this.specification = specification; - } - - @Override - public String toString() { - return "Wheel{" + - "brand='" + brand + '\'' + - ", specification='" + specification + '\'' + - '}'; - } -} -``` - -### Car -```java -public class Car { - - private String name; - - private Wheel wheel; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Wheel getWheel() { - return wheel; - } - - public void setWheel(Wheel wheel) { - this.wheel = wheel; - } - - @Override - public String toString() { - return "Car{" + - "name='" + name + '\'' + - ", wheel=" + wheel + - '}'; - } -} -``` - -### xml注册 -```xml - - - - - - - - - - - -``` - -### SimpleIOC -```java -public class SimpleIOC { - //需要hashmap - private Map beanMap = new HashMap<>(); - - public SimpleIOC(String location) throws Exception { - loadBean(location); - } - - public Object getBean(String name) { - Object bean = beanMap.get(name); - if (bean == null) { - throw new IllegalArgumentException("there is no bean with name" + name); - } - return bean; - } - - private void loadBean(String location) throws Exception { - // 加载xml配置文件 - InputStream inputStream = new FileInputStream(location); - // docbuilder factory - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - // doc builder - DocumentBuilder documentBuilder = factory.newDocumentBuilder(); - // doc - Document document = documentBuilder.parse(inputStream); - // element - Element element = document.getDocumentElement(); - // nodes - NodeList nodes = element.getChildNodes(); - // 遍历标签获取bean节点信息 - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - if (node instanceof Element) { - Element ele = (Element) node; - String id = ele.getAttribute("id"); - String className = ele.getAttribute("class"); - // 加载beanClass - Class beanClass = null; - try { - beanClass = Class.forName(className);// 反射 - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return; - } - // 创建bean - Object bean = beanClass.newInstance(); // new实例 - // 遍历标签 - NodeList propertyNodes = ele.getElementsByTagName("property"); - for (int j = 0; j < propertyNodes.getLength(); j++) { - Node propertyNode = propertyNodes.item(j); - if (propertyNode instanceof Element) { - Element propertyElement = (Element) propertyNode; - String name = propertyElement.getAttribute("name"); - String value = propertyElement.getAttribute("value"); - // 利用反射将 bean 相关字段访问权限设为可访问 // 暴力访问 - Field declaredField = bean.getClass().getDeclaredField(name); - declaredField.setAccessible(true); - - if (value != null && value.length() > 0) { - // 将属性值填充到相关字段中 - declaredField.set(bean, value); - } else { - String ref = propertyElement.getAttribute("ref"); - if (ref == null || ref.length() == 0) { - throw new IllegalArgumentException("ref config error"); - } - // 将引用填充到相关字段中 - declaredField.set(bean, getBean(ref)); - } - } - // 将 bean 注册到 bean 容器中 - registerBean(id, bean); - } - } - } - } - - private void registerBean(String id, Object bean) { - beanMap.put(id, bean); - } -} -``` - -### SimpleIOCTest -```java -public class SimpleIOCTest { - public static void main(String[] args) throws Exception { - System.out.println(SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile()); - String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile(); - SimpleIOC simpleIOC = new SimpleIOC(location); - Wheel wheel = (Wheel) simpleIOC.getBean("wheel"); // 获取wheel bean - System.out.println(wheel); - Car car = (Car) simpleIOC.getBean("car"); // 获取 bean - System.out.println(car); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a731c1bd..00000000 --- "a/Java/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,318 +0,0 @@ -# AQS -> Java的`AbstractQueuedSynchronizer`抽象类,抽象同队队列 - - -## 原理 -> AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。 -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` -状态信息通过 protected 类型的`getState`,`setState`,`compareAndSetState`进行操作 -```java -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -**AQS 定义两种资源共享方式** - -1. **Exclusive**(独占)只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁。 - - 总结:公平锁和非公平锁只有两处不同: - - 1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。 - 2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。 - -2. **Share**(共享)多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、 CyclicBarrier、ReadWriteLock 。 - -**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:** -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 -``` - -## ReentrantLock -> 我们知道,我们需要在哪里上锁,一般都是调用`xxx.lock()`,在finally的代码块中释放锁`xxx.unlock()`,那么我们来看一下源码。 - -### 构造方法 -```java -// 默认是非公平锁 -public ReentrantLock() { - sync = new NonfairSync(); -} -// 可选参数,是否公平 -public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); -} -``` - -#### 公平锁的实现 -```java -protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (!hasQueuedPredecessors() && // 判断队列是否轮到该线程 - compareAndSetState(0, acquires)) { // 利用cas更换状态 - setExclusiveOwnerThread(current); // 如果都ok,就设置独占锁 - return true; - } - } - else if (current == getExclusiveOwnerThread()) {// 判断当前独占锁是否还是当前线程 - int nextc = c + acquires;// 状态累加 - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); // 设置状态 - return true; - } // 否则false - return false; -} -``` -#### 非公平锁的实现 -> 比公平锁少了队列的判断而已。 -```java -final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (compareAndSetState(0, acquires)) {// 在这里... - setExclusiveOwnerThread(current); - return true; - } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) // overflow - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; -} -``` - -## CountDownLatch -CountDownLatch是共享锁的一种实现,它默认构造 `AQS` 的 `state` 值为 `count`。当线程使用`countDown`方法时,其实使用了`tryReleaseShared`方法以CAS的操作来减少`state`,直至`state`为0就代表所有的线程都调用了`countDown`方法。当调用`await`方法的时候,如果`state`不为0,就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入**阻塞队列Park,并自旋CAS判断state == 0**,直至最后一个线程调用了countDown,使得state == 0,于是阻塞的线程便判断成功,全部往下执行。 - -### 构造方法 -```java -public CountDownLatch(int count) { - if (count < 0) throw new IllegalArgumentException("count < 0"); - this.sync = new Sync(count); // 这里设置state的次数 -} -``` - -### countDown -```java -public void countDown() { - sync.releaseShared(1); -} -``` -### tryReleaseShared -```java -protected boolean tryReleaseShared(int releases) { - // Decrement count; signal when transition to zero - for (;;) { - int c = getState(); - if (c == 0) - return false; - int nextc = c-1; // 每执行一次该方法,状态减一 - if (compareAndSetState(c, nextc)) - return nextc == 0; - } -} -``` - -### 三种用法: - -1. 某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :`new CountDownLatch(n)`,每当一个任务线程执行完毕,就将计数器减 1 `countdownlatch.countDown()`,当计数器的值变为 0 时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 -2. 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1)`,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。 -3. 死锁检测:一个非常方便的使用场景是,你可以使用 n 个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 - -### 举个例子 -> 主线程等待多个子线程 -```java -public class CountDownLatchDemo { - - public static void main(String[] args) throws InterruptedException { - countDownLatchTest(); -// general(); - } - - public static void general() { - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - }, "Thread --> " + i).start(); - } - while (Thread.activeCount() > 2) { // 一般方式 - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } - } - - public static void countDownLatchTest() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(6); - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - countDownLatch.countDown(); // 每个线程执行完都会调用countDown - }, "Thread --> " + i).start(); - } - countDownLatch.await(); // 阻塞主线程 - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } -} -``` - -## CyclicBarrier -> CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - -### 举个例子 -```java -public class CyclicBarrierDemo { - public static void main(String[] args) { - cyclicBarrierTest(); - } - - public static void cyclicBarrierTest() { - CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { - System.out.println("====召唤神龙===="); // 拦截到一定数量,执行该代码 - }); - for (int i = 0; i < 7; i++) { - final int tempInt = i; - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 收集到第" + tempInt + "颗龙珠"); - try { - cyclicBarrier.await(); // 拦截 - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (BrokenBarrierException e) { - e.printStackTrace(); - } - }, "" + i).start(); - } - } -} -``` -当调用 `CyclicBarrier` 对象调用 `await()` 方法时,实际上调用的是`dowait(false, 0L)`方法。 `await()` 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。 -看一下源码: -```java - // 当线程数量或者请求数量达到 count 时 await 之后的方法才会被执行。上面的示例中 count 的值就为 7。 - private int count; - /** - * Main barrier code, covering the various policies. - */ - private int dowait(boolean timed, long nanos) - throws InterruptedException, BrokenBarrierException, - TimeoutException { - final ReentrantLock lock = this.lock; - // 锁住 - lock.lock(); - try { - final Generation g = generation; - - if (g.broken) - throw new BrokenBarrierException(); - - // 如果线程中断了,抛出异常 - if (Thread.interrupted()) { - breakBarrier(); - throw new InterruptedException(); - } - // cout减1 - int index = --count; - // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行await 方法之后的条件 - if (index == 0) { // tripped - boolean ranAction = false; - try { - final Runnable command = barrierCommand; - if (command != null) - command.run(); - ranAction = true; - // 将 count 重置为 parties 属性的初始化值 - // 唤醒之前等待的线程 - // 下一波执行开始 - nextGeneration(); - return 0; - } finally { - if (!ranAction) - breakBarrier(); - } - } - - // loop until tripped, broken, interrupted, or timed out - for (;;) { - try { - if (!timed) - trip.await(); - else if (nanos > 0L) - nanos = trip.awaitNanos(nanos); - } catch (InterruptedException ie) { - if (g == generation && ! g.broken) { - breakBarrier(); - throw ie; - } else { - // We're about to finish waiting even if we had not - // been interrupted, so this interrupt is deemed to - // "belong" to subsequent execution. - Thread.currentThread().interrupt(); - } - } - - if (g.broken) - throw new BrokenBarrierException(); - - if (g != generation) - return index; - - if (timed && nanos <= 0L) { - breakBarrier(); - throw new TimeoutException(); - } - } - } finally { - lock.unlock(); - } - } -``` -总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,count 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 - -## Semaphore -> **synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** - -### 举个例子 -```java -public class SemaphoreDemo { - public static void main(String[] args) { - Semaphore semaphore = new Semaphore(3);// 模拟三个停车位 - for (int i = 0; i < 6; i++) { // 模拟6部汽车 - new Thread(() -> { - try { - semaphore.acquire(); - System.out.println(Thread.currentThread().getName() + " 抢到车位"); - // 停车3s - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " 停车3s后离开车位"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - semaphore.release(); - } - }, "Car " + i).start(); - } - } -} -``` diff --git "a/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c01f3e17..00000000 --- "a/Java/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,89 +0,0 @@ -# CAS -> **我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了`CAS` - -```java -public class Test { - - public AtomicInteger i; - - public void add() { - i.getAndIncrement(); - } -} -``` -**我们来看`getAndIncrement`的内部:** -```java -public final int getAndIncrement() { - return unsafe.getAndAddInt(this, valueOffset, 1); -} -``` -**再深入到`getAndAddInt`():** -```java -public final int getAndAddInt(Object var1, long var2, int var4) { - int var5; - do { - var5 = this.getIntVolatile(var1, var2); - } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); - - return var5; -} -``` -**现在重点来了,`compareAndSwapInt(var1, var2, var5, var5 + var4)`其实换成`compareAndSwapInt(obj, offset, expect, update)`比较清楚,意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。** - -```c -UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) - UnsafeWrapper("Unsafe_CompareAndSwapInt"); - oop p = JNIHandles::resolve(obj); - jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); - return (jint)(Atomic::cmpxchg(x, addr, e)) == e; -UNSAFE_END -``` -**p是取出的对象,addr是p中offset处的地址,最后调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现。** - -## ABA问题 -> 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。 - -**目前在JDK的atomic包里提供了一个类`AtomicStampedReference`来解决ABA问题。** - -```java -public class ABADemo { - static AtomicInteger atomicInteger = new AtomicInteger(100); - static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1); - - public static void main(String[] args) { - System.out.println("=====ABA的问题产生====="); - new Thread(() -> { - atomicInteger.compareAndSet(100, 101); - atomicInteger.compareAndSet(101, 100); - }, "t1").start(); - - new Thread(() -> { - // 保证线程1完成一次ABA问题 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(atomicInteger.compareAndSet(100, 2020) + " " + atomicInteger.get()); - }, "t2").start(); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - - System.out.println("=====解决ABA的问题====="); - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); // 第一次获取版本号 - System.out.println(Thread.currentThread().getName() + " 第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); - atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp()); - }, "t3").start(); - - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); - System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } - boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1); - System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); - System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference()); - }, "t4").start(); - - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" "b/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" deleted file mode 100644 index e8c3f0a3..00000000 --- "a/Java/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" +++ /dev/null @@ -1,98 +0,0 @@ -# 各种锁 -## 公平锁/非公平锁 -**公平锁指多个线程按照申请锁的顺序来获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...),ReentrantLock,了解一下。** - -## 可重入锁 -**可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,典型的synchronized,了解一下** -```java -synchronized void setA() throws Exception { - Thread.sleep(1000); - setB(); // 因为获取了setA()的锁,此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行 -} -synchronized void setB() throws Exception { - Thread.sleep(1000); -} -``` - -## 独享锁/共享锁 -- 独享锁:是指该锁一次只能被一个线程所持有。 -- 共享锁:是该锁可被多个线程所持有。 - -## 互斥锁/读写锁 -**上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是其具体的实现** - -## 乐观锁/悲观锁 - -1. **乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待兵法同步的角度。** -2. **悲观锁认为对于同一个人数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。** -3. **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作时没有事情的。** -4. **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** -5. **悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁** - -## 分段锁 - -1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** -2. **以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** -3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** -4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** - -## 偏向锁/轻量级锁/重量级锁 - -1. **这三种锁是锁的状态,并且是针对Synchronized。在Java5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。** -2. **偏向锁的适用场景:始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。** -3. **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** -4. **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** - -## 自旋锁 - -1. **在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。** -2. **自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。** -3. **自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。** -4. **但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适用使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。** - -### 手写自旋锁的例子 -```java -public class SpinLock { - - // 原子引用线程 - AtomicReference atomicReference = new AtomicReference<>(); - - public void mylock() { - Thread thread = Thread.currentThread(); - System.out.println(Thread.currentThread().getName() + " como in..."); - while (!atomicReference.compareAndSet(null, thread)) { -// System.out.println("不爽,重新获取一次值瞧瞧..."); - } - } - - public void myUnlock() { - Thread thread = Thread.currentThread(); - atomicReference.compareAndSet(thread, null); - System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); - } - - public static void main(String[] args) { - SpinLock spinLock = new SpinLock(); - new Thread(() -> { - spinLock.mylock(); - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - spinLock.myUnlock(); - }, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - - new Thread(() -> { - spinLock.mylock(); - spinLock.myUnlock(); - }, "t2").start(); - } - -} -``` - -## Java锁总结 - -**Java锁机制可归为Sychornized锁和Lock锁两类。Synchronized是基于JVM来保证数据同步的,而Lock则是硬件层面,依赖特殊的CPU指令来实现数据同步的。** - -- Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 -- ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 -- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 \ No newline at end of file diff --git "a/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" "b/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" deleted file mode 100644 index d140c65c..00000000 --- "a/Java/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" +++ /dev/null @@ -1,61 +0,0 @@ -# Runnable、Callable和FutureTask -> Runnable自Java 1.0以来一直存在,但Callable仅在Java 1.5中引入,目的就是为了来处理**Runnable不支持的用例**。**Runnable 接口不会返回结果或抛出检查异常,但是Callable 接口可以**。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。 - -## Runnable源码 -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` - -## Callable源码 -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` - -## 线程池的submit - -![看图说话](https://imgkr.cn-bj.ufileos.com/16a8713e-b2d1-408d-82e2-ac411a51b6e0.png) - -## 再瞧Thread - - -![](https://imgkr.cn-bj.ufileos.com/ff6bdf55-1616-4887-b320-1b21576f8211.png) - -有个疑问,没有FutureTask的参数哇? - -再看FutureTask: - -![](https://imgkr.cn-bj.ufileos.com/6f63d5a6-50a5-4412-b301-1bab8ce91b4d.png) - -它的参数有Callable和Runnable。 - -`public class FutureTask implements RunnableFuture` -源码实现了RunnableFuture,那我们跟进再看: - -```java -public interface RunnableFuture extends Runnable, Future { - /** - * Sets this Future to the result of its computation - * unless it has been cancelled. - */ - void run(); -} -``` -RunnableFuture继承了Runnable和Future。意味着FutureTask既有Runnable和Future的特性,Future的方法: - -![](https://imgkr.cn-bj.ufileos.com/5c856df9-feb9-43c0-9329-97db80239de0.png) - -可看出,Future的特性的get方法了。 diff --git "a/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 6ca02b79..00000000 --- "a/Java/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,38 +0,0 @@ -# ThreadLocal -**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。****如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -## 原理 -```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` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -## 内存泄露 -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。 \ No newline at end of file diff --git "a/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index e6d70f2c..00000000 --- "a/Java/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,97 +0,0 @@ -# synchronized - -## 修饰范围 -先看例子: -```java -public class Test { - private final Object lock = new Object(); - - private static int money = 0; - - // 非静态方法 - public synchronized void noStaticMethod() { - money++; - } - // 静态方法 - public static synchronized void staticMethod() { - money++; - } - public void codeBlock() { - // 代码块 - synchronized(lock) { - money++; - } - } -} -``` -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁** - -## synchronized和ReentrantLock对比 -1. **两者都是可重入锁**:两者都是可重入锁。“可重入锁”概念是:**自己可以再次获取自己的内部锁**。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -2. **Synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**:synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -3. **ReenTrantLock 比 Synchronized 增加了一些高级功能** - 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 2. **可实现公平锁** - 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” - 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** - -## 底层原理 -> **synchronized 关键字底层原理属于 JVM 层面。** - -1. **synchronized 同步语句块的情况** - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -1. **synchronized 修饰方法的的情况** - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - -## 1.6版本的优化 -> 在 Java 早期版本中,`synchronized` 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 **Mutex Lock** 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要**操作系统**帮忙完成,而操作系统实现线程之间的切换时需要从**用户态转换到内核态**,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如**自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等**技术来减少锁操作的开销。 - -锁主要存在四中状态,依次是:**无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态**,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -1. **偏向锁** - **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - 偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步! - 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。**升级过程:** - 1. 访问Mark Word中**偏向锁的标识是否设置成1**,**锁标识位是否为01**,确认偏向状态 - 2. 如果为可偏向状态,则判断**当前线程ID是否为偏向线程** - 3. 如果偏向线程未当前线程,则通过**cas操作竞争锁**,如果竞争成功则操作Mark Word中线程ID设置为当前线程ID - 4. 如果cas偏向锁获取失败,则挂起当前偏向锁线程,偏向锁升级为**轻量级锁** - -3. **轻量级锁** - 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** - **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!升级过程** : - 1. 线程由偏向锁升级为轻量级锁时,会先把**锁的对象头MarkWord复制一份到线程的栈帧中,建立一个名为锁记录空间(Lock Record),用于存储当前Mark Word的拷贝**。 - 2. 虚拟机使用cas操作尝试将**对象的Mark Word指向Lock Record的指针,并将Lock record里的owner指针指对象的Mark Word**。 - 3. 如果cas操作成功,则该线程拥有了对象的轻量级锁。第二个线程cas自旋锁等待锁线程释放锁。 - 4. 如果多个线程竞争锁,轻量级锁要膨胀为**重量级锁**,**Mark Word中存储的就是指向重量级锁(互斥量)的指针**。其他等待线程进入阻塞状态。 - -5. **自旋锁和自适应自旋** - 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 - 互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 - - **一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - - **在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 - -6. **锁消除** - - 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -7. **锁粗化** - - 原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -8. **总结升级过程**: - - 1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 - 2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1 - 3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。 - 4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁 - 5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 - 6. 如果自旋成功则依然处于轻量级状态。 - 7. 如果自旋失败,则升级为重量级锁。 diff --git "a/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index fbba865f..00000000 --- "a/Java/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,164 +0,0 @@ -# volatile -> 那肯定介绍的它的两大特性:内存可见性和可序性。当然,还会介绍它的底层原理 - -## 内存可见性 -```java -public class Test { - private volatile boolean isStart = true; - void m() { - System.out.println(Thread.currentThread().getName() + " start..."); - while (isStart) {} - System.out.println(Thread.currentThread().getName() + " end..."); - } - - public static void main(String[] args) { - Test t1 = new Test(); - new Thread(t1::m, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - t1.isStart = false; - } -} -``` - -分析:一开始isReady为true,m方法中的while会一直循环,而主线程开启开线程之后会延迟1s将isReady赋值为false,若不加volatile修饰,则程序一直在运行,若加了volatile修饰,则程序最后会输出t1 m end... - -## 可序性 -> 有序性是指程序代码的执行是按照代码的实现顺序来按序执行的;volatile的有序性特性则是指禁止JVM指令重排优化。 -```java -public class Test { - private volatile static Test instance = null; - private Test(){} - - private static Test getInstance() { - if (instance != null) { - synchronized (Test.class) { - if (instance != null) { - instance = new Test(); - } - } - } - return instance; - } -} -``` -上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为`instance = new Singleton();`这个初始化操作并不是原子的,在JVM上会对应下面的几条指令: -``` -memory =allocate(); //1. 分配对象的内存空间 -ctorInstance(memory); //2. 初始化对象 -instance =memory; //3. 设置instance指向刚分配的内存地址 -``` -上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下: -``` -memory =allocate(); //1. 分配对象的内存空间 -instance =memory; //3. 设置instance指向刚分配的内存地址 -ctorInstance(memory); //2. 初始化对象 -``` -这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。 - -所以我们在用这种方式实现单例模式时,会使用volatile关键字修饰instance变量,这是因为volatile关键字除了可以保证变量可见性之外,还具有防止指令重排序的作用。当用volatile修饰instance之后,JVM执行时就不会对上面提到的初始化指令进行重排序优化,这样也就不会出现多线程安全问题了。 - -## 不能保证原子性 -> 20个线程,每个线程count累加10000次,最后count理论上是200000 -```java -public class Test { - volatile int count = 0; - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - for (int i = 0; i < 10000; i++) { - count++; - } - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch latch = new CountDownLatch(20); - Test t1 = new Test(latch); - for (int i = 0; i < 20; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - latch.await(); - System.out.println(t1.count); - } -// 85121 -``` -分析图:[https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) - -## 整一波内存屏障 -> **Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的;为了实现volatile内存语义,JMM会分别限制重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:** - - | 是否能重排序 | 第二个操作 | | | -| :----------: | :--------: | :--------: | :--------: | - | 第一个操作 | 普通读/写 | volatile读 | volatile写 | - | 普通读/写 | | | no | - | volatile读 | no | no | no | - | volatile写 | | no | no | - -**从上表我们可以看出**: -- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 -- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 -- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 -**为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:** -- 在每个volatile写操作的前面插入一个StoreStore屏障。 -- 在每个volatile写操作的后面插入一个StoreLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadStore屏障。 - -**volatile写插入内存指令图:** -![volatile写插入](https://image-static.segmentfault.com/416/041/416041851-5ac871370fec9_articlex) - -**上图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。** - -> **这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面,是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在这里采取了保守策略:在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。** - -**volatile读插入内存指令图:** -![volatile读](https://image-static.segmentfault.com/288/764/2887649856-5ac871c442f52_articlex) - -**上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。** - -**上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明:** - -```java -class VolatileBarrierExample { - int a; - volatile int v1 = 1; - volatile int v2 = 2; - - void readAndWrite() { - int i = v1; //第一个volatile读 - int j = v2; // 第二个volatile读 - a = i + j; //普通写 - v1 = i + 1; // 第一个volatile写 - v2 = j * 2; //第二个 volatile写 - } -} -``` -针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化: -![readAndWrite](https://image-static.segmentfault.com/178/456/1784565222-5ac871e5e6dec_articlex) - -**注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器常常会在这里插入一个StoreLoad屏障。** - -## volatile的汇编 -```c -0x000000011214bb49: mov %rdi,%rax -0x000000011214bb4c: dec %eax -0x000000011214bb4e: mov %eax,0x10(%rsi) -0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35) - -0x000000011214bb56: imul %edi,%ebx -0x000000011214bb59: mov %ebx,0x14(%rsi) -0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36) -``` -**可见其本质是通过一个lock指令来实现的。那么lock是什么意思呢?** - -**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - -- 锁住内存 -- 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 diff --git "a/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" "b/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" deleted file mode 100644 index 41239eb7..00000000 --- "a/Java/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" +++ /dev/null @@ -1,73 +0,0 @@ -# 主线程等待多个子线程的例子 - -## sleep -> 这个不常用,但是简单一些 -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("main thread"); - } -} -``` - -## join - -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - ArrayList threads = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - threads.add(new Thread(t1::m, "Thread " + i)); - } - threads.forEach(o -> o.start()); - threads.forEach(o -> { - try { - o.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - System.out.println("main thread"); - } -} -``` - -## CountDownLatch -```java -public class Test { - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - System.out.println(Thread.currentThread().getName()); - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(5); - Test t1 = new Test(countDownLatch); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - countDownLatch.await(); - System.out.println("main thread"); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index eed0016c..00000000 --- "a/Java/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,94 +0,0 @@ -# 举个多线程的小例子 -> 开启10个线程,每个线程对同一个变量进行1000次加1操作。 - -## V1 -```java -public class Test { - int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -// 运行结果 -// 9378 -// 想知道原因,得需要知道Java内存模型... -``` -## V2(volatile) -```java -public class Test { - volatile int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -count++是非原子性操作,即使使用volatile保证内存可见性,但是无法保证原子性,因此,还是凉凉 - -## V3(synchronized) -```java -public class Test { - int count; - synchronized void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -这里可以得到线程安全同步,但是效率似乎有点慢,毕竟这个操作是自增。 - -## V4(CAS) -```java -public class Test { - AtomicInteger count = new AtomicInteger(0); - void m() { - count.incrementAndGet(); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` - -像Volatile、synchronized、CAS稍后再说... \ No newline at end of file diff --git "a/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" "b/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" deleted file mode 100644 index f63c7eed..00000000 --- "a/Java/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" +++ /dev/null @@ -1,38 +0,0 @@ -# 死锁例子 - -## 死锁的四个条件 -- 互斥条件:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) -- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) -- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) -- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗) - -## 例子 -```java -public class Test { - private static Object res1 = new Object(); - private static Object res2 = new Object(); - - public static void main(String[] args) { - new Thread(() -> { - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - // 延迟一下, 确保B拿到了res2 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - } - } - }, "ThreadA").start(); - - new Thread(() -> { - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - // 延迟一下,确保A拿到了res1 - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - } - } - }, "ThreadB").start(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" "b/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" deleted file mode 100644 index d68976cc..00000000 --- "a/Java/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" +++ /dev/null @@ -1,192 +0,0 @@ -# 生产者和消费者 -> 在这里有三种方式实现:synchronized、ReentrantLock和BlockingQueue - -## synchronized -```java -public class ProdConsumerSynchronized { - - private final LinkedList lists = new LinkedList<>(); - - public synchronized void put(String s) { - while (lists.size() != 0) { // 用while怕有存在虚拟唤醒线程 - // 满了, 不生产了 - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - this.notifyAll(); // 这里可是通知所有被挂起的线程,包括其他的生产者线程 - } - - public synchronized void get() { - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - } - - public static void main(String[] args) { - ProdConsumerSynchronized prodConsumerSynchronized = new ProdConsumerSynchronized(); - - // 启动消费者线程 - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerSynchronized::get, "ConsA" + i).start(); - } - - // 启动生产者线程 - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerSynchronized.put("" + tempI); - }, "ProdA" + i).start(); - } - } -} -``` - -## ReentrantLock -```java -public class ProdConsumerReentrantLock { - - private LinkedList lists = new LinkedList<>(); - - private Lock lock = new ReentrantLock(); - - private Condition prod = lock.newCondition(); - - private Condition cons = lock.newCondition(); - - public void put(String s) { - lock.lock(); - try { - // 1. 判断 - while (lists.size() != 0) { - // 等待不能生产 - prod.await(); - } - // 2.干活 - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - // 3. 通知 - cons.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public void get() { - lock.lock(); - try { - // 1. 判断 - while (lists.size() == 0) { - // 等待不能消费 - cons.await(); - } - // 2.干活 - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - // 3. 通知 - prod.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public static void main(String[] args) { - ProdConsumerReentrantLock prodConsumerReentrantLock = new ProdConsumerReentrantLock(); - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerReentrantLock.put(tempI + ""); - }, "ProdA" + i).start(); - } - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerReentrantLock::get, "ConsA" + i).start(); - } - } -} -``` -## BlockingQueue -```java -public class ProdConsumerBlockingQueue { - - private volatile boolean flag = true; - - private AtomicInteger atomicInteger = new AtomicInteger(); - - BlockingQueue blockingQueue = null; - - public ProdConsumerBlockingQueue(BlockingQueue blockingQueue) { - this.blockingQueue = blockingQueue; - } - - public void myProd() throws Exception { - String data = null; - boolean retValue; - while (flag) { - data = atomicInteger.incrementAndGet() + ""; - retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS); - if (retValue) { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 成功"); - } else { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 失败"); - } - TimeUnit.SECONDS.sleep(1); - } - System.out.println(Thread.currentThread().getName() + " 大老板叫停了,flag=false,生产结束"); - } - - public void myConsumer() throws Exception { - String result = null; - while (flag) { - result = blockingQueue.poll(2, TimeUnit.SECONDS); - if (null == result || result.equalsIgnoreCase("")) { - flag = false; - System.out.println(Thread.currentThread().getName() + " 超过2s没有取到蛋糕,消费退出"); - return; - } - System.out.println(Thread.currentThread().getName() + " 消费队列" + result + "成功"); - } - } - - public void stop() { - flag = false; - } - - public static void main(String[] args) { - ProdConsumerBlockingQueue prodConsumerBlockingQueue = new ProdConsumerBlockingQueue(new ArrayBlockingQueue<>(10)); - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 生产线程启动"); - try { - prodConsumerBlockingQueue.myProd(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Prod").start(); - - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 消费线程启动"); - try { - prodConsumerBlockingQueue.myConsumer(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Consumer").start(); - - try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("5s后main叫停,线程结束"); - prodConsumerBlockingQueue.stop(); - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index b5afec2a..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,136 +0,0 @@ -# 线程池 -线程池的好处 -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -## FixedThreadPool -`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现: -```java -/** - * 创建一个可重用固定数量线程的线程池 - */ -public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutor(nThreads, nThreads, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory); -} -``` -**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。** -1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务; -2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`; -3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行; - -不推荐使用 - -**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :** - -1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize; -2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。 -3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数; -4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。 - -## SingleThreadExecutor -```java -/** - *返回只有一个线程的线程池 - */ -public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory)); -} -``` -和上面一个差不多,只不过core和max都被设置为1 - -## CachedThreadPool -```java -/** - * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。 - */ -public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); -} -``` -`CachedThreadPool` 的` corePoolSize` 被设置为空(0),`maximumPoolSize `被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2; -2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成; - -## ThreadPoolExecutor(重点) -```java -/** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ -public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) -``` - -**`ThreadPoolExecutor` 3 个最重要的参数:** - -- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。 -- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: - -- **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁; -- **`unit`** : `keepAliveTime` 参数的时间单位。 -- **`threadFactory`** :executor 创建新线程的时候会用到。 -- **`handler`** :饱和策略。关于饱和策略下面单独介绍一下. - -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) - -- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。 -- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。 - -> Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。) - -**Executors 返回线程池对象的弊端如下**: - -- **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。 -- **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。 - -```java -public class ThreadPoolExecutorDemo { - public static void main(String[] args) { - ExecutorService threadpools = new ThreadPoolExecutor( - 3, - 5, - 1l, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(3), - Executors.defaultThreadFactory(), - new ThreadPoolExecutor.AbortPolicy()); -//new ThreadPoolExecutor.AbortPolicy(); -//new ThreadPoolExecutor.CallerRunsPolicy(); -//new ThreadPoolExecutor.DiscardOldestPolicy(); -//new ThreadPoolExecutor.DiscardPolicy(); - try { - for (int i = 0; i < 8; i++) { - threadpools.execute(() -> { - System.out.println(Thread.currentThread().getName() + "\t办理业务"); - }); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - threadpools.shutdown(); - } - } -} -``` \ No newline at end of file diff --git "a/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c6d28b1e..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,90 +0,0 @@ -# 线程的生命周期 - -先放一张图: -![线程周期图](https://www.pdai.tech/_images/pics/ace830df-9919-48ca-91b5-60b193f593d2.png) - -线程创建之后它将处于**New**(新建)状态,调用 **start()** 方法后开始运行,线程这时候处于 **READY**(可运行) 状态。可运行状态的线程获得了 **CPU 时间片**(timeslice)后就处于 **RUNNING**(运行) 状态。 - -- 当线程执行 **wait()**方法之后,线程进入 **WAITING**(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态 -- 而 **TIME_WAITING**(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 **sleep**(long millis)方法或 **wait**(long millis)方法可以将 Java 线程置于 **TIMED_WAITING** 状态。当超时时间到达后 Java 线程将会返回到 **RUNNABLE** 状态。 -- 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞)状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。 - -## 上下文切换 - -> 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,**CPU 采取的策略是为每个线程分配时间片并轮转的形式**。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 -实际上就是任务从保存到再加载的过程就是一次上下文切换。 上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 - -## 直接调用Thread的run方法不行吗? -### 区别 -> Java中启动线程有两种方法,继承Thread类和实现Runnable接口,由于Java无法实现多重继承,所以一般通过实现Runnable接口来创建线程。但是无论哪种方法都可以通过start()和run()方法来启动线程,下面就来介绍一下他们的区别。 - -#### start -通过该方法启动线程的同时**也创建了一个线程,真正实现了多线程**。**无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码**。此时start()的这个线程处于**就绪状态**,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。 - -#### run -通过run方法启动线程其实就是调用一个类中的方法,**当作普通的方法的方式调用**。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。 - -```java -public class Test { - public static void main(String[] args) { - Thread t = new Thread(){ - public void run() { - cat(); - } - }; - t.run(); - System.out.println("dream"); - } - - static void cat() { - System.out.println("cat"); - } -} -// 结果: -// cat -// dream -``` -如果将`t.run()`改成`t.start()`; -```java -dream -cat -``` - -### 探究一下源码 -看一下start方法源码: -```java -public synchronized void start() { - if (threadStatus != 0) - throw new IllegalThreadStateException(); - group.add(this); - - boolean started = false; - try { - start0(); - started = true; - } finally { - try { - if (!started) { - group.threadStartFailed(this); - } - } catch (Throwable ignore) { - /* do nothing. If start0 threw a Throwable then - it will be passed up the call stack */ - } - } -} - -private native void start0(); -``` - 可以看到,当一个线程启动的时候,它的状态(threadStatus)被设置为0,如果不为0,则抛出`IllegalThreadStateException`异常。正常的话,将该**线程加入线程组**,最后尝试调用start0方法,**而start0方法是私有的native方法**(Native Method是一个java调用非java代码的接口)。 - -我们再看一下run方法源码: -```java -public void run() { - if (target != null) { - target.run(); - } -} -``` -target的定义:`private Runnable target;` -所以其实就是一个Runnable接口,正如上面代码中new Thread的部分,其实我们就是在实现它的run()方法。所以如果**直接调用run,就和一个普通的方法没什么区别,是不会创建新的线程的,因为压根就没执行start0方法**。 diff --git "a/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" "b/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" deleted file mode 100644 index 08795ae1..00000000 --- "a/Java/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" +++ /dev/null @@ -1,53 +0,0 @@ -# 线程的启动方式 - -## Thread -```java -public class Test extents Thread { - public void run() { - // 重写Thread的run方法 - System.out.println("dream"); - } - - public static void main(String[] args) { - new Test().start(); - } -} -``` - -## Runnable -```java -public class Test { - public static void main(String[] args) { - new Thread(() -> { - System.out.println("dream"); - }).start(); - } -} -``` - -## Callable -```java -public class Test { - public static void main(String[] args) { - // FutureTask 构造方法包装了Callable和Runnable。 - FutureTask task = new FutureTask<>(() -> { - System.out.println("dream"); - return 0; - }); - new Thread(task).start(); - } -} -``` - -## 线程池 -```java -public class Test { - public static void main(String[] args) { - ExecutorService threadPool = Executors.newFixedThreadPool(1); - threadPool.submit(() -> { - System.out.println("dream"); - }); - threadPool.shutdown(); - } -} -``` diff --git "a/Java/linux/linux-\345\237\272\347\241\200.md" "b/Java/linux/linux-\345\237\272\347\241\200.md" deleted file mode 100644 index fb815ec4..00000000 --- "a/Java/linux/linux-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,695 +0,0 @@ -## 引言 - -**linux-基础** - -## 常用操作以及概念 - -### 快捷键 - -- Tab: 命令和文件名补全; -- Ctrl+C: 中断正在运行的程序; -- Ctrl+D: 结束键盘输入(End Of File,EOF) - -### 求助 - - - -#### --help - -指令的基本用法与选项介绍。 - -#### man - -man 是 manual 的缩写,将指令的具体信息显示出来。 - -#### info - -info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 - -#### doc - -/usr/share/doc 存放着软件的一整套说明文件。 - -### 关机 - -#### who - -在关机前需要先使用 who 命令查看有没有其它用户在线。 - -#### sync - -为了加快对磁盘文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 - -#### shutdown - -```bash -## shutdown [-krhc] 时间 [信息] --k : 不会关机,只是发送警告信息,通知所有在线的用户 --r : 将系统的服务停掉后就重新启动 --h : 将系统的服务停掉后就立即关机 --c : 取消已经在进行的 shutdown 指令内容 -``` - -### PATH - -可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 - -`/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin` - -### sudo - -sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令. - -### 包管理工具 - -RPM 和 DPKG 为最常见的两类软件包管理工具: - -- RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。 -- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。 - -YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能。 - -### 发行版 - -Linux 发行版是 Linux 内核及各种应用软件的集成版本。 - -| 基于的包管理工具 | 商业发行版 | 社区发行版 | -| :--------------: | :--------: | :-------------: | -| RPM | Red Hat | Fedora / CentOS | -| DPKG | Ubuntu | Debian | - -### VIM - -- 一般指令模式(Command mode): VIM 的默认模式,可以用于移动游标查看内容; -- 编辑模式(Insert mode): 按下 "i" 等按键之后进入,可以对文本进行编辑; -- 指令列模式(Bottom-line mode): 按下 ":" 按键之后进入,用于保存退出等操作。 - -在指令列模式下,有以下命令用于离开或者保存文件。 - -| 命令 | 作用 | -| :--: | :----------------------------------------------------------: | -| :w | 写入磁盘 | -| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 | -| :q | 离开 | -| :q! | 强制离开不保存 | -| :wq | 写入磁盘后离开 | -| :wq! | 强制写入磁盘后离开 | - -## 磁盘 - -### 磁盘接口 - -#### IDE - -IDE(ATA)全称 Advanced Technology Attachment,接口速度最大为 133MB/s,因为并口线的抗干扰性太差,且排线占用空间较大,不利电脑内部散热,已逐渐被 SATA 所取代。 - -#### SATA - -SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能。SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 - -#### SCSI - -SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 - -#### SAS - -SAS(Serial Attached SCSI)是新一代的 SCSI 技术,和 SATA 硬盘相同,都是采取序列式技术以获得更高的传输速度,可达到 6Gb/s。此外也透过缩小连接线改善系统内部空间等。 - -## 分区 - -### 分区表 - -磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 - -#### MBR - -MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。 - -分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 - -Linux 也把分区当成文件,分区文件的命名方式为: 磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 - -#### GPT - -不同的磁盘有不同的扇区大小,例如 512 bytes 和最新磁盘的 4 k。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA),LBA 默认大小为 512 bytes。 - -GPT 第 1 个区块记录了主要开机记录(MBR),紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。这 33 个区块第一个为 GPT 表头纪录,这个部份纪录了分区表本身的位置与大小和备份分区的位置,同时放置了分区表的校验码 (CRC32),操作系统可以根据这个校验码来判断 GPT 是否正确。若有错误,可以使用备份分区进行恢复。 - -GPT 没有扩展分区概念,都是主分区,每个 LAB 可以分 4 个分区,因此总共可以分 4 * 32 = 128 个分区。 - -MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB = 8 ZB。 - -### 开机检测程序 - -#### BIOS - -BIOS(Basic Input/Output System,基本输入输出系统),它是一个固件(嵌入在硬件中的软件),BIOS 程序存放在断电后内容不会丢失的只读内存中。 - -BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 - -主要开机记录(MBR)中的开机管理程序提供以下功能: 选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动开机管理程序时,就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 - -下图中,第一扇区的主要开机记录(MBR)中的开机管理程序提供了两个选单: M1、M2,M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 - -安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉主要开机记录(MBR),而 Linux 可以选择将开机管理程序安装在主要开机记录(MBR)或者其它分区的启动扇区,并且可以设置开机管理程序的选单。 - -#### UEFI - -BIOS 不可以读取 GPT 分区表,而 UEFI 可以。 - -## 文件系统 - -### 分区与文件系统 - -对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统。 - -### 组成 - -最主要的几个组成部分如下: - -- inode: 一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 编号; -- block: 记录文件的内容,文件太大时,会占用多个 block。 -- superblock: 记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; -- block bitmap: 记录 block 是否被使用的位域。 - -### 文件读取 - -对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 - -### 磁盘碎片 - -指一个文件内容所在的 block 过于分散。 - -### Block - -在 Ext2 文件系统中所支持的 block 大小有 1K,2K 及 4K 三种,不同的大小限制了单个文件和文件系统的最大大小。 - -一个 block 只能被一个文件所使用,未使用的部分直接浪费了。因此如果需要存储大量的小文件,那么最好选用比较小的 block。 - -### inode - -inode 具体包含以下信息: - -- 权限 (read/write/excute); -- 拥有者与群组 (owner/group); -- 容量; -- 建立或状态改变的时间 (ctime); -- 最近一次的读取时间 (atime); -- 最近修改的时间 (mtime); -- 定义文件特性的旗标 (flag),如 SetUID...; -- 该文件真正内容的指向 (pointer)。 - -inode 具有以下特点: - -- 每个 inode 大小均固定为 128 bytes (新的 ext4 与 xfs 可设定到 256 bytes); -- 每个文件都仅会占用一个 inode。 - -inode 中记录了文件内容所在的 block 编号,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block 编号。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块记录引用信息。 - -### 目录 - -建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。 - -可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 - -### 日志 - -如果突然断电,那么文件系统会发生错误,例如断电前只修改了 block bitmap,而还没有将数据真正写入 block 中。 - -ext3/ext4 文件系统引入了日志功能,可以利用日志来修复文件系统。 - -### 挂载 - -挂载利用目录作为文件系统的进入点,也就是说,进入目录之后就可以读取文件系统的数据。 - -### 目录配置 - -为了使不同 Linux 发行版本的目录结构保持一致性,Filesystem Hierarchy Standard (FHS) 规定了 Linux 的目录结构。最基础的三个目录如下: - -- / (root, 根目录) -- /usr (unix software resource): 所有系统默认软件都会安装到这个目录; -- /var (variable): 存放系统或程序运行过程中的数据文件。 - -## 文件 - -### 文件属性 - -用户分为三种: 文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 - -使用 ls 查看一个文件时,会显示一个文件的信息,例如 `drwxr-xr-x. 3 root root 17 May 6 00:14 .config`,对这个信息的解释如下: - -- drwxr-xr-x: 文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段 -- 3: 链接数 -- root: 文件拥有者 -- root: 所属群组 -- 17: 文件大小 -- May 6 00:14: 文件最后被修改的时间 -- .config: 文件名 - -常见的文件类型及其含义有: - -- d: 目录 -- -: 文件 -- l: 链接文件 - -9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 - -文件时间有以下三种: - -- modification time (mtime): 文件的内容更新就会更新; -- status time (ctime): 文件的状态(权限、属性)更新就会更新; -- access time (atime): 读取文件时就会更新。 - -### 文件与目录的基本操作 - -#### ls - -列出文件或者目录的信息,目录的信息就是其中包含的文件。 - -```bash -## ls [-aAdfFhilnrRSt] file|dir --a : 列出全部的文件 --d : 仅列出目录本身 --l : 以长数据串行列出,包含文件的属性与权限等等数据 - -``` - -#### cd - -更新当前目录 - -`cd [相对路径或者绝对路径]` - -#### mkdir - -创建目录 - -```bash -## mkdir [-mp] 目录名称 --m : 配置目录权限 --p : 递归创建目录 -``` - -#### rmdir - -删除目录,目录必须为空 - -```bash -rmdir [-p] 目录名称 --p : 递归删除目录 -``` - -#### touch - -更新文件时间或者建立新文件 - -```bash -## touch [-acdmt] filename --a : 更新 atime --c : 更新 ctime,若该文件不存在则不建立新文件 --m : 更新 mtime --d : 后面可以接更新日期而不使用当前日期,也可以使用 --date="日期或时间" --t : 后面可以接更新时间而不使用当前时间,格式为[YYYYMMDDhhmm] - -``` - -#### cp - -复制文件 - -如果源文件有两个以上,则目的文件一定要是目录才行。 - -```bash -cp [-adfilprsu] source destination --a : 相当于 -dr --preserve=all 的意思,至于 dr 请参考下列说明 --d : 若来源文件为链接文件,则复制链接文件属性而非文件本身 --i : 若目标文件已经存在时,在覆盖前会先询问 --p : 连同文件的属性一起复制过去 --r : 递归持续复制 --u : destination 比 source 旧才更新 destination,或 destination 不存在的情况下才复制 ---preserve=all : 除了 -p 的权限相关参数外,还加入 SELinux 的属性, links, xattr 等也复制了 - -``` - -#### rm - -删除文件 - -```bash -## rm [-fir] 文件或目录 --r : 递归删除 -``` - -#### mv - -移动文件 - -```bash -## mv [-fiu] source destination -## mv [options] source1 source2 source3 .... directory --f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -``` - -### 修改权限 - -可以将一组权限用数字来表示,此时一组权限的 3 个位当做二进制数字的位,从左到右每个位的权值为 4、2、1,即每个权限对应的数字权值为 r : 4、w : 2、x : 1。 - -`## chmod [-R] xyz dirname/filename` - -示例: 将 .bashrc 文件的权限修改为 -rwxr-xr--。 - -`## chmod 754 .bashrc` - -也可以使用符号来设定权限。 - -```bash -## chmod [ugoa] [+-=] [rwx] dirname/filename -- u: 拥有者 -- g: 所属群组 -- o: 其他人 -- a: 所有人 -- +: 添加权限 -- -: 移除权限 -- =: 设定权限 -``` - -示例: 为 .bashrc 文件的所有用户添加写权限。 - -`## chmod a+w .bashrc` - -### 文件默认权限 - -- 文件默认权限: 文件默认没有可执行权限,因此为 666,也就是 -rw-rw-rw- 。 -- 目录默认权限: 目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是 drwxrwxrwx。 - -可以通过 umask 设置或者查看文件的默认权限,通常以掩码的形式来表示,例如 002 表示其它用户的权限去除了一个 2 的权限,也就是写权限,因此建立新文件时默认的权限为 -rw-rw-r--。 - -### 目录的权限 - -文件名不是存储在一个文件的内容中,而是存储在一个文件所在的目录中。因此,拥有文件的 w 权限并不能对文件名进行修改。 - -目录存储文件列表,一个目录的权限也就是对其文件列表的权限。因此,目录的 r 权限表示可以读取文件列表;w 权限表示可以修改文件列表,具体来说,就是添加删除文件,对文件名进行修改;x 权限可以让该目录成为工作目录,x 权限是 r 和 w 权限的基础,如果不能使一个目录成为工作目录,也就没办法读取文件列表以及对文件列表进行修改了。 - -### 链接 - -```bash -## ln [-sf] source_filename dist_filename --s : 默认是 hard link,加 -s 为 symbolic link --f : 如果目标文件存在时,先删除目标文件 -``` - -### 实体链接 - -在目录下创建一个条目,记录着文件名与 inode 编号,这个 inode 就是源文件的 inode。 - -删除任意一个条目,文件还是存在,只要引用数量不为 0。 - -有以下限制: 不能跨越文件系统、不能对目录进行链接。 - -```bash -## ln /etc/crontab . -## ll -i /etc/crontab crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -``` - -### 符号链接 - -符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式。 - -当源文件被删除了,链接文件就打不开了。 - -可以为目录建立链接。 - -```bash -## ll -i /etc/crontab /root/crontab2 -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab -``` - -### 获取文件内容 - -#### cat - -取得文件内容。 - -```bash -## cat [-AbEnTv] filename --n : 打印出行号,连同空白行也会有行号,-b 不会 -``` - -#### tac - -是 cat 的反向操作,从最后一行开始打印。 - -#### more - -和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。 - -#### less - -和 more 类似,但是多了一个向前翻页的功能。 - -#### head - -取得文件前几行。 - -```bash -## head [-n number] filename --n : 后面接数字,代表显示几行的意思 -``` - -#### tail - -是head的反向操作,之水取得是后几行 - -#### od - -以字符或者十六进制的形式显示二进制文件。 - -### 指令与文件搜索 - -#### which - -指令搜索 - -```bash -## which [-a] command --a : 将所有指令列出,而不是只列第一个 -``` - -#### whereis - -文件搜索。速度比较快,因为它只搜索几个特定的目录。 - -```bash -## whereis [-bmsu] dirname/filename -``` - -#### locate - -文件搜索。可以用关键字或者正则表达式进行搜索。 - -locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内存中,并且每天更新一次,所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。 - -```bash -## locate [-ir] keyword --r: 正则表达式 -``` - -#### find - -文件搜索。可以使用文件的属性和权限进行搜索。 - -```bash -## find [basedir] [option] -example: find . -name "shadow*" -``` - -## 压缩与打包 - -### 压缩文件名 - -#### gzip - -gzip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。 - -经过 gzip 压缩过,源文件就不存在了。 - -有 9 个不同的压缩等级可以使用。 - -可以使用 zcat、zmore、zless 来读取压缩文件的内容。 - -```bash -$ gzip [-cdtv#] filename --c : 将压缩的数据输出到屏幕上 --d : 解压缩 --t : 检验压缩文件是否出错 --v : 显示压缩比等信息 --## : ## 为数字的意思,代表压缩等级,数字越大压缩比越高,默认为 6 -``` - -#### bzip2 - -提供比 gzip 更高的压缩比。 - -查看命令: bzcat、bzmore、bzless、bzgrep。 - -#### xz - -提供比 bzip2 更佳的压缩比。 - -可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意的是,压缩比越高,压缩的时间也越长。 - -查看命令: xzcat、xzmore、xzless、xzgrep。 - -### 打包 - -压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。 - -```bash -$ tar [-z|-j|-J] [cv] [-f 新建的 tar 文件] filename... ==打包压缩 -$ tar [-z|-j|-J] [tv] [-f 已有的 tar 文件] ==查看 -$ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目录] ==解压缩 --z : 使用 zip; --j : 使用 bzip2; --J : 使用 xz; --c : 新建打包文件; --t : 查看打包文件里面有哪些文件; --x : 解打包或解压缩的功能; --v : 在压缩/解压缩的过程中,显示正在处理的文件名; --f : filename: 要处理的文件; --C 目录 : 在特定目录解压缩。 -``` - -| 使用方式 | 命令 | -| :------: | ----------------------------------------------------- | -| 打包压缩 | tar -jcv -f filename.tar.bz2 要被压缩的文件或目录名称 | -| 查 看 | tar -jtv -f filename.tar.bz2 | -| 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 | - -## bash - -可以通过 Shell 请求内核提供服务,Bash 正是 Shell 的一种。 - -### 特性 - -- 命令历史:记录使用过的命令 -- 命令与文件补全:快捷键:tab -- shell scripts -- 通配符: 例如 ls -l /usr/bin/X* 列出 /usr/bin 下面所有以 X 开头的文件 - -### 变量操作 - -对一个变量赋值直接使用 =。 - -对变量取用需要在变量前加上 $ ,也可以用 ${} 的形式; - -输出变量使用 echo 命令。 - -```bash -$ x=abc -$ echo $x -$ echo ${x} -``` - -变量内容如果有空格,必须使用双引号或者单引号。 - -- 双引号内的特殊字符可以保留原本特性,例如 x="lang is $LANG",则 x 的值为 lang is zh_TW.UTF-8; -- 单引号内的特殊字符就是特殊字符本身,例如 x='lang is $LANG',则 x 的值为 lang is $LANG。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -Bash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明,默认是字符串类型。变量的声明使用 declare 命令: - -```bash -$ declare [-aixr] variable --a : 定义为数组类型 --i : 定义为整数类型 --x : 定义为环境变量 --r : 定义为 readonly 类型 -``` - -对数组操作 - -```bash -$ array[1]=a -$ array[2]=b -$ echo ${array[1]} -``` - -## 正则 - -g/re/p(globally search a regular expression and print),使用正则表示式进行全局查找并打印。 - -```bash -$ grep [-acinv] [--color=auto] 搜寻字符串 filename --c : 统计个数 --i : 忽略大小写 --n : 输出行号 --v : 反向选择,也就是显示出没有 搜寻字符串 内容的那一行 ---color=auto : 找到的关键字加颜色显示 -``` - -示例: 把含有 the 字符串的行提取出来(注意默认会有 --color=auto 选项,因此以下内容在 Linux 中有颜色显示 the 字符串) - -```bash -$ grep -n 'the' regular_express.txt -8:I can't finish the test. -12:the symbol '*' is represented as start. -15:You are the best is mean you are the no. 1. -16:The world Happy is the same with "glad". -18:google is the best tools for search keyword -``` - -## 进程管理 - -### 查看进程 - -#### ps - -查看某个时间点的进程信息 - -示例一: 查看自己的进程`ps -l` - -示例二: 查看系统所有进程`ps aux` - -示例三: 查看特定的进程`ps aux | grep python` - -#### top - -实时显示进程信息 - -示例: 两秒钟刷新一次`top -d 2` - -#### pstree - -查看进程树 - -示例: 查看所有进程树`pstree -A` - -#### netstat - -查看占用端口的进程 - -示例: 查看特定端口的进程`netstat -anp | grep port` - -### 孤儿进程 - -一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 - -孤儿进程将被init进程(进程号为1)所收养,并有init进程对它们完成状态收集工作。 - -由于孤儿进程会被init进程收养,所以孤儿进程不会对系统造成危害。 - -### 僵尸进程 - -一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过wait()或者waitpid()获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用wait()或waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。 - -僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。 - -系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 - -要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。 \ No newline at end of file diff --git "a/Java/other/\345\270\270\347\224\250sql.md" "b/Java/other/\345\270\270\347\224\250sql.md" deleted file mode 100644 index 43109e85..00000000 --- "a/Java/other/\345\270\270\347\224\250sql.md" +++ /dev/null @@ -1,1247 +0,0 @@ - -### 创建数据库并制定编码 - -- ```sql - Create database 数据库名 default character set utf8; - Create database ssm default character set utf8; - ``` - -### 删除数据库 - -- ```sql - drop database 数据名; - drop database ssm; - ``` - - ### 创建表 - -- 一般情况: - -- ```sql - Create table 表名 ( - 列名 类型 约束 auto_increment comment '备注'; - ); - - create tablie flower ( - id int(10) primary key auto_increment comment '编号', - name varchar(30) not null comment '花名', - price float not null comment '价格', - production varchar(30) not null comment '原产地' - ); - - create tablie flower ( - `id` int(10) not null auto_increment comment '编号', - `name` varchar(30) not null comment '花名', - `price` float not null comment '价格', - `production` varchar(30) not null comment '原产地', - primary key (`id`) - ); - ``` - -### 插入信息 - -- ```sql - insert into 表明(姓名,性别,年龄) values('李一', '女', '18'); - insert into `flower` (`id`, `name`, `price`, `production`) values (defalut, '矮牵牛', 2.5, '南美阿根廷') - ``` - -### 查询信息 - -- ```sql - select * from 表名; - select * from flower; - select * from 表名 where ID=5 - select * from flower where ID=1 - ``` - -### 更新信息 - -- ```sql - update 表名 set name='李一' where name='王五'; - update flower set price=1.2 where id=1; - ``` - -### 删除信息 - -- ```sql - delete from 表名 where id=1; - delete from flower where id=1; - ``` - -### 删除表 - -- ```sql - drop table 表名; - drop table flower; - ``` - -### 查看表信息 - -- ```sql - desc 表名; - desc flower; - ``` - -### 一对多关系 - -- 可能是写的不熟练吧,所以要做个笔记,以免之后再次忘掉。 - -- 例子:假如有这样的一对多关系,首先有一个User模型,其中该模型有id、name和gender字段。还有一个Artical模型,其中该模型有id、title、content和user_id字段,而user_id则是外键。如下图: - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170733.png) - -- 那么在mysql或者mysql的一些工具下创建输入以下代码: - -- ```sql - create database demo default character set utf8; -- 创建数据库 - - use demo; - - -- 创建user表格 - create table user ( - `id` int(10) not null auto_increment comment '编号', - `name` varchar(16) not null comment '姓名', - `gender` varchar(4) not null comment '性别', - primary key (`id`) - ); - - -- 创建artical - create table artical ( - `id` int(10) not null auto_increment comment '文章编号', - `title` varchar(32) not null comment '标题', - `content` text default null comment '内容', - `user_id` int(10) not null comment '用户编号', - primary key (`id`), - key (`user_id`), - constraint `artical_idfk_1` foreign key (`user_id`) references `user` (`id`) -- 这句话非常重要 constraint ··· 约束 - ); - - -- 在user中插入数据 - insert into user (`id`,`name`,`gender`) values(default, 'Maifeng', '男'); - insert into user (`id`,`name`,`gender`) values(default, 'Liumeng', '女'); - - -- 在artical中插入数据 - insert into `artical` (`id`, `title`, `content`, `user_id`) values (default, '我是mm', 'null', 1); - insert into `artical` (`id`, `title`, `content`, `user_id`) values (default, '我是xxx', 'null', 2); - ``` - -- 如下图user和artical表 - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170817.png) - -- ![](https://raw.githubusercontent.com/DreamCats/PicBed/master/20191121170856.png) - - - -### 更改auto_increment - -- ```sql - alter table 表名 auto_increment=5; - alter table flower auto_increment=5; - ``` - -- - -### 常用sql语句 - -#### 数据库 - -```mysql -# 查看所有数据库 -show databases; - -# 创建一个数据库 -create database demo; - -# 删除一个数据库 -drop dababase demo; - -# 使用这个数据库 -use demo; - -``` - -#### 表 - -```mysql -# 查看所有的表 -show tables; - -# 创建一个表 -create table n(`id` int, `name` varchar(10)); -create table m(`id` int, `name` varchar(10), primary key(`id`), foreign key(`id`) references n(`id`), unique); -create table m(`id` int, `name` varchar(10)); - -# 直接将查询结果导入或复制到新创建的表 -create table n select * from m; - -# 将创建的表与一个存在的表的数据结构类似 -create table m like n; - -# 创建一个临时表 -# 临时表将在你连接MySQL期间存在。当断开连接时,MySQL将自动删除表并释放所用的空间。也可手动删除。 -create temporary table l(`id` int, name varchar(10)); -# 直接将查询结果导入或复制到新的临时表 -create temporary table tt select * from n; - -# 删除一个已经存在的表 -drop table if exists m; - -# 更改存在表的名称 -alter table n rename m; -rename table n to m; - -# 查看表的结构(5种,效果相同) -desc n; -describe n; -show columns in n; -show columns from n; -explain n; -# 查看表的创建语句 -show create table n; -``` - -#### 表的结构 - -```mysql -# 添加字段 -alter table n add age varchar(2); -# 删除字段 -alter table n drop age; -# 更改字段属性和属性 -alter table n change age a int; -# 只更改字段属性 -alter table n modify age varchar(7); -``` - -#### 表的数据 - -```mysql -# 增加数据 -insert into n values(1, 'tom', '23'), (2, 'john', '22'); -insert into n select * from n; # 把数据复制一遍重新插入 -# 删除数据 -delete from n where id = 2; -# 更改数据 -update n set name = 'tom' where id = 2; -# 数据查找 -select * from n where name like '%h%'; -# 数据排序(反序) -select * from n order by name, id desc; -``` - -#### 键 - -```mysql -# 添加主键 -alter table n add primary key(id); -# 删除主键 -alter table n drop primary key; -# 添加外键 -alter table m add foreign key(id) references n(id); # 自动生成键名m_ibfk_1 -# 自定义名称的外键 -alter table m add constraint fk_id foreign key(id) references n(id); -# 删除外键 -alter table m drop foreign key `fk_id`; -# 修改外键 -alter table m drop foreign key `fk_id`, add constraint fk_id2 foreign key(id) references n(id); -# 添加唯一键 -alter table n add unique (name); -alter table n add unique u_name (name); -alter table n add unique index u_name (name); -alter table n add constraint u_name unique (name); -create unique index u_name on n(name); -# 添加索引 -alter table n add index (age); -alter table n add index i_age (age); -create index i_age on n(age); - -# 删除索引或唯一键 -drop index u_name on n; -drop index i_age on n; -``` - -#### 视图 - -```mysql -# 创建视图 -create view v as select id, name from n; -create view v(id, name) as select id, name from n; -# 查看视图(与表操作类似) -select * from v; -desc v; -# 查看视图语句 -show create view v; -# 更改视图 -CREATE OR REPLACE VIEW v AS SELECT name, age FROM n; -ALTER VIEW v AS SELECT name FROM n; -# 删除视图 -drop view if exists v; -``` - -#### 联接 - -```mysql -# 内联接 -select * from m inner join n on m.id = n.id; -# 左外连接 -select * from m left join n on m.id = n.id; -# 右外连接 -select * from m right join n on m.id = n.id; -# 交叉连接 -select * from m cross join n; # 标准写法 -select * from m,n; -# 类似于全连接full join 的联接用法 -select id, name from m -union -select id, name from n -``` - -#### 函数 - -```mysql -# 聚合函数 -select count(id) as total from n; # 总数 -select sum(age) as all_age from n; # 总和 -select avg(age) as all_age from n; # 平均值 -select max(age) as all_age from n; # 最大值 -select min(age) as all_age from n; # 最小值 -# 数学函数 -select abs(-5); # 绝对值 -select bin(15), oct(15), hex(15); # 二进制,八进制,十六进制 -SELECT pi(); # 圆周率3.141593 -SELECT ceil(5.5); # 大于x的最小整数值6 -SELECT floor(5.5); # 小于x的最大整数值5 -SELECT greatest(3,1,4,1,5,9,2,6); # 返回集合中最大的值9 -SELECT least(3,1,4,1,5,9,2,6); # 返回集合中最小的值1 -SELECT mod(5,3); # 余数2 -SELECT rand(); # 返回0到1内的随机值,每次不一样 -SELECT rand(5); # 提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。 -SELECT round(1415.1415); # 四舍五入1415 -SELECT round(1415.1415, 3); # 四舍五入三位数1415.142 -SELECT round(1415.1415, -1); # 四舍五入整数位数1420 -SELECT truncate(1415.1415, 3); # 截短为3位小数1415.141 -SELECT truncate(1415.1415, -1); # 截短为-1位小数1410 -SELECT sign(-5); # 符号的值负数-1 -SELECT sign(5); # 符号的值正数1 -SELECT sqrt(9); # 平方根3 -SELECT sqrt(9); # 平方根3 -# 字符串函数 -SELECT concat('a', 'p', 'p', 'le'); # 连接字符串-apple -SELECT concat_ws(',', 'a', 'p', 'p', 'le'); # 连接用','分割字符串-a,p,p,le -SELECT insert('chinese', 3, 2, 'IN'); # 将字符串'chinese'从3位置开始的2个字符替换为'IN'-chINese -SELECT left('chinese', 4); # 返回字符串'chinese'左边的4个字符-chin -SELECT right('chinese', 3); # 返回字符串'chinese'右边的3个字符-ese - -SELECT substring('chinese', 3); # 返回字符串'chinese'第三个字符之后的子字符串-inese -SELECT substring('chinese', -3); # 返回字符串'chinese'倒数第三个字符之后的子字符串-ese -SELECT substring('chinese', 3, 2); # 返回字符串'chinese'第三个字符之后的两个字符-in -SELECT trim(' chinese '); # 切割字符串' chinese '两边的空字符-'chinese' -SELECT ltrim(' chinese '); # 切割字符串' chinese '两边的空字符-'chinese ' -SELECT rtrim(' chinese '); # 切割字符串' chinese '两边的空字符-' chinese' -SELECT repeat('boy', 3); # 重复字符'boy'三次-'boyboyboy' -SELECT reverse('chinese'); # 反向排序-'esenihc' -SELECT length('chinese'); # 返回字符串的长度-7 -SELECT upper('chINese'), lower('chINese'); # 大写小写 CHINESE chinese -SELECT ucase('chINese'), lcase('chINese'); # 大写小写 CHINESE chinese -SELECT position('i' IN 'chinese'); # 返回'i'在'chinese'的第一个位置-3 -SELECT position('e' IN 'chinese'); # 返回'i'在'chinese'的第一个位置-5 -SELECT strcmp('abc', 'abd'); # 比较字符串,第一个参数小于第二个返回负数- -1 -SELECT strcmp('abc', 'abb'); # 比较字符串,第一个参数大于第二个返回正数- 1 -# 时间函数 -SELECT current_date, current_time, now(); # 2018-01-13 12:33:43 2018-01-13 12:33:43 -SELECT hour(current_time), minute(current_time), second(current_time); # 12 31 34 -SELECT year(current_date), month(current_date), week(current_date); # 2018 1 1 -SELECT quarter(current_date); # 1 -SELECT monthname(current_date), dayname(current_date); # January Saturday -SELECT dayofweek(current_date), dayofmonth(current_date), dayofyear(current_date); # 7 13 13 -# 控制流函数 -SELECT if(3>2, 't', 'f'), if(3<2, 't', 'f'); # t f -SELECT ifnull(NULL, 't'), ifnull(2, 't'); # t 2 -SELECT isnull(1), isnull(1/0); # 0 1 是null返回1,不是null返回0 -SELECT nullif('a', 'a'), nullif('a', 'b'); # null a 参数相同或成立返回null,不同或不成立则返回第一个参数 -SELECT CASE 2 - WHEN 1 THEN 'first' - WHEN 2 THEN 'second' - WHEN 3 THEN 'third' - ELSE 'other' - END ; # second -# 系统信息函数 -SELECT database(); # 当前数据库名-test -SELECT connection_id(); # 当前用户id-306 -SELECT user(); # 当前用户-root@localhost -SELECT version(); # 当前mysql版本 -SELECT found_rows(); # 返回上次查询的检索行数 -``` - -#### 用户 - -```mysql -# 增加用户 -CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; -INSERT INTO mysql.user(Host, User, Password) VALUES ('localhost', 'test', Password('test')); # 在用户表中插入用户信息,直接操作User表不推荐 -# 删除用户 -DROP USER 'test'@'localhost'; -DELETE FROM mysql.user WHERE User='test' AND Host='localhost'; -FLUSH PRIVILEGES ; -# 更改用户密码 -SET PASSWORD FOR 'test'@'localhost' = PASSWORD('test'); -UPDATE mysql.user SET Password=Password('t') WHERE User='test' AND Host='localhost'; -FLUSH PRIVILEGES ; -# 用户授权 -GRANT ALL PRIVILEGES ON *.* TO test@localhost IDENTIFIED BY 'test'; -# 授予用'test'密码登陆成功的test@localhost用户操作所有数据库的所有表的所有的权限 -FLUSH PRIVILEGES ; # 刷新系统权限表,使授予权限生效 -# 撤销用户授权 -REVOKE DELETE ON *.* FROM 'test'@'localhost'; # 取消该用户的删除权限 -``` - -#### 其他语句 - -```mysql -# 查看所有的表信息(包括视图) -SHOW TABLE STATUS; -``` - -### 小破站学习的mysql - -- [美女老师](https://www.bilibili.com/video/av49181542?p=1) -- [sql表](https://blog.csdn.net/GongmissYan/article/details/102937816) - -#### 先贴sql - -```sql -CREATE TABLE `t_user` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '账户', - `password` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码', - `gender` varchar(11) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', - `age` int(11) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -INSERT INTO `t_user` (`id`, `user_name`, `password`, `gender`, `age`) -VALUES - (1, 'abc', '123', '0', 20), - (2, 'a', '123', '0', 20), - (3, 'b', '123', '1', 21), - (4, 'c', '123', '0', 22), - (5, 'dae', '123', '1', 23), - (6, 'e', '123', '0', 24), - (7, 'f', '123', '0', 25); - -``` - -### 基础查询 - -```sql -/* -语法: - select 查询列表 from 表明; -特点: - 1. 查询列表可以是:表中的字段、常量值、表达式、函数 - 2. 查询的结构是一个虚拟的表格 -*/ - /* - 别名 - ①便于理解 - ②如果要查询的字段有重名的情况,使用别名可以区分开来 - - */ - # +号的作用 - /* -select 100+90; 两个操作数都为数值型,则做加法运算 -select '123'+90;只要其中一方为字符型,试图将字符型数值转换成数值型 - 如果转换成功,则继续做加法运算 -select 'john'+90; 如果转换失败,则将字符型数值转换成0 - -select null+10; 只要其中一方为null,则结果肯定为null - */ -# 题目 - -#1. 下面的语句是否可以执行成功 :能 - SELECT - last_name , - job_id , - salary AS sal -FROM - employees; -#2.下面的语句是否可以执行成功 :能 - SELECT - * -FROM - employees; -#3.找出下面语句中的错误 :有个中文标点符号 - SELECT - employee_id , - last_name, - salary * 12 AS "ANNUAL SALARY" -FROM - employees; - -#4.显示表departments的结构,并查询其中的全部数据 -DESC departments ; -SELECT * FROM departments; -#5.显示出表employees中的全部job_id(不能重复) -SELECT DISTINCT job_id FROM employees; - -``` - -### 条件查询 - -```sql -# 2: 条件查询 -/* - -语法: - select - 查询列表 - from - 表名 - where - 筛选条件; - -分类: - 一、按条件表达式筛选 - - 简单条件运算符:> < = != <> >= <= - - 二、按逻辑表达式筛选 - 逻辑运算符: - 作用:用于连接条件表达式 - && || ! - and or not - - &&和and:两个条件都为true,结果为true,反之为false - ||或or: 只要有一个条件为true,结果为true,反之为false - !或not: 如果连接的条件本身为false,结果为true,反之为false - - 三、模糊查询 - like - between and - in - is null -*/ -# 1. 查询年龄大于22的用户信息 -SELECT - * -FROM - `t_user` -WHERE - `age` > 22; - -# 2. 查询年龄不等于22的用户和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` <> 20; - -# 查询年龄在21到23之间的用户名和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` >=21 AND `age` <= 23; -SELECT - `user_name`, - `password`, - `age` -FROM - `t_user` -WHERE - `age` BETWEEN 21 AND 23; - -# 查询年龄21和23的用户名和密码 -SELECT - `user_name`, - `password` -FROM - `t_user` -WHERE - `age` IN(21, 23); - -# 模糊查询 like -/* -特点: -①一般和通配符搭配使用 - 通配符: - % 任意多个字符,包含0个字符 - _ 任意单个字符 -*/ -# 查询用户名包字符a的用户信息 -SELECT - * -FROM - `t_user` -WHERE - `user_name` LIKE '%a%'; - -SELECT - * -FROM - `t_user` -WHERE - `user_name` LIKE '_a%'; - -``` - -### 排序查询 - -```sql -/* -排序 - select 查询列表 - from 表 - order by 排序列表 asc|desc. asc:升序,desc降序 - 可以放单个字段,也可以放多个字段,可以表达式,也可以放函数 - order by 放最后 除limit语句 -*/ -题目: -#1.查询员工的姓名和部门号和年薪,按年薪降序 按姓名升序 -SELECT - last_name, - department_id, - salary*12*(1+IFNULL(commission_pct,0)) 年薪 -FROM - employees -ORDER BY 年薪 DESC, last_name ASC; - -#2.选择工资不在8000到17000的员工的姓名和工资,按工资降序 -SELECT - last_name , - salary -FROM employees -WHERE - salary NOT BETWEEN 8000 AND 17000 -ORDER BY salary DESC ; -#3.查询邮箱中包含e的员工信息,并先按邮箱的字节数降序,再按部门号升序 -SELECT - *,LENGTH(email) -FROM employees -WHERE email LIKE '%e%' -ORDER BY LENGTH(email) DESC,department_id ASC; - - -``` - -### 常见函数 - -```sql -/* - -概念:类似于java的方法,将一组逻辑语句封装在方法体中,对外暴露方法名 -好处:1、隐藏了实现细节 2、提高代码的重用性 -调用:select 函数名(实参列表) 【from 表】; -特点: - ①叫什么(函数名) - ②干什么(函数功能) - -分类: - 1、单行函数 - 如 concat、length、ifnull等 - 2、分组函数 - - 功能:做统计使用,又称为统计函数、聚合函数、组函数 - -常见函数: - 一、单行函数 - 字符函数: - length:获取字节个数(utf-8一个汉字代表3个字节,gbk为2个字节) - concat - substr - instr - trim - upper - lower - lpad - rpad - replace - - 数学函数: - round - ceil - floor - truncate - mod - - 日期函数: - now - curdate - curtime - year - month - monthname - day - hour - minute - second - str_to_date - date_format - 其他函数: - version - database - user - 控制函数 - if - case -*/ -# 1. 字符函数 -SELECT LENGTH('join'); -SELECT LENGTH('张三丰hahaha'); -SHOW VARIABLES LIKE '%char%'; -# 2. 拼接字符串 -SELECT CONCAT(`user_name`, '_', `password`) infos FROM t_user; -#3.upper、lower -SELECT UPPER('john'); -SELECT LOWER('joHn'); -#示例:将姓变大写,名变小写,然后拼接 -SELECT CONCAT(UPPER(`user_name`), LOWER(`user_name`)) infos FROM t_user; -#4.substr、substring -#注意:索引从1开始 -#截取从指定索引处后面所有字符 -SELECT SUBSTR('我是dreamcat',7) out_put; -#截取从指定索引处指定字符长度的字符 -SELECT SUBSTR('我是dreamcat',1,3) out_put; - -SELECT -CONCAT(UPPER(SUBSTR(`user_name`, 1, 1)), '_', LOWER(SUBSTR(`user_name`, 2))) out_put -FROM t_user; -#5.instr 返回子串第一次出现的索引,如果找不到返回0 -SELECT INSTR('我是dreamcat', 'dream') out_put; -#6.trim -SELECT -LENGTH( - TRIM( - ' dreamcat. ' - ) -) out_put; -SELECT TRIM('aa' FROM 'aaaaaaaaa张aaaaaaaaaaaa翠山aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') AS out_put; -#7.lpad 用指定的字符实现左填充指定长度 -SELECT LPAD('殷素素',5,'*') AS out_put; -#8.rpad 用指定的字符实现右填充指定长度 -SELECT RPAD('殷素素',12,'ab') AS out_put; -#9.replace 替换 -SELECT REPLACE('周芷若周芷若周芷若周芷若张无忌爱上了周芷若','周芷若','赵敏') AS out_put; - - -#二、数学函数 -#round 四舍五入 -SELECT ROUND(-1.55); -SELECT ROUND(1.567,2); -#ceil 向上取整,返回>=该参数的最小整数 -SELECT CEIL(-1.02); -SELECT CEIL(1.02); -#floor 向下取整,返回<=该参数的最大整数 -SELECT FLOOR(-9.99); -SELECT FLOOR(9.99); -#truncate 截断 -SELECT TRUNCATE(1.69999,1); -#mod取余 -SELECT MOD(10,-3); -SELECT 10%3; - - -#三、日期函数 -#now 返回当前系统日期+时间 -SELECT NOW(); -#curdate 返回当前系统日期,不包含时间 -SELECT CURDATE(); -#curtime 返回当前时间,不包含日期 -SELECT CURTIME(); -#可以获取指定的部分,年、月、日、小时、分钟、秒 -SELECT YEAR(NOW()) 年; -SELECT YEAR('1998-1-1') 年; -SELECT MONTH(NOW()) 月; -SELECT MONTHNAME(NOW()) 月; -#str_to_date 将字符通过指定的格式转换成日期 -SELECT STR_TO_DATE('1998-3-2','%Y-%c-%d') AS out_put; -#date_format 将日期转换成字符 -SELECT DATE_FORMAT(NOW(),'%y年%m月%d日') AS out_put; - -#四、其他函数 - -SELECT VERSION(); -SELECT DATABASE(); -SELECT USER(); - -#五、流程控制函数 -#1.if函数: if else 的效果 - -SELECT IF(10<5,'大','小'); - -SELECT - *, -IF(`age` < 24, 'young', 'old') 'new age' -FROM - t_user; - -#2.case函数的使用一: switch case 的效果 -/* -java中 -switch(变量或表达式){ - case 常量1:语句1;break; - ... - default:语句n;break; - - -} - -mysql中 - -case 要判断的字段或表达式 -when 常量1 then 要显示的值1或语句1; -when 常量2 then 要显示的值2或语句2; -... -else 要显示的值n或语句n; -end -*/ - -SELECT - *, -CASE `age` -WHEN 21 THEN 21+1 -WHEN 22 THEN 22+2 -WHEN 23 THEN 23+3 -ELSE age -END 'NEW age' -FROM - t_user; -题目: -#1. 显示系统时间(注:日期+时间) -SELECT NOW(); - -#2. 查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary) -SELECT - employee_id , - last_name , - salary , - salary * 1.2 'new salary' -FROM employees; - -#3. 将员工的姓名按首字母排序,并写出姓名的长度(length) -SELECT - LENGTH (last_name) 长度, SUBSTR(last_name,1,1) 首字母, last_name -FROM employees -ORDER BY 首字母; - -#4. 做一个查询,产生下面的结果 -# earns monthly but wants -#Dream Salary -#King earns 24000 monthly but wants 72000 -SELECT - CONCAT(last_name, ' earns ', salary, ' monthly but wants ', salary * 3) AS 'Dream Salary' -FROM employees -WHERE salary = 24000; - -``` - -### 分组函数 - -```sql -#二、分组函数 -/* -功能:用作统计使用,又称为聚合函数或统计函数或组函数 - -分类: -sum 求和、avg 平均值、max 最大值 、min 最小值 、count 计算个数 - -特点: -1、sum、avg一般用于处理数值型 - max、min、count可以处理任何类型 -2、以上分组函数都忽略null值 - -3、可以和distinct搭配实现去重的运算 - -4、count函数的单独介绍 -一般使用count(*)用作统计行数 - -5、和分组函数一同查询的字段要求是group by后的字段 - -*/ -#1、简单 的使用 -SELECT SUM(age) FROM t_user; -SELECT AVG(age) FROM t_user; -SELECT MIN(age) FROM t_user; -SELECT MAX(age) FROM t_user; -SELECT COUNT(age) FROM t_user; -SELECT SUM(user_name) FROM t_user; -SELECT AVG(user_name) FROM t_user; -SELECT COUNT(user_name) FROM t_user; - -SELECT SUM(DISTINCT password ) FROM t_user; -SELECT COUNT(DISTINCT password) FROM t_user; - -SELECT COUNT(*) FROM t_user; -SELECT COUNT(1) FROM t_user; -``` - -### 分组查询 - -```sql - -#进阶5:分组查询 - /* -语法: - -select 查询列表 -from 表 -【where 筛选条件】 -group by 分组的字段 -【order by 排序的字段】; - -特点: -1、和分组函数一同查询的字段必须是group by后出现的字段 -2、筛选分为两类:分组前筛选和分组后筛选 - 针对的表 位置 连接的关键字 -分组前筛选 原始表 group by前 where - -分组后筛选 group by后的结果集 group by后 having - -问题1:分组函数做筛选能不能放在where后面 -答:不能 - -问题2:where——group by——having - -一般来讲,能用分组前筛选的,尽量使用分组前筛选,提高效率 - -3、分组可以按单个字段也可以按多个字段 -4、可以搭配着排序使用 -*/ -SELECT - COUNT(*) -FROM - t_user -WHERE - password = '123'; -SELECT - AVG(age), - password -FROM - t_user -GROUP BY password; - -SELECT - AVG(age), - gender -FROM - t_user -GROUP BY gender ; -题目: -#1.查询各job_id的员工工资的最大值,最小值,平均值,总和,并按job_id升序 -SELECT - MAX(salary), - MIN(salary), - AVG(salary), - SUM(salary) -FROM employees -GROUP BY job_id -ORDER BY job_id ; - -#2.查询员工最高工资和最低工资的差距(DIFFERENCE) -SELECT - MAX(salary) - MIN(salary) AS DIFFERENCE -FROM employees; -#3.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内 - -SELECT - MIN(salary), manager_id -FROM employees -WHERE manager_id IS NOT NULL -GROUP BY manager_id -HAVING MIN(salary) >= 6000; - -#4.查询所有部门的编号,员工数量和工资平均值,并按平均工资降序 - - -SELECT - department_id , - COUNT(*), - AVG(salary) -FROM - employees -GROUP BY department_id -ORDER BY AVG(salary) DESC; - -#5.选择具有各个job_id的员工人数 - -SELECT - COUNT(*), - job_id -FROM - employees -GROUP BY job_id; -``` - -### 连接查询 - -```sql - -#进阶6:连接查询 - /* -含义:又称多表查询,当查询的字段来自于多个表时,就会用到连接查询 - -笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行 - -发生原因:没有有效的连接条件 -如何避免:添加有效的连接条件 - -分类: - - 按年代分类: - sql92标准:仅仅支持内连接 - sql99标准【推荐】:支持内连接+外连接(左外和右外)+交叉连接 - - 按功能分类: - 内连接: - 等值连接 - 非等值连接 - 自连接 - 外连接: - 左外连接 - 右外连接 - 全外连接 - - 交叉连接 -*/ -#1.显示所有员工的姓名,部门号和部门名称。 - SELECT - e.last_name , - d.department_id , - d.department_name -FROM - employees e , - departments d -WHERE - e.department_id = d.department_id ; -#2.查询90号部门员工的job_id和90号部门的location_id - SELECT - e.job_id , - d.location_id -FROM - employees e , - departments d -WHERE - e.department_id = d.department_id - AND e.department_id = 90; -#3. 选择所有有奖金的员工的 last_name , department_name , location_id , city - SELECT - last_name , - department_name , - l.location_id , - city -FROM - employees e, - departments d, - locations l -WHERE - e.department_id = d.department_id - AND d.location_id = l.location_id - AND e.commission_pct IS NOT NULL; -#4.选择city在Toronto工作的员工的 last_name , job_id , department_id , department_name - SELECT - e.last_name , - e.job_id , - e.department_id , - d.department_name -FROM - employees e, - departments d, - locations l -WHERE - e.department_id = d.department_id - AND d.location_id = l.location_id - AND l.city = 'Toronto'; -#5.查询每个工种、每个部门的部门名、工种名和最低工资 - SELECT - d.department_name , - j.job_title , - MIN(e.salary) -FROM - employees e , - departments d , - jobs j -WHERE - e.department_id = d.department_id - AND e.job_id = j.job_id -GROUP BY - d.department_name , - j.job_title ; -#6.查询每个国家下的部门个数大于2的国家编号 - SELECT - l.country_id , - COUNT(*) -FROM - locations l , - departments d -WHERE - d.location_id = l.location_id -GROUP BY - country_id -HAVING - COUNT(*) > 2; - -#7 、选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式 -SELECT - e.last_name employees, - e.employee_id "Emp#", - m.last_name manager, - m.employee_id "Mgr#" -FROM - employees e, - employees m -WHERE - e.manager_id = m.employee_id - AND e.last_name = 'kochhar'; - -#二、sql99语法 -/* -语法: - select 查询列表 - from 表1 别名 【连接类型】 - join 表2 别名 - on 连接条件 - 【where 筛选条件】 - 【group by 分组】 - 【having 筛选条件】 - 【order by 排序列表】 - - -分类: -内连接(★):inner -外连接 - 左外(★):left 【outer】 - 右外(★):right 【outer】 - 全外:full【outer】 -交叉连接:cross - -*/ -#一)内连接 -/* -语法: - -select 查询列表 -from 表1 别名 -inner join 表2 别名 -on 连接条件; - -分类: -等值 -非等值 -自连接 - -特点: -①添加排序、分组、筛选 -②inner可以省略 -③ 筛选条件放在where后面,连接条件放在on后面,提高分离性,便于阅读 -④inner join连接和sql92语法中的等值连接效果是一样的,都是查询多表的交集 -*/ -#二、外连接 - - /* - 应用场景:用于查询一个表中有,另一个表没有的记录 - - 特点: - 1、外连接的查询结果为主表中的所有记录 - 如果从表中有和它匹配的,则显示匹配的值 - 如果从表中没有和它匹配的,则显示null - 外连接查询结果=内连接结果+主表中有而从表没有的记录 - 2、左外连接,left join左边的是主表 - 右外连接,right join右边的是主表 - 3、左外和右外交换两个表的顺序,可以实现同样的效果 - 4、全外连接=内连接的结果+表1中有但表2没有的+表2中有但表1没有的 - */ -``` - -### 子查询 - -```sql -#进阶7:子查询 -/* -含义: -出现在其他语句中的select语句,称为子查询或内查询 -外部的查询语句,称为主查询或外查询 - -分类: -按子查询出现的位置: - select后面: - 仅仅支持标量子查询 - - from后面: - 支持表子查询 - where或having后面:★ - 标量子查询(单行) √ - 列子查询 (多行) √ - - 行子查询 - - exists后面(相关子查询) - 表子查询 -按结果集的行列数不同: - 标量子查询(结果集只有一行一列) - 列子查询(结果集只有一列多行) - 行子查询(结果集有一行多列) - 表子查询(结果集一般为多行多列) -*/ -#一、where或having后面 -/* -1、标量子查询(单行子查询) -2、列子查询(多行子查询) - -3、行子查询(多列多行) - -特点: -①子查询放在小括号内 -②子查询一般放在条件的右侧 -③标量子查询,一般搭配着单行操作符使用 -> < >= <= = <> - -列子查询,一般搭配着多行操作符使用 -in、any/some、all - -④子查询的执行优先于主查询执行,主查询的条件用到了子查询的结果 - -*/ -``` - -### 分页查询 - -```sql -#进阶8:分页查询 ★ -/* - -应用场景:当要显示的数据,一页显示不全,需要分页提交sql请求 -语法: - select 查询列表 - from 表 - 【join type join 表2 - on 连接条件 - where 筛选条件 - group by 分组字段 - having 分组后的筛选 - order by 排序的字段】 - limit 【offset,】size; - - offset要显示条目的起始索引(起始索引从0开始) - size 要显示的条目个数 -特点: - ①limit语句放在查询语句的最后 - ②公式 - 要显示的页数 page,每页的条目数size - - select 查询列表 - from 表 - limit (page-1)*size,size; - - size=10 - page - 1 0 - 2 10 - 3 20 -*/ -``` - - diff --git "a/Java/other/\345\274\200\346\272\220github.md" "b/Java/other/\345\274\200\346\272\220github.md" deleted file mode 100644 index 4449e13b..00000000 --- "a/Java/other/\345\274\200\346\272\220github.md" +++ /dev/null @@ -1,119 +0,0 @@ -> **一些有趣的github项目总结,其中包括终端、Python、Java、笔试&面试、效率软件等。** - - -## Java - -- [Awesome Java]() A curated list of awesome frameworks, libraries and software for the Java programming language. **star:21651** -- [CS-Notes]() 技术面试必备基础知识、Leetcode 题解、Java、C++、Python、后端面试、操作系统、计算机网络、系统设计。**star:67433** -- [spring-boot-examples]() 对于初学者学习Spring-boot,是个非常不错的例子。**star:16408** -- [SpringAll]()循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。**star:6181** -- [interest]() Vue+Spring boot前后端分离博客项目。**star:494** -- [litemall]() 一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。**star:7586** -- [vhr]()微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。**star:4705** -- [mybatis]() MyBatis SQL mapper framework for Java **star:11335** -- [miaosha]() ⭐⭐⭐⭐秒杀系统设计与实现.互联网工程师进阶与分析🙋🐓 **star:9400** -- [spring-boot-plus]()🔥spring-boot-plus集成Spring Boot 2.1.6,Mybatis,Mybatis Plus,Druid,FastJson,Redis,Rabbit MQ,Kafka等,可使用代码生成器快速开发项目. **star:551** -- [hope-boot]() 🌱🚀一款现代化的脚手架项目。🍻整合Springboot2 **star:1543** -- [spring-boot-demo]() spring boot demo 是一个用来学习 spring boot 的项目,总共包含 57 个集成demo,已经完成 47 个。**star:2149** -- [spring-boot-api-project-seed]() 🌱🚀一个基于Spring Boot & MyBatis的种子项目,用于快速构建中小型API、RESTful API项目~ **star:1543** -- [White-Jotter]()白卷是一款使用 Vue+Spring Boot 开发的前后端分离的图书管理项目 **star:115** -- [eladmin]() 项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由 **start:3163** -- [dbblog]() 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 **star:375** -- [spring-analysis]() Spring源码阅读 **star:4045** -- [mall]() mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。**star:21926** -- [后端架构师技术图谱](https://github.com/xingshaocheng/architect-awesome) **star:42900** -- [mall-swarm](https://github.com/macrozheng/mall-swarm)mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。**star:1400** -- [jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)一款基于代码生成器的JAVA快速开发平台,开源界“小普元”超越传统商业企业级开发平台!**star:9600** -- [dbblog](https://github.com/llldddbbb/dbblog) 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 [http://www.dblearn.cn](http://www.dblearn.cn/) **star:643** -- [springboot-seckill](https://github.com/zaiyunduan123/springboot-seckill) 基于SpringBoot + MySQL + Redis + RabbitMQ + Guava开发的高并发商品限时秒杀系统 **star:943** -- [MeetingFilm](https://github.com/daydreamdev/MeetingFilm)基于微服务架构的在线电影购票平台 **star:111** -- [guava](https://github.com/google/guava) Google core libraries for Java **star:36400** -- [hutool](https://github.com/looly/hutool) A set of tools that keep Java sweet. [http://www.hutool.cn](http://www.hutool.cn/) **star:10800** -- [JavaGuide]() 一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159** -- [advanced-java]() 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。**star:22747** -- [technology-talk]() 汇总java生态圈常用技术框架等。**star:6229** -- [interview_internal_reference]() 2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:14335** -- [interviews]() Everything you need to know to get the job. **star:37598** -- [interview_internal_reference]()2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:15570** -- [reverse-interview-zh]() 技术面试最后反问面试官的话 **star:4500** -- [JavaFamily](https://github.com/AobingJava/JavaFamily)【互联网一线大厂面试+学习指南】进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,作者风格幽默,看起来津津有味,把学习当做一种乐趣,何乐而不为,后端同学必看,前端同学我保证你也看得懂,看不懂你加我微信骂我渣男就好了。 **star:7100** -- [Java-Interview](https://github.com/gzc426/Java-Interview) Java 面试必会 直通BAT **star:3500** -- [arthas](https://github.com/alibaba/arthas):Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas **star:24200** -- [Sentinel](https://github.com/alibaba/Sentinel)A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件) **star:14900** - -## 算法 -- [LeetcodeTop](https://github.com/afatcoder/LeetcodeTop)About汇总各大互联网公司容易考察的高频leetcode题🔥 **star:5000** -- [fucking-algorithm](https://github.com/labuladong/fucking-algorithm) 手把手撕LeetCode题目,扒各种算法套路的裤子,not only how,but also why. English version supported! https://labuladong.gitbook.io/algo/ **star:78200** -- [LeetCodeAnimation]() LeetCode用动画的形式的呈现。**star:32650** -- [leetcode]() LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。) -- [algo](https://github.com/wangzheng0822/algo) 数据结构和算法必知必会的50个代码实现 **star:10700** - -## Vue&&前端 - -- [awesome-vue]() awesome things related to Vue.js **star:46634** -- [vue-form-making]()基于Vue的表单设计器,让表单开发简单而高效。 **star:1347** -- [fe-interview]() 前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家)**star:6667** -- [vue2-elm]() 基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用 **star:30300** -- [Web]() 前端入门和进阶学习笔记,超详细的Web前端学习图文教程。从零开始学前端,做一名精致的前端工程师。持续更新… **star:55300** -- [javascript-algorithms]() Algorithms and data structures implemented in JavaScript with explanations and links to further readings **star:55277** -- [nodebestpractices]() ✅ The largest Node.js best practices list (September 2019) **star:35000** -- [gods-pen](https://github.com/ymm-tech/gods-pen) 基于vue的高扩展在线网页制作平台,可自定义组件,可添加脚本,可数据统计。A mobile page builder/editor, similar with amolink. [https://godspen.ymm56.com](https://godspen.ymm56.com/) **star:1200** -- [Daily-Interview-Question](https://github.com/Advanced-Frontend/Daily-Interview-Question) 我是木易杨,公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。**star:17000** - -## Python - -- [awesome-python-login-model]() 模拟登陆一些大型网站的demo,个人觉得不错,值得学习。**star:8403** -- [pyppeteer]() 模拟动态加载js,比selenium稍微好用一些。**star:1924** -- [requests]() Python HTTP Requests for Humans™ ✨🍰✨ **star:39860** -- [requests-html]() Pythonic HTML Parsing for Humans™ **star:10111** -- [httpx]() A next generation HTTP client for Python. 🦋 **star:1900** -- [PySimpleGUI]() 做一些简单的GUI,可以用这个,简单应用。**star:1608** -- [bokeh]() Interactive Web Plotting for Python。 **star:10701** -- [wxpy]() 微信机器人 / 可能是最优雅的微信个人号 API ✨✨ [http://wxpy.readthedocs.io](http://wxpy.readthedocs.io/) **star:10700** - - -## 效率软件 - -- [frp]() 内网穿透,你懂的。**star:24539** -- [musicbox]() 网易云音乐命令行版本。**star:7601** -- [motrix]() 配合百度云有着奇淫技巧。 **star:11098** -- [Electronic WeChat]() 该项目虽然不再维护,但挺好用的(linux)。**star:12622** -- [hexo]() 搭建博客系统框架,挺好用。**star:26868** -- [awesome-mac]()推荐的非常给力且实用。 **star:29814** -- [蓝灯]() 免费的vpn。 **star:9520** -- [LazyDocker]() The lazier way to manage everything docker. **star:10906** -- [postwoman]() 👽 API request builder - Helps you create your requests faster, saving you precious time on your development. **star:2190** -- [x64dbg]()An open-source x64/x32 debugger for windows. **star:34260** -- [google-access-helper]() 谷歌访问助手破解版 **star:3887** -- [v2ray-core]() A platform for building proxies to bypass network restrictions. **star:21444** -- [v2rayN]() vpn **star:1249** -- [Alfred-collection]() A collection of all known Alfred3 workflows **star:411** -- [RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher) editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) **star:1500** -- [lx-music-desktop](https://github.com/lyswhut/lx-music-desktop) 一个基于 electron 的音乐软件 **star:1300** -- [UnblockNeteaseMusic](https://github.com/nondanee/UnblockNeteaseMusic) Revive unavailable songs for Netease Cloud Music **star:7900** -- [CodeVar](https://github.com/xudaolong/CodeVar) 生成可用的代码变量 (CodeVar that return u a better variable from Chinese to English . ) **star:684** -- [Bob](https://github.com/ripperhe/Bob) Bob 是一款 Mac 端翻译软件,支持划词翻译、截图翻译以及手动输入翻译。 **star:3100** - -## 其他 - -- [ChineseBQB](ChineseBQB)🇨🇳Chinese sticker pack,More joy / 中国表情包大集合,更欢乐 **star:2319** -- [free-programming-books-zh_CN]()📚 免费的计算机编程类中文书籍,欢迎投稿 **star:55296** -- [freeCodeCamp]() The [https://www.freeCodeCamp.org](https://www.freecodecamp.org/) open source codebase and curriculum. Learn to code for free together with millions of people. **star:304920** -- [hosts]() 镜像 **star:15582** -- [free-api]() 收集免费的接口服务,做一个api的搬运工 **star:6000** -- [fanhaodaquan](https://github.com/imfht/fanhaodaquan) 番号大全。 **star:1200** -- [BullshitGenerator](https://github.com/menzi11/BullshitGenerator) Needs to generate some texts to test if my GUI rendering codes good or not. so I made this. **star:3700** -- [howto-make-more-money](https://github.com/easychen/howto-make-more-money): 程序员如何优雅的挣零花钱 **star:8200** -- [marktext](https://github.com/marktext/marktext) A simple and elegant markdown editor, available for Linux, macOS and Windows. [https://marktext.app](https://marktext.app/) **star:14800** -- [FreePAC](https://github.com/xiaoming2028/FreePAC) 科学上网/梯子/自由上网/翻墙 SS/SSR/V2Ray/Brook 搭建教程 **star:2146** - -- [The Art of Command Line]() :系统的学习命令行的用法。**star:57509** -- [oh-my-zsh]()这玩意不用我简单介绍了吧?**star:90516** -- [git-tips](https://github.com/521xueweihan/git-tips) Git的奇技淫巧 **star:11400** -- [powerline-fonts]() mac挺好用的一款终端字体。**star:169** -- [powerlevel9k]() oh-my-zsh的一款皮肤。**star:9952** -- [zsh-syntax-highlighting]() 终端输入的命令有语法高亮效果。**star:7326** -- [zsh-autosuggestions]()终端代码补全插件。**star:6662** -- [howto-make-more-money](https://github.com/easychen/howto-make-more-money): 程序员如何优雅的挣零花钱,2.0版,升级为小书了。Most of this not work outside China , so no English translate **star:13800** -- [yapi](https://github.com/YMFE/yapi)YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台 **star:19400** -- [algorithm-visualizer](https://github.com/algorithm-visualizer/algorithm-visualizer):🎆Interactive Online Platform that Visualizes Algorithms from Code **star:33000** \ No newline at end of file diff --git a/README.md b/README.md index acdcf15b..d6d7bc3c 100644 --- a/README.md +++ b/README.md @@ -4,63 +4,28 @@ > - [在线面试阅读](http://dreamcat.ink/java-interview/) > - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) -在线面试阅读: - -java-interview-readme-1-qAdVxj - -java-interview-readme-2-m0q7qX - -## 面经助手 - -> 包含面经、算法题和知识点,持续维护 - -
-Dc-Notes-small-OlULPl -
- -## 秋招经历 - -> 校招Java职位面试经历 - -### 面经汇总 -- [大厂面经](/Java/mianjing/README.md) - -### 知识体系 -- [Java后端知识分类](/Java/classify/README.md) -- [定时推送后端面经题目脚本](https://github.com/DreamCats/dream-script/blob/master/notify_know.py) - -### 刷题系列 -- [推荐:CS-Notes](http://www.cyc2018.xyz/):个人建议,如果时间不充足的情况下,将CS-Notes的Leetcode刷明白,毕竟200道经典的题,也都很有套路,其次剑指offer,刷到5分钟自己能写完为止。 -- [推荐:一写算法套路模版](https://github.com/labuladong/fucking-algorithm):有些套路挺实用的,推荐阅读... -- [手写多线程编程面试题](/Java/alg/多线程编程题.md) -- [按热度总结lc](/Java/alg/按热度总结lc.md) -- [剑指offer](/Java/alg/剑指offer.md) -- [个人刷熟题](/Java/alg/个人刷熟题.md) -- [总结的一套秋招常考的热点题](Java/alg/README.md) -- [定时每日推送leetcode脚本](https://github.com/DreamCats/dream-script/blob/master/notify_lc.py) - -### 笔试题 -- [LC-SQL](/Java/bishi/sql.md):这里说一下, 图解SQL面试题,个人建议全部练习,也不难,但也的确面试高频手写SQL题 -- [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) -- [常用SQL语句](Java/other/常用sql.md) -- [部分笔试题](/Java/bishi/README.md):可以从牛客找笔试题的感觉,也可以练习输入输出,做多了,你就发现基本没有树和链表! - -### Java面试思维导图(包括分布式架构) -- [知识架构思维导图](Java/mind/README.md) - -### 吐血系列 -- [疯狂吐血系列](Java/crazy/README.md) - -### 项目 -- [微服务班车在线预约系统](/Java/bus/README.md) - -### 其他 -- [面试必问设计模式](/Java/mode/README.md) -- [JDK1.8部分源码](Java/jdk/README.md) -- [相应阅读的书籍](Java/other/books.md) - - +### 🔖DreamCats +| 类型 | 名称 | 来源 | +| ----- | ------------------------------------------------------------ | -------------- | +| 小程序 | [在线面试助手-待定](wx.md) | DreamCats | +| 面经 | [大厂面经汇总](Java/mianjing/README.md) | DreamCats | +| 知识体系 | [Java后端知识分类](Java/classify/README.md) | DreamCats | +| | [疯狂吐血系列](Java/crazy/README.md) | DreamCats | +| | [知识架构思维导图](Java/mind/README.md) | DreamCats | +| 刷题 | [CS-Notes](http://www.cyc2018.xyz/) | cyc | +| | [写算法套路模板](https://github.com/labuladong/fucking-algorithm) | labuladong | +| | [按热度总结lc](Java/alg/按热度总结lc.md) | DreamCats | +| | [剑指Offer](Java/alg/剑指offer.md) | DreamCats | +| | [个人秋招刷熟题](Java/alg/个人刷熟题.md) | DreamCats | +| | [秋招常考的热点题](Java/alg/README.md) | DreamCats | +| | [LC-SQL](Java/bishi/sql.md) | DreamCats | +| | [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) | xxx | +| | [牛客走起来](https://www.nowcoder.com/contestRoom) | xxx | +| 项目 | [微服务班车在线预约系统-文档](Java/bus/README.md) | DreamCats | +| | [微服务班车在线预约系统-项目地址](https://github.com/DreamCats/school-bus) | DreamCats | +| 其他 | [JDK1.8部分源码](Java/jdk/README.md) | DreamCats | +| | [相应阅读的书籍](books.md) | DreamCats | ## 文章系列 @@ -75,7 +40,6 @@ | Other | [计算机专业电子书下载](https://tanqingbo.cn/CSBook001/) | IT码农 | | Other | [计算机专业电子书下载-2](https://tanqingbo.cn/CSBook001/) | javaer-roadmap | | Other | [各个技术开发文档](https://www.bookstack.cn/) | 书栈网 | -| Other | [程序员写好技术文章的几点小技巧](https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247503035&idx=1&sn=940e18795275cd0428c150822898f53a&chksm=e92af1b4de5d78a2b381cfc860f0ae000538432bd7a49d59c37bbb769784be6025d157962573#rd) | 阿里技术 | | Other | [TikTok二面: 说下二维码登录的原理?](https://mp.weixin.qq.com/s/HUJxTbMr0mep9uxAOa4F0A) | 小哈学 | | Other | [性能优化:关于缓存的一些思考](https://mp.weixin.qq.com/s/9aUTvdKFbsi_fzuO0BjPvw) | 阿里技术 | diff --git a/Java/other/books.md b/books.md similarity index 100% rename from Java/other/books.md rename to books.md diff --git a/wx.md b/wx.md new file mode 100644 index 00000000..e69de29b From 332aa03dadb38bcdbe2ba4d65c548a7e28ef9774 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Thu, 12 Aug 2021 21:19:47 +0800 Subject: [PATCH 298/366] remove ds --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 3cce28e5c4bb2383f8bfaee47ed0f47f02f2e421..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfrs;6rTrGS`b3et zf)_pc89aFOZu|zGj2}U--hA_?eI|x z^ltg^sPUrt^0kx`aQ6{5q_7Qd(CMtsDb&>N1n=9c`)U}S_4N;28oWGY43CTsj}6D; z@hju;iOI>Ud1KVJ3I`>h|I#JqGC$>3=~l^iirI=)aJ*73q|d{wb42sT$QkMyEj~LA ztshp9Gj<#hcrF;Q?pb9TizSj76^xQ8a*sSF8{~i-kyqp`c}G5wPvi^vMt(paOu}`T zg4?hFi|`QAumTx)0&6n6r~N9gwWp8>TZo-kS Date: Fri, 13 Aug 2021 10:14:31 +0800 Subject: [PATCH 299/366] add mq article --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d6d7bc3c..c072e3d5 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ | 分布式 | [干货 \|携程最终一致和强一致性缓存实践](https://mp.weixin.qq.com/s/E-chAZyHtaZOdA19mW59-Q) | GSF | | 分布式锁 | [基于Redis的分布式锁设计](https://www.cnblogs.com/xiaoxiaotank/p/14982602.html) | xiaoxiaotank | | 分布式 | [分布式系统中一致性哈希算法](https://www.cnblogs.com/jajian/p/10896624.html) | 码辣架构 | +| 消息 | [消息幂等(去重)如何解决?来看看这个方案!](https://mp.weixin.qq.com/s/kLb1pweomL19aGaiHjO3Fg) | JAVA日知录 | ### 🎈数据结构 From 8f86587d63b260366989259653d573e65486cd55 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:16:01 +0800 Subject: [PATCH 300/366] add wx articke --- Java/.DS_Store | Bin 16388 -> 14340 bytes wx.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/Java/.DS_Store b/Java/.DS_Store index 84f1385a337541d9004d0f46881895577272a1b1..1fe0163f8cc3fd6d136f8e46c9f4b35cc9d29e98 100644 GIT binary patch delta 239 zcmZo^U~DN+VPIfjbSh0TWMD7=GC6=4L<{f$i8clX1rW_JQAJx=8Avk&B^Vfr%YuvY za`N-iCl;zsHn3rlW&}#HfTfcdN)ggP5yr`aMsk}SrBAVJR$!@Q+RVI7l8F@GJC`9l~F3>dE{7~o)6HsibBrDJ)ATZzt w60XQ@+bqcPo_R9AjV>q12?v0fVX_U6GzSyJBNmerY^F}`Q`gg4lUqKurA@5{=1C_4lI%>e>s2X6=i=s#@+ zM}}aA7=}WI28QVjiy5{u9Ar4h@R;E%!+%CzMlnVuMr}qNMpH&}Mh8YmMrTG3M$gIp zN~*d@&M6Bn%FD^mO9z_C2*k_`#SBFZ$&e7qPXdaZoFv>ZnNQw_9V#_>zKrQ+E~#Ed zVW0kh403#MG$1yp>Wa{Jt+A))> OB)vBGiTN;rtN;L$PkJQ) diff --git a/wx.md b/wx.md index e69de29b..42b2b471 100644 --- a/wx.md +++ b/wx.md @@ -0,0 +1,110 @@ +--- +title: 互联网面试题助手 +weight: 1 +--- + +# 互联网面试题助手 + +
+Dc-Notes-small-OlULPl +
+ +![](https://cdn.nlark.com/yuque/0/2021/png/1067743/1616314950564-0a745bb8-e526-41ba-a11f-b6a4c9c1241b.png#align=left&display=inline&height=2228&margin=%5Bobject%20Object%5D&originHeight=2228&originWidth=2658&size=0&status=done&style=none&width=2658) + +# 需求 +> 秋招一般从三个方面准备: +> - 知识点:形成个人体系,要根据自己的简历所建立的知识点 +> - 算法:开发岗的话,一般都是leetcode的题,不需要盲目刷完,后面我会讲 +> - 项目:要有针对性的项目,而且要不断的思考项目的亮点、困难点和如何解决的,以及多个方案对比。 + +如上图,从功能分析,大概分为三个子功能,也就对应三个功能的主页面。 +## 面经 +据我个人秋招经历来讲,看面经是又必须要的,有两种好处: + +1. 秋招之前,不知道面试官问什么,也并不知道如何回答,如果提前看了一些面经上的问题,心中大概有一些眉目,可以私下多准备,这时候一方面可以提供自信心;另一方面,不容易紧张。 +1. 查漏补缺,如果有了个人知识体系,看面经可以不断的查看自己哪部分没考虑到,可以及时补缺。 + +接着,对这个功能,我首先是分为前后端,算法岗暂时不涉及。毕竟前后端的面经是不一样的。目前涉及的公司大概有20-30之间吧,后续可以补充,当然,常见的大厂肯定是有的。当然,🐂客网有这些面经呀,但是有时候用手机看也挺方便的,虽然🐂🐂也有小程序,哈哈哈。但总体来讲,还是稍微复杂了一些,我仅仅是想一个简简单单的功能的嘛,简约。 +## 算法 +这一块分为笔试和面试。以我秋招的经历,我个人觉得,笔试常考的类型: + +1. 二分法 +1. 字符串 +1. dfs +1. bfs +1. 贪心 +1. 排序 +1. 哈希 +1. 规律题 +1. 动态规划 +1. 双指针 +1. 数学 +1. 模拟题 +1. 图 + +而,面试一般常考的类型: + +1. 链表 +1. 树(迭代和递归,可都要熟练) +1. 二分法 +1. dfs +1. bfs +1. 动态规划 +1. 背包 +1. 双指针 +1. 哈希 +1. 排序 +1. 数组 +1. 字符串 + +这个功能,有三大好处: + +1. 不同语言是如何解题(js、go和java) +1. 对着标题,尝试一下是否有思路 +1. 不断的手写,要熟能生巧 + +其实,我个人觉得,没有必要将leetcode全部刷完撒,而且面试不会考那些很陌生的题,一般都是常考的题,因为有价值撒。其实根据我的观察,常考的那些题,也就300-400道,而且一般都是考中等题,比较典型的hard题,可以做做,但没必要全做。 +## 知识点 +这一块,我秋招的面试岗位是Java服务端,所以总结了大量的关于Java的知识点,以及形成了一套Java生态的个人体系,当然也是根据我所设计的**班车项目**而来的。前不久,学了一下前端的知识,其实我也是想对比前端的js和java相同点和不同点,学习一下其中的思想。这一块,也是分为前后端,当然,我总结了300套面经的内容,并总结了面试题的频率,所以才有此针对做了一些知识点。前端包括: + +1. js +1. css +1. html +1. 浏览器 +1. vue +1. 计算机网络 + +当然,也得根据自身的简历哈。后端这一块: + +1. Java基础 +1. Java集合 +1. Java多线程 +1. JVM +1. Spring +1. MySQL +1. Redis +1. 计算机网络 +1. 操作系统 +1. 分布式 + +当然,以上关于班车项目,我后续会总结。班车项目,目前存放在了Github上。链接:[https://github.com/DreamCats/Dc-Notes](https://github.com/DreamCats/Dc-Notes) + + + +# 页面 +页面虽然比较简单,但功能实用,将面经、算法和知识点集合在一起方便背诵。 +## 面经页面 +![互联网助手-面经页面-iPhoneX-BQQ9MQ](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E9%A1%B5%E9%9D%A2-iPhone%20X-BQQ9MQ.png) +![互联网助手-面经列表页面-iPhoneX-AkQP0V](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E5%88%97%E8%A1%A8%E9%A1%B5%E9%9D%A2-iPhone%20X-AkQP0V.png) +![互联网助手-面经详情页面-iPhoneX-mTPXPt](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E9%9D%A2%E7%BB%8F%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-mTPXPt.png) +## 算法页面 + + +![互联网助手-算法页面-iPhoneX-tMYxJj](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%AE%97%E6%B3%95%E9%A1%B5%E9%9D%A2-iPhone%20X-tMYxJj.png) +![互联网助手-算法详情页面-iPhoneX-hJwWLu](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%AE%97%E6%B3%95%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-hJwWLu.png) +## 知识页面 +![互联网助手-知识页面-iPhoneX-l1D7Q9](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%9F%A5%E8%AF%86%E9%A1%B5%E9%9D%A2-iPhone%20X-l1D7Q9.png) +![互联网助手-知识详情页面-iPhoneX-SPF7m9](http://imgs.dreamcat.ink/uPic/%E4%BA%92%E8%81%94%E7%BD%91%E5%8A%A9%E6%89%8B-%E7%9F%A5%E8%AF%86%E8%AF%A6%E6%83%85%E9%A1%B5%E9%9D%A2-iPhone%20X-SPF7m9.png) + + + From 7f3d91ae0eb3a954e224ab490360cc57a0ad22dd Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:20:47 +0800 Subject: [PATCH 301/366] fix wx --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c072e3d5..4f70e65c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ | 类型 | 名称 | 来源 | | ----- | ------------------------------------------------------------ | -------------- | -| 小程序 | [在线面试助手-待定](wx.md) | DreamCats | +| 小程序 | [在线面试助手](wx.md) | DreamCats | | 面经 | [大厂面经汇总](Java/mianjing/README.md) | DreamCats | | 知识体系 | [Java后端知识分类](Java/classify/README.md) | DreamCats | | | [疯狂吐血系列](Java/crazy/README.md) | DreamCats | From 940082e9e810ace596eb258f305f3d86ca067a7a Mon Sep 17 00:00:00 2001 From: DreamCats Date: Fri, 13 Aug 2021 10:57:57 +0800 Subject: [PATCH 302/366] remove github source --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4f70e65c..357786f3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ | 类型 | 名称 | 来源 | | ----- | ------------------------------------------------------------ | -------------- | -| Other | [持续更新非常优秀的Github项目](Java/other/开源github.md) | Dreamcats | | Other | [计算机专业电子书下载](https://tanqingbo.cn/CSBook001/) | IT码农 | | Other | [计算机专业电子书下载-2](https://tanqingbo.cn/CSBook001/) | javaer-roadmap | | Other | [各个技术开发文档](https://www.bookstack.cn/) | 书栈网 | From 7f7dbd754fa6472458043ccd8fdb3b1e515825f9 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Sun, 15 Aug 2021 00:23:02 +0800 Subject: [PATCH 303/366] :memo:update blog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d7bc3c..5d257108 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # java-notes ## 引言 -> - [个人博客](http://dreamcat.ink/blog) +> - [个人博客](https://blog.dreamcat.ink/) > - [在线面试阅读](http://dreamcat.ink/java-interview/) > - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) From e383be615fe6508b54b30de7c693672737b11e14 Mon Sep 17 00:00:00 2001 From: DreamCats Date: Mon, 16 Aug 2021 11:26:43 +0800 Subject: [PATCH 304/366] :memo: add Guava Cache --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ffbbf7a3..371cc345 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ | 分布式锁 | [基于Redis的分布式锁设计](https://www.cnblogs.com/xiaoxiaotank/p/14982602.html) | xiaoxiaotank | | 分布式 | [分布式系统中一致性哈希算法](https://www.cnblogs.com/jajian/p/10896624.html) | 码辣架构 | | 消息 | [消息幂等(去重)如何解决?来看看这个方案!](https://mp.weixin.qq.com/s/kLb1pweomL19aGaiHjO3Fg) | JAVA日知录 | +| 本地缓存 | [Guava Cache 原理分析与最佳实践](https://mp.weixin.qq.com/s/ADcu_XKTJxXectMQ8S20SQ) | 梓川 | ### 🎈数据结构 From 91638a533e898af1e0cb3048c25b0279d06ba1c9 Mon Sep 17 00:00:00 2001 From: Dreamcats Date: Tue, 17 Aug 2021 00:26:56 +0800 Subject: [PATCH 305/366] :sparkles:add spring-books --- .DS_Store | Bin 0 -> 6148 bytes Java/.DS_Store | Bin 14340 -> 14340 bytes ...70\350\247\201\351\227\256\351\242\230.md" | 154 +++++++++++ Java/spring-books/README.md | 73 +++++ ...01\347\250\213\345\210\206\346\236\220.md" | 151 +++++++++++ ...20\347\240\201\346\200\273\347\273\223.md" | 253 ++++++++++++++++++ ...50\247\243bean\346\226\271\345\274\217.md" | 17 ++ ...37\345\221\275\345\221\250\346\234\237.md" | 78 ++++++ ...ue\347\232\204\344\275\277\347\224\250.md" | 32 +++ ...ce\347\232\204\344\275\277\347\224\250.md" | 32 +++ ...ed\347\232\204\344\275\277\347\224\250.md" | 71 +++++ ...er\347\232\204\344\275\277\347\224\250.md" | 71 +++++ ...le\347\232\204\344\275\277\347\224\250.md" | 83 ++++++ ...OP\345\260\217\344\276\213\345\255\220.md" | 210 +++++++++++++++ .../17.Spring\344\272\213\345\212\241.md" | 98 +++++++ .../docs/18.BeanFactoryPostProcessor.md | 39 +++ .../19.BeanDefinitionRegistryPostProcessor.md | 32 +++ ...53\346\217\217\346\226\271\345\274\217.md" | 17 ++ .../docs/20.ApplicationListener.md | 25 ++ ...45\206\214bean\346\226\271\345\274\217.md" | 40 +++ ...53\346\217\217\346\226\271\345\274\217.md" | 41 +++ ...er\347\232\204\344\275\277\347\224\250.md" | 34 +++ ...pe\347\232\204\344\275\277\347\224\250.md" | 50 ++++ ...zy\347\232\204\344\275\277\347\224\250.md" | 50 ++++ ...al\347\232\204\344\275\277\347\224\250.md" | 46 ++++ ...rt\347\232\204\344\275\277\347\224\250.md" | 37 +++ Java/spring-books/spring-aop2/src/Advice.java | 7 + .../spring-aop2/src/AfterAdvice.java | 29 ++ .../spring-aop2/src/BeforeAdvice.java | 27 ++ .../spring-aop2/src/HelloService.java | 11 + .../spring-aop2/src/HelloServiceImpl.java | 14 + .../spring-aop2/src/MethodInvocation.java | 11 + .../spring-aop2/src/SimpleAOP.java | 16 ++ .../spring-aop2/src/SimpleAOPTest.java | 29 ++ Java/spring-books/spring-ioc/src/Car.java | 39 +++ .../spring-ioc/src/SimpleIOC.java | 101 +++++++ .../spring-ioc/src/SimpleIOCTest.java | 20 ++ Java/spring-books/spring-ioc/src/Wheel.java | 39 +++ Java/spring-books/spring-ioc/src/ioc.xml | 11 + README.md | 1 + 40 files changed, 2089 insertions(+) create mode 100644 .DS_Store create mode 100644 "Java/spring-books/MyBatis\346\241\206\346\236\266\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" create mode 100644 Java/spring-books/README.md create mode 100644 "Java/spring-books/SpringBoot\345\220\257\345\212\250\346\265\201\347\250\213\345\210\206\346\236\220.md" create mode 100644 "Java/spring-books/Spring\345\222\214SpringAOP\346\272\220\347\240\201\346\200\273\347\273\223.md" create mode 100644 "Java/spring-books/docs/1.xml\346\263\250\350\247\243bean\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/10.bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" create mode 100644 "Java/spring-books/docs/11.@Value\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/12.@PropertySource\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/13.@Autowired\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/14.@Qualifier\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/15.@Profile\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/16.AOP\345\260\217\344\276\213\345\255\220.md" create mode 100644 "Java/spring-books/docs/17.Spring\344\272\213\345\212\241.md" create mode 100644 Java/spring-books/docs/18.BeanFactoryPostProcessor.md create mode 100644 Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md create mode 100644 "Java/spring-books/docs/2.xml\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" create mode 100644 Java/spring-books/docs/20.ApplicationListener.md create mode 100644 "Java/spring-books/docs/3.\346\263\250\350\247\243\346\263\250\345\206\214bean\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/4.\346\263\250\350\247\243\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" create mode 100644 "Java/spring-books/docs/5.@Filter\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/6.@Scope\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/7.@Lazy\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/8.@Conditional\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/spring-books/docs/9.@Import\347\232\204\344\275\277\347\224\250.md" create mode 100644 Java/spring-books/spring-aop2/src/Advice.java create mode 100644 Java/spring-books/spring-aop2/src/AfterAdvice.java create mode 100644 Java/spring-books/spring-aop2/src/BeforeAdvice.java create mode 100644 Java/spring-books/spring-aop2/src/HelloService.java create mode 100644 Java/spring-books/spring-aop2/src/HelloServiceImpl.java create mode 100644 Java/spring-books/spring-aop2/src/MethodInvocation.java create mode 100644 Java/spring-books/spring-aop2/src/SimpleAOP.java create mode 100644 Java/spring-books/spring-aop2/src/SimpleAOPTest.java create mode 100644 Java/spring-books/spring-ioc/src/Car.java create mode 100644 Java/spring-books/spring-ioc/src/SimpleIOC.java create mode 100644 Java/spring-books/spring-ioc/src/SimpleIOCTest.java create mode 100644 Java/spring-books/spring-ioc/src/Wheel.java create mode 100644 Java/spring-books/spring-ioc/src/ioc.xml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5083484dbd636a69dfba1bc52992b9ba20df2573 GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~CWx4WV2^w8)XH<+2a)3({&Y0( z!`O%Z+fp<+{vreT?F@FtJT_t8=kMna!eEqCD(}2dG}hKPOv^H@E$i0p$5S`)lJThN z4X?3x?j?O++ZS;h&TY@X40_YHy>lE#i5En@p-c|CJqWqE3Zia2ZN}rMJCgIbYCta3 zwo7SRZ8XZFc38{GB5mwf%A$Ty&oay0-K!p*cJ3b@)2HnDMX}1^2b8j{aR#qo%r`i3 zhfx?uci@}F&*BJ)0b+m{SRn@V5om6%(4J|l!~ikyGY0T{5TJ;T#$2I1I-tSpBaR!0 zC}87T0#RsmH0BCn1ca+pK$Xh%6N9UC@CzO1Xv`I=bjIb%FpplD>lX@_tAk(2aK;^l z)Di>4z#;=h)wQwypM3xRUreGNF+dFbD+ajHa$8Na)4+vcZBn{LM1Ha0^2T>YOVE_OC literal 0 HcmV?d00001 diff --git a/Java/.DS_Store b/Java/.DS_Store index 1fe0163f8cc3fd6d136f8e46c9f4b35cc9d29e98..e393f85cd2a97c048d46ddf77ddfa9a98eb82047 100644 GIT binary patch delta 111 zcmZoEXepTBC7U^hRb*k&GqG=5bchGK>Sh9ZVchCGIJ23>|EhI}B*W+*NTF3QWv b&r4@uU|^hhv1Bv1LORi^3pTSG{AC9KXb&CF delta 43 ucmZoEXepTBgHU^hRb=w=>)H2%r6 项目中,经常用到MyBatis,面试也需要掌握滴! + + +## 基本概念 + +### 数据持久化 + +**数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。例如,文件的存储、数据的读取等都是数据持久化操作。数据模型可以是任何数据结构或对象的模型、XML、二进制流等。** + +**当我们编写应用程序操作数据库,对表数据进行增删改查的操作的时候就是数据持久化的操作。** + +### Mybatis框架简介 + +- **MyBatis框架是一个开源的数据持久层框架。** +- **它的内部封装了通过JDBC访问数据库的操作,支持普通的SQL查询、存储过程和高级映射,几乎消除了所有的JDBC代码和参数的手工设置以及结果集的检索。** +- **MyBatis作为持久层框架,其主要思想是将程序中的大量SQL语句剥离出来,配置在配置文件当中,实现SQL的灵活配置。** +- **这样做的好处是将SQL与程序代码分离,可以在不修改代码的情况下,直接在配置文件当中修改SQL。** + +### ORM + +**ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。** + +**MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。** + +## MyBatis框架的优缺点及其适用的场合 + +### 优点 + +1. 与JDBC相比,减少了50%以上的代码量。 +2. MyBatis是嘴加单的持久层框架,小巧并且简单易学。 +3. MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML文件里,从程序代码中彻底分离,降低耦合度,便于统一的管理和优化,并可重用。 +4. 提供XML标签,支持编写动态的SQL,满足不同的业务需求。 +5. 提供映射标签,支持对象与数据库的ORM字段关系映射。 + +### 缺点 + +1. SQL语句的编写工作量较大,对开发人员编写SQL的能力有一定的要求。 +2. SQL语句依赖于数据库,导致数据库不具有好的移植性,不可以随便更换数据库。 + +### 适用场合 + +**MyBatis专注于SQL自身,是一个足够灵活的DAO层解决方案。对性能的要求很高,或者需求变化较多的项目,例如Web项目,那么MyBatis是不二的选择。** + +## MyBatis与Hibernate有哪些不同? + +1. Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。 +2. Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 +3. Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。 + +## **#{}和${}的区别是什么?** + +1. \#{} 是预编译处理,${}是字符串替换。 +2. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值; +3. Mybatis在处理\${}时,就是把\${}替换成变量的值。 + +4. 使用#{}可以有效的防止SQL注入,提高系统安全性。 + +## 当实体类中的属性名和表中的字段名不一样 ,怎么办 ? + +1. 第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。 +2. 第2种: 通过 `` 来映射字段名和实体类属性名的一一对应的关系。 + +## 模糊查询like语句该怎么写? + +1. 第1种:在Java代码中添加sql通配符。 +2. 第2种:在sql语句中拼接通配符,会引起sql注入 + +## 通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗? + +**Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。** +**Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中每`