From 41578dbafd697887531f00cd1d49a432dcda6ccb Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 12 Oct 2020 16:51:20 +0800 Subject: [PATCH 01/22] update --- Rocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index 2c10a9c..30fa36d 100644 --- a/Rocket.md +++ b/Rocket.md @@ -753,7 +753,7 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 5. 异常被 catch 捕获导致@Transactional失效。 6. 数据库引擎不支持事务。 -### Spring的的事务传播机制 +### Spring中的事务传播机制 1. REQUIRED(默认,常用):支持使用当前事务,如果当前事务不存在,创建一个新事务。eg:方法B用REQUIRED修饰,方法A调用方法B,如果方法A当前没有事务,方法B就新建一个事务(若还有C则B和C在各自的事务中独立执行),如果方法A有事务,方法B就加入到这个事务中,当成一个事务。 2. SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。 From 1ae42b25c163070d6686c6c0ca2fd0a6036b8e2f Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 12 Oct 2020 17:01:53 +0800 Subject: [PATCH 02/22] update q206 --- .../f1/Solution.java" | 25 ++++++------------- .../f2/Solution.java" | 17 ++++++------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f1/Solution.java" "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f1/Solution.java" index 8e21915..bcd4a1c 100644 --- "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f1/Solution.java" +++ "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f1/Solution.java" @@ -1,28 +1,19 @@ package 链表操作.q206_反转链表.f1; -import java.util.ArrayList; -import java.util.List; - /** - * 暴力法舍弃空间 o(n) + * 遍历直接反向修改next指针 o(n) */ class Solution { + public ListNode reverseList(ListNode head) { - if (head == null || head.next == null) { - return head; - } - List list = new ArrayList<>(); + ListNode pre = null; ListNode temp = head; while (temp != null) { - list.add(temp.val); - temp = temp.next; - } - ListNode rs = new ListNode(list.get(list.size() - 1)); - ListNode t1 = rs; - for (int i = list.size() - 2; i >= 0; i--) { - t1.next = new ListNode(list.get(i)); - t1 = t1.next; + ListNode t = temp.next; + temp.next = pre; + pre = temp; + temp = t; } - return rs; + return pre; } } diff --git "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f2/Solution.java" "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f2/Solution.java" index d65acaf..479ed78 100644 --- "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f2/Solution.java" +++ "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q206_\345\217\215\350\275\254\351\223\276\350\241\250/f2/Solution.java" @@ -1,18 +1,17 @@ package 链表操作.q206_反转链表.f2; /** - * 遍历直接反向修改next指针 o(n) + * 递归法 o(n) */ class Solution { + public ListNode reverseList(ListNode head) { - ListNode pre = null; - ListNode temp = head; - while (temp != null) { - ListNode t = temp.next; - temp.next = pre; - pre = temp; - temp = t; + if (head == null || head.next == null) { + return head; } - return pre; + ListNode p = reverseList(head.next); + head.next.next = head; + head.next = null; + return p; } } From ae30f6fa6cd7657123d9bf159cae6f6859ae8fc4 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 12 Oct 2020 17:19:47 +0800 Subject: [PATCH 03/22] add q1325 --- README.md | 1 + README_EN.md | 1 + .../Solution.java" | 21 +++++++++++++++++++ .../TreeNode.java" | 20 ++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 "src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/Solution.java" create mode 100644 "src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/TreeNode.java" diff --git a/README.md b/README.md index ad5e43f..247a5cf 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ - [q104_二叉树的最大深度](/src/递归/q104_二叉树的最大深度) - [q226_翻转二叉树](/src/递归/q226_翻转二叉树) - [q236_二叉树的最近公共祖先](/src/递归/q236_二叉树的最近公共祖先) +- [q1325_删除给定值的叶子节点](/src/递归/q1325_删除给定值的叶子节点) ### 分治法/二分法 diff --git a/README_EN.md b/README_EN.md index 99586a0..21acd77 100644 --- a/README_EN.md +++ b/README_EN.md @@ -84,6 +84,7 @@ - [Question 104 : Maximum Depth of Binary Tree](/src/递归/q104_二叉树的最大深度) - [Question 226 : Invert Binary Tree](/src/递归/q226_翻转二叉树) - [Question 236 : Lowest Common Ancestor of a Binary Tree](/src/递归/q236_二叉树的最近公共祖先) +- [Question 1325 : Delete Leaves With a Given Value](/src/递归/q1325_删除给定值的叶子节点) ### Divide and Conquer / Dichotomy diff --git "a/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/Solution.java" "b/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/Solution.java" new file mode 100644 index 0000000..88e89d9 --- /dev/null +++ "b/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/Solution.java" @@ -0,0 +1,21 @@ +package 递归.q1325_删除给定值的叶子节点; + +/** + * 递归 o(n) + */ +public class Solution { + + public TreeNode removeLeafNodes(TreeNode root, int target) { + if (root == null) { + return null; + } + + root.left = removeLeafNodes(root.left, target); + root.right = removeLeafNodes(root.right, target); + + if (root.val == target && root.left == null && root.right == null) { + return null; + } + return root; + } +} diff --git "a/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/TreeNode.java" "b/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/TreeNode.java" new file mode 100644 index 0000000..3101161 --- /dev/null +++ "b/src/\351\200\222\345\275\222/q1325_\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271/TreeNode.java" @@ -0,0 +1,20 @@ +package 递归.q1325_删除给定值的叶子节点; + +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode() { + } + + TreeNode(int val) { + this.val = val; + } + + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } +} From b5021188338c7a75b6ff6989fb4651278854d368 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 12 Oct 2020 18:03:15 +0800 Subject: [PATCH 04/22] update --- Rocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index 30fa36d..e0c3bbc 100644 --- a/Rocket.md +++ b/Rocket.md @@ -382,7 +382,7 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 1. 标记阶段:首先是初始标记(Initial-Mark),这个阶段也是停顿的(stop-the-word),并且会稍带触发一次yong GC。 2. 并发标记:这个过程在整个堆中进行,并且和应用程序并发运行。并发标记过程可能被yong GC中断。在并发标记阶段,如果发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,每个区域的对象活性(区域中存活对象的比例)被计算。 3. 再标记:这个阶段是用来补充收集并发标记阶段产新的新垃圾。与之不同的是,G1中采用了更快的算法:SATB。 -4. 清理阶段:选择活性低的区域(同时考虑停顿时间),等待下次yong GC一起收集,对应GC log: [GC pause (mixed)],这个过程也会有停顿(STW)。 +4. 清理阶段:选择活性低的区域(同时考虑停顿时间),等待下次yong GC一起收集,这个过程也会有停顿(STW)。 5. 回收/完成:新的yong GC清理被计算好的区域。但是有一些区域还是可能存在垃圾对象,可能是这些区域中对象活性较高,回收不划算,也肯能是为了迎合用户设置的时间,不得不舍弃一些区域的收集。 ### G1和CMS的比较 From 8cc62eb4410ebd14bc98baf850ae464be25ca712 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 13 Oct 2020 02:28:35 +0800 Subject: [PATCH 05/22] update --- Rocket.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Rocket.md b/Rocket.md index e0c3bbc..288ae45 100644 --- a/Rocket.md +++ b/Rocket.md @@ -149,14 +149,32 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 3. 用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 4. value的大小:redis可以达到1GB,而memcache只有1MB。 -### Redis的哨兵机制 +### Redis的几种集群模式 -哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于Master是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。 +1. 主从复制 +2. 哨兵模式 +3. cluster模式 + +### Redis的哨兵模式 + +哨兵是一个分布式系统,在主从复制的基础上你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于Master是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。 每个哨兵会向其它哨兵、master、slave定时发送消息,以确认对方是否活着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机”)。 若“哨兵群“中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。 +### Redis的rehash + +Redis的rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的,redis会维护维持一个索引计数器变量rehashidx来表示rehash的进度。 + +这种渐进式的 rehash 避免了集中式rehash带来的庞大计算量和内存操作,但是需要注意的是redis在进行rehash的时候,正常的访问请求可能需要做多要访问两次hashtable(ht[0], ht[1]),例如键值被rehash到新ht1,则需要先访问ht0,如果ht0中找不到,则去ht1中找。 + +### Redis的hash表被扩展的条件 + +1. 哈希表中保存的key数量超过了哈希表的大小. +2. Redis服务器目前没有在执行BGSAVE命令(rdb)或BGREWRITEAOF命令,并且哈希表的负载因子大于等于1. +3. Redis服务器目前在执行BGSAVE命令(rdb)或BGREWRITEAOF命令,并且哈希表的负载因子大于等于5.(负载因子=哈希表已保存节点数量 / 哈希表大小,当哈希表的负载因子小于0.1时,对哈希表执行收缩操作。) + ### Redis并发竞争key的解决方案 1. 分布式锁+时间戳 From 4dba21a9b09fd14234b691bc6661f66887c00b33 Mon Sep 17 00:00:00 2001 From: Guangxin Yuan <274841922@qq.com> Date: Tue, 23 Mar 2021 14:08:34 +0800 Subject: [PATCH 06/22] Update Rocket.md update --- Rocket.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Rocket.md b/Rocket.md index 288ae45..14a8f93 100644 --- a/Rocket.md +++ b/Rocket.md @@ -294,11 +294,19 @@ MySQL为了保证ACID中的一致性和持久性,使用了WAL(Write-Ahead Logg 1. 模糊查询 %like 2. 索引列参与计算,使用了函数 3. 非最左前缀顺序 -4. where对null判断 +4. where单列索引对null判断 5. where不等于 6. or操作有至少一个字段没有索引 7. 需要回表的查询结果集过大(超过配置的范围) +### 为什么Mysql数据库存储不建议使用NULL + +1. NOT IN子查询在有NULL值的情况下返回永远为空结果,查询容易出错。 +2. 索引问题,单列索引无法存储NULL值,where对null判断会不走索引。 +3. 如果在两个字段进行拼接(CONCAT函数),首先要各字段进行非null判断,否则只要任意一个字段为空都会造成拼接的结果为null +4. 如果有 Null column 存在的情况下,count(Null column)需要格外注意,null 值不会参与统计。 +5. Null列需要更多的存储空间:需要一个额外的字节作为判断是否为NULL的标志位 + ### explain命令概要 1. id:select选择标识符 @@ -631,6 +639,15 @@ CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。 如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。 +### Synchronized的四种使用方式 + +1. synchronized(this):当a线程执行到该语句时,锁住该语句所在对象object,其它线程无法访问object中的所有synchronized代码块。 +2. synchronized(obj):锁住对象obj,其它线程对obj中的所有synchronized代码块的访问被阻塞。 +3. synchronized method():与(1)类似,区别是(1)中线程执行到某方法中的该语句才会获得锁,而对方法加锁则是当方法调用时立刻获得锁。 +4. synchronized static method():当线程执行到该语句时,获得锁,所有调用该方法的其它线程阻塞,但是这个类中的其它非static声明的方法可以访问,即使这些方法是用synchronized声明的,但是static声明的方法会被阻塞;注意,这个锁与对象无关。 + +前三种方式加的是对象锁,但是如果(2)中obj是一个class类型的对象,那么加的是类锁,并且锁的范围比(4)还要大;如果该class类型的某个实例对象获得了类锁,那么该class类型的所有实例对象的synchronized代码块均无法访问。 + ### Synchronized和Lock的区别 1. 首先synchronized是java内置关键字在jvm层面,Lock是个java类。 @@ -691,6 +708,16 @@ AQS有两个队列,同步对列和条件队列。同步队列依赖一个双 3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 +### 线程池的正确创建方式 + +不能用Executors,newFixed和newSingle,因为队列无限大,容易造成耗尽资源和OOM,newCached和newScheduled最大线程数是Integer.MAX_VALUE,线程创建过多和OOM。应该通过ThreadPoolExecutor手动创建。 + +### 线程提交submit()和execute()有什么区别 + +1. submit()相比于excute(),支持callable接口,也可以获取到任务抛出来的异常 +2. 可以获取到任务返回结果 +3. 用submit()方法执行任务,用Future.get()获取异常 + ### 线程池的线程数量怎么确定 1. 一般来说,如果是CPU密集型应用,则线程池大小设置为N+1。 @@ -1108,4 +1135,4 @@ select quantity from products WHERE id=3 for update; ``` quantity = select quantity from products WHERE id=3; update products set quantity = ($quantity-1) WHERE id=3 and queantity = $quantity; -``` \ No newline at end of file +``` From d44d8f81d4e3321b703964dab274efab9e40ca8a Mon Sep 17 00:00:00 2001 From: Guangxin Yuan <274841922@qq.com> Date: Tue, 23 Mar 2021 14:13:29 +0800 Subject: [PATCH 07/22] Update Rocket.md update --- Rocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index 14a8f93..f68d3c0 100644 --- a/Rocket.md +++ b/Rocket.md @@ -775,7 +775,7 @@ HashSet的value存的是一个static finial PRESENT = newObject()。而HashSet ### Spring如何解决循环依赖问题 -Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。 +Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。(非单例的实例作用域是不允许出现循环依赖) ### BeanFactory和ApplicationContext的区别 From fb85dd57c718362cba995b974792f14c1ba44881 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 3 Jan 2022 00:25:11 +0800 Subject: [PATCH 08/22] update q209 --- .../Solution.java" | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git "a/src/\345\217\214\346\214\207\351\222\210\351\201\215\345\216\206/q209_\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204/Solution.java" "b/src/\345\217\214\346\214\207\351\222\210\351\201\215\345\216\206/q209_\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204/Solution.java" index 9f8a4bf..dad0410 100644 --- "a/src/\345\217\214\346\214\207\351\222\210\351\201\215\345\216\206/q209_\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204/Solution.java" +++ "b/src/\345\217\214\346\214\207\351\222\210\351\201\215\345\216\206/q209_\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204/Solution.java" @@ -14,16 +14,7 @@ public int minSubArrayLen(int s, int[] nums) { if (k == nums.length && i == nums.length) { break; } - if (sum == s) { - min = Math.min(k - i, min); - if (k < nums.length) { - sum += nums[k]; - k++; - } else { - sum -= nums[i]; - i++; - } - } else if (sum < s) { + if (sum < s) { if (k == nums.length) { break; } From 68d5bbcb064cb452676fad8ca71108224b924c35 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 3 Jan 2022 13:13:16 +0800 Subject: [PATCH 09/22] add q103 --- README.md | 1 + README_EN.md | 1 + .../Solution.java" | 44 +++++++++++++++++++ .../TreeNode.java" | 11 +++++ 4 files changed, 57 insertions(+) create mode 100644 "src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/Solution.java" create mode 100644 "src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/TreeNode.java" diff --git a/README.md b/README.md index 247a5cf..0d7df89 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ - [q94_二叉树的中序遍历](/src/树的遍历/q94_二叉树的中序遍历) - [q102_二叉树的层次遍历](/src/树的遍历/q102_二叉树的层次遍历) +- [q103_二叉树的锯齿形层序遍历](/src/树的遍历/q103_二叉树的锯齿形层序遍历) - [q110_平衡二叉树](/src/树的遍历/q110_平衡二叉树) - [q144_二叉树的前序遍历](/src/树的遍历/q144_二叉树的前序遍历) - [q145_二叉树的后序遍历](/src/树的遍历/q145_二叉树的后序遍历) diff --git a/README_EN.md b/README_EN.md index 21acd77..48df09d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -119,6 +119,7 @@ - [Question 94 : Binary Tree Inorder Traversal](/src/树的遍历/q94_二叉树的中序遍历) - [Question 102 : Binary Tree Level Order Traversal](/src/树的遍历/q102_二叉树的层次遍历) +- [Question 103 : Binary Tree Zigzag Level Order Traversal](/src/树的遍历/q103_二叉树的锯齿形层序遍历) - [Question 110 : Balanced Binary Tree](/src/树的遍历/q110_平衡二叉树) - [Question 144 : Binary Tree Preorder Traversal](/src/树的遍历/q144_二叉树的前序遍历) - [Question 145 : Binary Tree Postorder Traversal](/src/树的遍历/q145_二叉树的后序遍历) diff --git "a/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/Solution.java" "b/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/Solution.java" new file mode 100644 index 0000000..dff5487 --- /dev/null +++ "b/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/Solution.java" @@ -0,0 +1,44 @@ +package 树的遍历.q103_二叉树的锯齿形层序遍历; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 和层序遍历相同,额外加一个标记控制插入队列的位置 + */ +public class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + List> ans = new ArrayList<>(); + if (root == null) { + return ans; + } + Queue queue = new LinkedList<>(); + queue.add(root); + boolean flag = true; + while (!queue.isEmpty()) { + List list = new ArrayList<>(); + int size = queue.size(); + while (size > 0) { + TreeNode tn = queue.poll(); + if (flag) { + list.add(tn.val); + } else { + list.add(0, tn.val); + } + if (tn.left != null) { + queue.add(tn.left); + } + if (tn.right != null) { + queue.add(tn.right); + } + size--; + } + flag = !flag; + ans.add(list); + } + return ans; + } +} diff --git "a/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/TreeNode.java" "b/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/TreeNode.java" new file mode 100644 index 0000000..931d109 --- /dev/null +++ "b/src/\346\240\221\347\232\204\351\201\215\345\216\206/q103_\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206/TreeNode.java" @@ -0,0 +1,11 @@ +package 树的遍历.q103_二叉树的锯齿形层序遍历; + +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { + val = x; + } +} From 75d8926232927f95375cda2b8f6a6beb53f27d21 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Fri, 11 Feb 2022 13:44:27 +0800 Subject: [PATCH 10/22] add q160 --- README.md | 1 + README_EN.md | 1 + .../ListNode.java" | 11 ++++++++ .../Solution.java" | 28 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 "src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/ListNode.java" create mode 100644 "src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/Solution.java" diff --git a/README.md b/README.md index 0d7df89..c49e0cb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [q25_k个一组翻转链表](/src/链表操作/q25_k个一组翻转链表) - [q61_旋转链表](/src/链表操作/q61_旋转链表) - [q138_复制带随机指针的链表](/src/链表操作/q138_复制带随机指针的链表) +- [q160_相交链表](/src/链表操作/q160_相交链表) - [q206_反转链表](/src/链表操作/q206_反转链表) ### 双指针遍历/滑动窗口 diff --git a/README_EN.md b/README_EN.md index 48df09d..4946777 100644 --- a/README_EN.md +++ b/README_EN.md @@ -16,6 +16,7 @@ - [Question 25 : Reverse Nodes in k-Group](/src/链表操作/q25_k个一组翻转链表) - [Question 61 : Rotate List](/src/链表操作/q61_旋转链表) - [Question 138 : Copy List with Random Pointer](/src/链表操作/q138_复制带随机指针的链表) +- [Question 160 : Intersection of Two Linked Lists](/src/链表操作/q160_相交链表) - [Question 206 : Reverse Linked List](/src/链表操作/q206_反转链表) ### Two Pointers Traversal / Sliding Window diff --git "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/ListNode.java" "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/ListNode.java" new file mode 100644 index 0000000..f3da319 --- /dev/null +++ "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/ListNode.java" @@ -0,0 +1,11 @@ +package 链表操作.q160_相交链表; + +public class ListNode { + int val; + ListNode next; + + ListNode(int x) { + val = x; + next = null; + } +} diff --git "a/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/Solution.java" "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/Solution.java" new file mode 100644 index 0000000..8009847 --- /dev/null +++ "b/src/\351\223\276\350\241\250\346\223\215\344\275\234/q160_\347\233\270\344\272\244\351\223\276\350\241\250/Solution.java" @@ -0,0 +1,28 @@ +package 链表操作.q160_相交链表; + +import java.util.HashSet; +import java.util.Set; + +/** + * 哈希存储 + * + * 方法二:两个链表相连,快慢指针判断是否有环(省略) + */ +public class Solution { + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + Set visited = new HashSet<>(); + ListNode temp = headA; + while (temp != null) { + visited.add(temp); + temp = temp.next; + } + temp = headB; + while (temp != null) { + if (visited.contains(temp)) { + return temp; + } + temp = temp.next; + } + return null; + } +} From a0acb0b50da217dbeb33a2510a8f31c354eb2657 Mon Sep 17 00:00:00 2001 From: Guangxin Yuan <274841922@qq.com> Date: Wed, 1 Mar 2023 10:30:21 +0800 Subject: [PATCH 11/22] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c49e0cb..f697400 100644 --- a/README.md +++ b/README.md @@ -136,3 +136,7 @@ ## 面试问题整理 - [面试问题整理](/Rocket.md) + +## LeetCode会员优惠 + +- [优惠开通LeetCode会员](https://leetcode.cn/premium/?promoChannel=yuanguangxin) From b533c626fbf36eeac8474bf6fe875e1c8d5d7ab9 Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Mon, 27 Mar 2023 03:07:40 +0800 Subject: [PATCH 12/22] add etc --- .../Main.java" | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 "src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" diff --git "a/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" "b/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" new file mode 100644 index 0000000..1ff1f10 --- /dev/null +++ "b/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" @@ -0,0 +1,61 @@ +package 其他.阿拉伯数字转中文; + +/** + * @author yuanguangxin + */ +public class Main { + private static final char[] numArrays = {'零', '一', '二', '三', '四', '五', '六', '七', '八', '九'}; + private static final char[] units = {'十', '百', '千', '万', '亿'}; + private static final StringBuilder ans = new StringBuilder(); + + /** + * 采用递归的方法将转换的结果存储到 ans 变量中, 注意 `万` 和 `亿` 在 units 数组中不是连续的, 所以 + * 当数字达到5位数或9位数时, 我们分开讨论。 + * + * @param num + */ + private static void intToChineseNum(int num) { + String s = String.valueOf(num); + char[] chars = s.toCharArray(); + int n = chars.length; + + // 只剩下一位时, 直接返回 numArrays 数组中对应的数字 + if (n == 1) { + ans.append(numArrays[chars[0] - '0']); + // 如果 num 超过 5 位, 则先判断是否上亿, 然后将 num 拆分 + } else if (n >= 5) { + n = n >= 9 ? 9 : 5; + int multi = (int) Math.pow(10, n - 1); + // div 表示 num 中上亿或上万的部分数值 + int div = num / multi; + // mod 表示剩余的部分数值 + int mod = num % multi; + // 对前一部分数值进行转换, 然后添加单位万/亿 + intToChineseNum(div); + ans.append(n == 5 ? units[3] : units[4]); + String s1 = String.valueOf(div); + String s2 = String.valueOf(mod); + // 判断中间是否有 0 + if (s.charAt(s1.length() - 1) == '0' || s2.length() < n - 1) ans.append("零"); + // 转换剩余部分 + intToChineseNum(mod); + // 如果 num 不超过 5 位, 处理过程与上面相似 + } else { + int multi = (int) Math.pow(10, n - 1); + int div = num / multi; + int mod = num % multi; + ans.append(numArrays[div]).append(units[n - 2]); + if (mod != 0) { + if (String.valueOf(mod).length() < n - 1) { + ans.append("零"); + } + intToChineseNum(mod); + } + } + } + + public static void main(String[] args) { + Main.intToChineseNum(121399013); + System.out.println(ans.toString()); + } +} From 13f120ec9891fbc7cf03f3e675a2e406dbae00d0 Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Mon, 3 Apr 2023 23:10:42 +0800 Subject: [PATCH 13/22] add etc --- .../Main.java" | 11 ----------- 1 file changed, 11 deletions(-) diff --git "a/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" "b/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" index 1ff1f10..1ff4aa2 100644 --- "a/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" +++ "b/src/\345\205\266\344\273\226/\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227\350\275\254\344\270\255\346\226\207/Main.java" @@ -8,12 +8,6 @@ public class Main { private static final char[] units = {'十', '百', '千', '万', '亿'}; private static final StringBuilder ans = new StringBuilder(); - /** - * 采用递归的方法将转换的结果存储到 ans 变量中, 注意 `万` 和 `亿` 在 units 数组中不是连续的, 所以 - * 当数字达到5位数或9位数时, 我们分开讨论。 - * - * @param num - */ private static void intToChineseNum(int num) { String s = String.valueOf(num); char[] chars = s.toCharArray(); @@ -53,9 +47,4 @@ private static void intToChineseNum(int num) { } } } - - public static void main(String[] args) { - Main.intToChineseNum(121399013); - System.out.println(ans.toString()); - } } From f58a68dcb18ba5382256e95275dac863c5fd8e1e Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Tue, 4 Apr 2023 00:17:16 +0800 Subject: [PATCH 14/22] add etc --- Rocket.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/Rocket.md b/Rocket.md index f68d3c0..52d8740 100644 --- a/Rocket.md +++ b/Rocket.md @@ -71,6 +71,26 @@ zk实现分布式锁主要利用其临时顺序节点,实现分布式锁的步 4. Set集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。 5. Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作。 +### Redis - ziplist、quicklist、listpack + +ziplist 是一个特殊双向链表,不像普通的链表使用前后指针关联在一起,它是存储在连续内存上的。 + +1. zlbytes: 32 位无符号整型,记录 ziplist 整个结构体的占用空间大小。当然了也包括 zlbytes 本身。这个结构有个很大的用处,就是当需要修改 ziplist 时候不需要遍历即可知道其本身的大小。 这个 SDS 中记录字符串的长度有相似之处,这些好的设计往往在平时的开发中可以采纳一下。 +2. zltail: 32 位无符号整型, 记录整个 ziplist 中最后一个 entry 的偏移量。所以在尾部进行 POP 操作时候不需要先遍历一次。 +3. zllen: 16 位无符号整型, 记录 entry 的数量, 所以只能表示 2^16。但是 Redis 作了特殊的处理:当实体数超过 2^16 ,该值被固定为 2^16 - 1。 所以这种时候要知道所有实体的数量就必须要遍历整个结构了。 +4. entry: 真正存数据的结构。 +5. zlend: 8 位无符号整型, 固定为 255 (0xFF)。为 ziplist 的结束标识 + +连锁更新是 ziplist 一个比较大的缺点,这也是在 v7.0 被 listpack 所替代的一个重要原因。 + +ziplist 在更新或者新增时候,如空间不够则需要对整个列表进行重新分配。当新插入的元素较大时,可能会导致后续元素的 prevlen 占用空间都发生变化,从而引起「连锁更新」问题,导致每个元素的空间都要重新分配,造成访问压缩列表性能的下降。 + +quicklist 的设计,其实是结合了链表和 ziplist 各自的优势。简单来说,一个 quicklist 就是一个链表,而链表中的每个元素又是一个 ziplist。 + +listpack 也叫紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据,同时为了节省内存空间,listpack 列表项使用了多种编码方式,来表示不同长度的数据,这些数据包括整数和字符串。在 listpack 中,因为每个列表项只记录自己的长度,而不会像 ziplist 中的列表项那样,会记录前一项的长度。所以,当我们在 listpack 中新增或修改元素时,实际上只会涉及每个列表项自己的操作,而不会影响后续列表项的长度变化,这就避免了连锁更新。 + +### + ### Redis 的数据过期策略 Redis 中数据过期策略采用定期删除+惰性删除策略 @@ -149,6 +169,22 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 3. 用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 4. value的大小:redis可以达到1GB,而memcache只有1MB。 +### Redis6.0 多线程的实现机制 + +在 redis 6.0 以前,完整的 redis 线程模型是 主线程(1个)+ 后台线程(三个),三个后台线程分别处理: +1. 关闭 AOF、RDB 等过程中产生的大临时文件 +2. 将追加至 AOF 文件的数据刷盘(一般情况下 write 调用之后,数据被写入内核缓冲区,通过 fsync 调用才将内核缓冲区的数据写入磁盘) +3. 惰性释放大对象(大键的空间回收交由单独线程实现,主线程只做关系解除,可以快速返回,继续处理其他事件,避免服务器长时间阻塞) + +Redis 6.0之后,Redis 正式在核心网络模型中引入了多线程,也就是所谓的 I/O threading,至此 Redis 真正拥有了多线程模型。一般来说,一个正常的客户端请求会经历 建立连接、IO就绪监听/读、命令执行、IO写等一系列操作。 +1. 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列 +2. 主线程处理完读事件之后,通过Round Robin 将这些连接分配给这些IO线程。 +3. 主线程阻塞等待IO线程读取socket +4. 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行回写 socket +5. 主线程阻塞等待 IO 线程将数据回写 socket 完毕 + +Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。 + ### Redis的几种集群模式 1. 主从复制 @@ -843,6 +879,10 @@ Kafka中消息是以topic进行分类的,生产者通过topic向Kafka broker * acks = 1:意味若 Leader 在收到消息并把它写入到分区数据文件(不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下,如果发生正常的 Leader 选举,生产者会在选举时收到一个 LeaderNotAvailableException 异常,如果生产者能恰当地处理这个错误,它会重试发送悄息,最终消息会安全到达新的 Leader 那里。不过在这个模式下仍然有可能丢失数据,比如消息已经成功写入 Leader,但在消息被复制到 follower 副本之前 Leader发生崩溃。 * acks = all(这个和 request.required.acks = -1 含义一样):意味着 Leader 在返回确认或错误响应之前,会等待所有同步副本都收到悄息。如果和min.insync.replicas 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息,生产者会一直重试直到消息被成功提交。不过这也是最慢的做法,因为生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。 +另外可以设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。 + +除此之外,可以设置 unclean.leader.election.enable = false。这是 Broker 端的参数,它控制的是哪些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false,即不允许这种情况的发生。 + ### Kafka消息是采用Pull模式,还是Push模式 Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式。Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到t达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达。 @@ -855,11 +895,46 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 4. 批量发送:Kafka允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去 5. 数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩 +### Kafka中broker数量和partition数量设置 + +1. 一个partition最好对应一个硬盘,这样能最大限度发挥顺序写的优势。一个broker如果对应多个partition,需要随机分发,顺序IO会退化成随机IO。 +2. 当broker数量大于partition数量,则有些broker空闲,此时增加partition会带来性能提升。而且是线性增长。 +3. 当两者相等,则所有broker都启用,吞吐达到瓶颈。 +4. 继续增加,则broker会不均衡,有点会分到更多的partition,顺序IO退化成随机IO。 + +### Kafka中partition数量和消费者数量设置 + +消费者的数量不应该比分区数多,因为多出来的消费者是空闲的,没有任何帮助 + ### Kafka判断一个节点还活着的两个条件 1. 节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接 2. 如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久 +### 如何提升 Kafka 生产者的吞吐量 + +1. buffer.memory:设置发送消息的缓冲区,默认值是33554432,就是32MB。如果发送消息出去的速度小于写入消息进去的速度,就会导致缓冲区写满,此时生产消息就会阻塞住,所以说这里就应该多做一些压测,尽可能保证说这块缓冲区不会被写满导致生产行为被阻塞住。 +2. compression.type:默认是none,不压缩,但是也可以使用lz4压缩,效率还是不错的,压缩之后可以减小数据量,提升吞吐量,但是会加大producer端的cpu开销。 +3. batch.size:设置merge batch的大小,如果 batch 太小,会导致频繁网络请求,吞吐量下降。如果batch太大,会导致一条消息需要等待很久才能被发送出去,而且会让内存缓冲区有很大压力,过多数据缓冲在内存里。默认值是:16384,就是16kb,也就是一个batch满了16kb就发送出去,一般在实际生产环境,这个batch的值可以增大一些来提升吞吐量。 + +### Kafka中的副本 + +在kafka中有topic的概念,每个topic会包含多个partition,而副本所描述的对象就是partition,通常情况下(默认情况下kafka集群最少3个broker节点),每个partition会有1个leader副本,2个follower副本。3个partition副本会保存到不同的broker上,这样即使当某个broker宕机了,由于副本的存在,kafka依旧可以对外提供服务。 + +在partition 副本机制中,只有leader副本可以对外提供读写服务,follower不对外提供服务,只是异步的从leader中拉群最新的数据,来保证follower和leader数据的一致性。 + +当leader所在broker宕机挂掉后,kafka依托Zookeeper提供的监控功能 ,能够实时的感知到,并立即在存活的follower中选择一个副本作为leader副本,对外提供服务,当老的leader副本重新回来后,只能作为follower副本加入到集群中。 + +虽然利用offset可以解决主从切换数据不一致问题,但是会导致消息写入性能下降,牺牲了kafka的高吞吐的特性。kafka自身也考虑到了,主从切换对数据一致性的影响,采取了ISR来降低数据的不一致。 + +ISR本质上是一个partition副本的集合,只不过副本要想进入这个集合中是有条件的。这个条件是:和leader副本中的数据是同步的follower副本才可以进入到ISR中。 + +用来衡量follower是否符合“和leader副本是同步的”,不是依据两者数据的差异量,而是两者数据不一致持续的时间,如果持续的时间过长,那么就认为两者不同步。其实这种设定也是有道理的,试想:发送者一次批量发送了大量的消息,这些消息先写入到leader中,此时follower中没有这些数据,那么所有follower都变成和leader是不同步的了,但是不足之处也很明显:ISR中和leader数据差异比较大的follower,是有可能被选为新的leader的。 + +上述持续时间的长短有参数 replica.lag.max.ms 来定义,默认10s,如果ISR中的follower副本和leader副本数据存在差异的时间超过10s,那么这个follower副本就会被从ISR中踢出出去,当然如果该follower后续慢慢追上了leader副本,那么这个follower就会被重新添加到ISR中,也就是说iSR是一个动态的集合。 + +kafka 把所有不在 ISR 中的存活副本都称为非同步副本,通常来说,非同步副本落后 leader 太多,因此,如果选择这些副本作为新 leader,就可能出现数据的丢失。毕竟,这些副本中保存的消息远远落后于老 leader 中的消息。在 Kafka 中,选举这种副本的过程称为 Unclean 领导者选举。Broker 端参数 unclean.leader.election.enable 控制是否允许 Unclean 领导者选举。 + ## Dubbo ### Dubbo的容错机制 From 6e56ef6981046d851ad6bc07053aba24ec95712e Mon Sep 17 00:00:00 2001 From: Guangxin Yuan <274841922@qq.com> Date: Sat, 17 Aug 2024 15:55:56 +0800 Subject: [PATCH 15/22] Update README.md update --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f697400..c49e0cb 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,3 @@ ## 面试问题整理 - [面试问题整理](/Rocket.md) - -## LeetCode会员优惠 - -- [优惠开通LeetCode会员](https://leetcode.cn/premium/?promoChannel=yuanguangxin) From ba523eb7fe50b1e7699f3c654c77e01c282fe70d Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Tue, 6 May 2025 15:01:44 +0800 Subject: [PATCH 16/22] add q1920 --- README.md | 1 + README_EN.md | 1 + .../Solution.java" | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 "src/\346\225\260\345\255\227\346\223\215\344\275\234/q1920_\345\237\272\344\272\216\346\216\222\345\210\227\346\236\204\345\273\272\346\225\260\347\273\204/Solution.java" diff --git a/README.md b/README.md index c49e0cb..e7f0b9b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ - [q43_字符串相乘](/src/数字操作/q43_字符串相乘) - [q172_阶乘后的零](/src/数字操作/q172_阶乘后的零) - [q258_各位相加](/src/数字操作/q258_各位相加) +- [q1920_基于排列构建数组](/src/数字操作/q1920_基于排列构建数组) ### 数组操作 diff --git a/README_EN.md b/README_EN.md index 4946777..e9e4f32 100644 --- a/README_EN.md +++ b/README_EN.md @@ -54,6 +54,7 @@ - [Question 43 : Multiply Strings](/src/数字操作/q43_字符串相乘) - [Question 172 : Factorial Trailing Zeroes](/src/数字操作/q172_阶乘后的零) - [Question 258 : Add Digits](/src/数字操作/q258_各位相加) +- [Question 1920 : Build Array from Permutation](/src/数字操作/q1920_基于排列构建数组) ### Array Operations diff --git "a/src/\346\225\260\345\255\227\346\223\215\344\275\234/q1920_\345\237\272\344\272\216\346\216\222\345\210\227\346\236\204\345\273\272\346\225\260\347\273\204/Solution.java" "b/src/\346\225\260\345\255\227\346\223\215\344\275\234/q1920_\345\237\272\344\272\216\346\216\222\345\210\227\346\236\204\345\273\272\346\225\260\347\273\204/Solution.java" new file mode 100644 index 0000000..9c2cc24 --- /dev/null +++ "b/src/\346\225\260\345\255\227\346\223\215\344\275\234/q1920_\345\237\272\344\272\216\346\216\222\345\210\227\346\236\204\345\273\272\346\225\260\347\273\204/Solution.java" @@ -0,0 +1,23 @@ +package 数字操作.q1920_基于排列构建数组; + +/** + * 注意观察题目,数组中数字为[0,999]闭区间 + */ +class Solution { + public int[] buildArray(int[] nums) { + int n = nums.length; + for (int i = 0; i < n; i++) { + nums[i] += 1000 * (nums[nums[i]] % 1000); + System.out.println(nums[i]); + } + for (int i = 0; i < n; i++) { + nums[i] /= 1000; + } + return nums; + } + + public static void main(String[] args) { + int[] nums = new int[]{3, 2, 0, 1, 4}; + new Solution().buildArray(nums); + } +} From 7f426ccfdfb7c25886b0ea397486110b102594ec Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Tue, 13 May 2025 19:31:08 +0800 Subject: [PATCH 17/22] update --- Rocket.md | 15 +++ src/Solution.java | 81 ++++++++++++++++ .../LRUCache.java" | 93 +++++++++++++++++++ .../QuickSelect.java" | 47 ++++++++++ 4 files changed, 236 insertions(+) create mode 100644 src/Solution.java create mode 100644 "src/\345\205\266\344\273\226/lru\345\256\236\347\216\260/LRUCache.java" create mode 100644 "src/\345\205\266\344\273\226/\346\237\245\346\211\276\347\254\254k\345\244\247\347\232\204\346\225\260\345\255\227/QuickSelect.java" diff --git a/Rocket.md b/Rocket.md index 52d8740..add3805 100644 --- a/Rocket.md +++ b/Rocket.md @@ -125,6 +125,14 @@ Redis中setnx不支持设置过期时间,做分布式锁时要想避免某一 1. 服务端缓存:即将热点数据缓存至服务端的内存中.(利用Redis自带的消息通知机制来保证Redis和服务端热点Key的数据一致性,对于热点Key客户端建立一个监听,当热点Key有更新操作的时候,服务端也随之更新。) 2. 备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。 +### Redis的RedLock算法 + +1. RedLock算法‌是由Redis的作者Salvatore Sanfilippo(也称为Antirez)提出的一种分布式锁的实现方案,旨在解决在分布式系统中实现可靠锁的问题,特别是解决单个Redis实例作为分布式锁时可能出现的单点故障问题。 +2. 过程1:‌获取当前时间戳‌:客户端在尝试获取锁时,首先记录当前的时间戳t0。 +3. 过程2:‌在多个Redis实例上尝试获取锁‌:客户端会向N个(通常建议是奇数个,如5个)独立的Redis实例发送请求,尝试获取相同的锁。每个请求都会设置相同的锁名、唯一标识符(如UUID)和过期时间。 +4. 过程3:‌统计获取锁成功的实例数量‌:客户端统计在多少个Redis实例上成功获取了锁。 +5. 过程4:‌判断锁是否获取成功‌:如果在过半数(即N/2+1)的Redis实例上成功获取了锁,并且从获取第一个锁到最后一个锁的总时间小于锁的过期时间的一半,那么认为锁获取成功。否则,认为锁获取失败,客户端需要释放已经获取的锁(如果有的话)‌ + ### 如何解决 Redis 缓存雪崩问题 1. 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉 @@ -834,6 +842,13 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 5. 异常被 catch 捕获导致@Transactional失效。 6. 数据库引擎不支持事务。 +### @Transactional配合分布式锁的使用 +1. 在分布式系统中,先获取分布式锁还是先开始事务,取决于具体的应用场景和业务需求。这两种方式各有优缺点,选择哪种方式需要根据实际需求权衡。 +2. 先获取分布式锁,再开始事务:如果锁的持有者在事务中发生故障,可能会导致锁无法释放,从而引发死锁。如果锁的粒度过大,可能会降低系统的并发性能。 +3. 先获取分布式锁,再开始事务适用场景:需要严格互斥访问共享资源的场景如对数据库中的某个表或某个记录进行更新操作,操作复杂且需要多个步骤的场景 +4. 先开始事务,再获取分布式锁:这种方式较少见,通常用于一些特殊的场景,例如事务的执行时间非常短,或者锁的获取成本较高。需要在事务中处理锁的获取和释放逻辑,增加了实现的复杂性。如果事务在获取锁之前提交,可能会导致数据不一致。 +5. 先开始事务,再获取分布式锁适用场景:事务执行时间非常短的场景:例如,简单的数据库更新操作。锁的获取成本较高的场景:例如,锁服务的响应时间较长,或者锁的粒度非常细。 + ### Spring中的事务传播机制 1. REQUIRED(默认,常用):支持使用当前事务,如果当前事务不存在,创建一个新事务。eg:方法B用REQUIRED修饰,方法A调用方法B,如果方法A当前没有事务,方法B就新建一个事务(若还有C则B和C在各自的事务中独立执行),如果方法A有事务,方法B就加入到这个事务中,当成一个事务。 diff --git a/src/Solution.java b/src/Solution.java new file mode 100644 index 0000000..ce72957 --- /dev/null +++ b/src/Solution.java @@ -0,0 +1,81 @@ +public class Solution { + + // 找到旋转数组的最小值位置 + private static int findMinIndex(int[] nums) { + int left = 0; + int right = nums.length - 1; + + while (left < right) { + int mid = left + (right - left) / 2; + if (nums[mid] > nums[right]) { + left = mid + 1; + } else { + right = mid; + } + } + return left; // 最小值的位置 + } + + // 快速选择算法(类似快速排序的分区操作) + private static int quickSelect(int[] nums, int start, int end, int k) { + if (start == end) { + return nums[start]; + } + + int pivot = partition(nums, start, end); + + if (pivot == k) { + return nums[pivot]; + } else if (pivot > k) { + return quickSelect(nums, start, pivot - 1, k); + } else { + return quickSelect(nums, pivot + 1, end, k); + } + } + + // 快速排序的分区操作 + private static int partition(int[] nums, int start, int end) { + int pivot = nums[end]; + int i = start - 1; + + for (int j = start; j < end; j++) { + if (nums[j] >= pivot) { // 从大到小排序 + i++; + swap(nums, i, j); + } + } + + swap(nums, i + 1, end); + return i + 1; + } + + // 交换数组中的两个元素 + private static void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + + // 主函数:在旋转数组中找到第 k 大的数字 + public static int findKthLargest(int[] nums, int k) { + int minIndex = findMinIndex(nums); // 找到最小值的位置 + int n = nums.length; + + // 将数组分为两部分,分别查找第 k 大的数字 + if (k <= n - minIndex) { + // 第 k 大的数字在后半部分 + return quickSelect(nums, minIndex, n - 1, k - 1); + } else { + // 第 k 大的数字在前半部分 + return quickSelect(nums, 0, minIndex - 1, k - (n - minIndex) - 1); + } + } + + public static void main(String[] args) { + int[] nums = {4, 5, 6, 7, 0, 1, 2}; + int k = 3; + + int result = findKthLargest(nums, k); + System.out.println("第 " + k + " 大的数字是: " + result); + } +} \ No newline at end of file diff --git "a/src/\345\205\266\344\273\226/lru\345\256\236\347\216\260/LRUCache.java" "b/src/\345\205\266\344\273\226/lru\345\256\236\347\216\260/LRUCache.java" new file mode 100644 index 0000000..91228f5 --- /dev/null +++ "b/src/\345\205\266\344\273\226/lru\345\256\236\347\216\260/LRUCache.java" @@ -0,0 +1,93 @@ +package 其他.lru实现; + +import java.util.HashMap; +import java.util.Map; + +public class LRUCache { + class DLinkedNode { + int key; + int value; + DLinkedNode prev; + DLinkedNode next; + + public DLinkedNode() { + } + + public DLinkedNode(int _key, int _value) { + key = _key; + value = _value; + } + } + + private Map cache = new HashMap<>(); + private int size; + private int capacity; + private DLinkedNode head, tail; + + public LRUCache(int capacity) { + this.size = 0; + this.capacity = capacity; + // 使用伪头部和伪尾部节点 + head = new DLinkedNode(); + tail = new DLinkedNode(); + head.next = tail; + tail.prev = head; + } + + public int get(int key) { + DLinkedNode node = cache.get(key); + if (node == null) { + return -1; + } + // 如果 key 存在,先通过哈希表定位,再移到头部 + moveToHead(node); + return node.value; + } + + public void put(int key, int value) { + DLinkedNode node = cache.get(key); + if (node == null) { + // 如果 key 不存在,创建一个新的节点 + DLinkedNode newNode = new DLinkedNode(key, value); + // 添加进哈希表 + cache.put(key, newNode); + // 添加至双向链表的头部 + addToHead(newNode); + ++size; + if (size > capacity) { + // 如果超出容量,删除双向链表的尾部节点 + DLinkedNode tail = removeTail(); + // 删除哈希表中对应的项 + cache.remove(tail.key); + --size; + } + } else { + // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部 + node.value = value; + moveToHead(node); + } + } + + private void addToHead(DLinkedNode node) { + node.prev = head; + node.next = head.next; + head.next.prev = node; + head.next = node; + } + + private void removeNode(DLinkedNode node) { + node.prev.next = node.next; + node.next.prev = node.prev; + } + + private void moveToHead(DLinkedNode node) { + removeNode(node); + addToHead(node); + } + + private DLinkedNode removeTail() { + DLinkedNode res = tail.prev; + removeNode(res); + return res; + } +} diff --git "a/src/\345\205\266\344\273\226/\346\237\245\346\211\276\347\254\254k\345\244\247\347\232\204\346\225\260\345\255\227/QuickSelect.java" "b/src/\345\205\266\344\273\226/\346\237\245\346\211\276\347\254\254k\345\244\247\347\232\204\346\225\260\345\255\227/QuickSelect.java" new file mode 100644 index 0000000..076c1de --- /dev/null +++ "b/src/\345\205\266\344\273\226/\346\237\245\346\211\276\347\254\254k\345\244\247\347\232\204\346\225\260\345\255\227/QuickSelect.java" @@ -0,0 +1,47 @@ +package 其他.查找第k大的数字; + +public class QuickSelect { + public static int findKthLargest(int[] nums, int k) { + return quickSelect(nums, 0, nums.length - 1, nums.length - k); + } + + private static int quickSelect(int[] nums, int left, int right, int kSmallest) { + if (left == right) { // 如果只剩一个元素,那么它就是第k小的 + return nums[left]; + } + + int pivotIndex = partition(nums, left, right); + + if (kSmallest == pivotIndex) { + return nums[kSmallest]; + } else if (kSmallest < pivotIndex) { + return quickSelect(nums, left, pivotIndex - 1, kSmallest); + } else { + return quickSelect(nums, pivotIndex + 1, right, kSmallest); + } + } + + private static int partition(int[] nums, int left, int right) { + int pivot = nums[right]; // 选择最右边的元素作为pivot + int i = left; // i是小于pivot的元素的最后一个位置 + for (int j = left; j < right; j++) { + if (nums[j] < pivot) { + swap(nums, i++, j); + } + } + swap(nums, i, right); // 把pivot放到中间位置 + return i; // 返回pivot的正确位置 + } + + private static void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + + public static void main(String[] args) { + int[] nums = {3, 2, 1, 5, 6, 4}; + int k = 2; // 找第2大的数字(即第k大的数字) + System.out.println("第 " + k + " 大的数字是: " + findKthLargest(nums, k)); // 输出应该是5 + } +} From adcef0c89d8aff1114fb778816cd581ec6bd4fef Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Thu, 22 May 2025 18:26:21 +0800 Subject: [PATCH 18/22] update --- .../Solution.java" | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git "a/src/\345\212\250\346\200\201\350\247\204\345\210\222/q1143_\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/Solution.java" "b/src/\345\212\250\346\200\201\350\247\204\345\210\222/q1143_\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/Solution.java" index 8225a50..9f55848 100644 --- "a/src/\345\212\250\346\200\201\350\247\204\345\210\222/q1143_\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/Solution.java" +++ "b/src/\345\212\250\346\200\201\350\247\204\345\210\222/q1143_\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227/Solution.java" @@ -2,7 +2,7 @@ /** * 动态规划 dp[i + 1][j + 1] = Math.max(dp[i+1][j], dp[i][j+1]) o(m*n) - * + *

* 若题目为最长公共子串,则在c1,c2不相等时不做处理(赋值0),在遍历过程中记录最大值即可 */ public class Solution { @@ -25,4 +25,37 @@ public int longestCommonSubsequence(String text1, String text2) { } return dp[m][n]; } + + /** + * 最长公共字串 + * + * @param str1 + * @param str2 + * @return + */ + public static String longestCommonSubstring(String str1, String str2) { + int m = str1.length(); + int n = str2.length(); + int[][] dp = new int[m + 1][n + 1]; + int maxLength = 0; + int endIndex = -1; + + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (str1.charAt(i - 1) == str2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + if (dp[i][j] > maxLength) { + maxLength = dp[i][j]; + endIndex = i - 1; + } + } else { + dp[i][j] = 0; + } + } + } + if (maxLength == 0) { + return ""; + } + return str1.substring(endIndex - maxLength + 1, endIndex + 1); + } } From 73cf06a9d0cfaa942f160e7b6219863c994259b8 Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Mon, 26 May 2025 15:30:28 +0800 Subject: [PATCH 19/22] update --- src/Solution.java | 81 ----------------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 src/Solution.java diff --git a/src/Solution.java b/src/Solution.java deleted file mode 100644 index ce72957..0000000 --- a/src/Solution.java +++ /dev/null @@ -1,81 +0,0 @@ -public class Solution { - - // 找到旋转数组的最小值位置 - private static int findMinIndex(int[] nums) { - int left = 0; - int right = nums.length - 1; - - while (left < right) { - int mid = left + (right - left) / 2; - if (nums[mid] > nums[right]) { - left = mid + 1; - } else { - right = mid; - } - } - return left; // 最小值的位置 - } - - // 快速选择算法(类似快速排序的分区操作) - private static int quickSelect(int[] nums, int start, int end, int k) { - if (start == end) { - return nums[start]; - } - - int pivot = partition(nums, start, end); - - if (pivot == k) { - return nums[pivot]; - } else if (pivot > k) { - return quickSelect(nums, start, pivot - 1, k); - } else { - return quickSelect(nums, pivot + 1, end, k); - } - } - - // 快速排序的分区操作 - private static int partition(int[] nums, int start, int end) { - int pivot = nums[end]; - int i = start - 1; - - for (int j = start; j < end; j++) { - if (nums[j] >= pivot) { // 从大到小排序 - i++; - swap(nums, i, j); - } - } - - swap(nums, i + 1, end); - return i + 1; - } - - // 交换数组中的两个元素 - private static void swap(int[] nums, int i, int j) { - int temp = nums[i]; - nums[i] = nums[j]; - nums[j] = temp; - } - - // 主函数:在旋转数组中找到第 k 大的数字 - public static int findKthLargest(int[] nums, int k) { - int minIndex = findMinIndex(nums); // 找到最小值的位置 - int n = nums.length; - - // 将数组分为两部分,分别查找第 k 大的数字 - if (k <= n - minIndex) { - // 第 k 大的数字在后半部分 - return quickSelect(nums, minIndex, n - 1, k - 1); - } else { - // 第 k 大的数字在前半部分 - return quickSelect(nums, 0, minIndex - 1, k - (n - minIndex) - 1); - } - } - - public static void main(String[] args) { - int[] nums = {4, 5, 6, 7, 0, 1, 2}; - int k = 3; - - int result = findKthLargest(nums, k); - System.out.println("第 " + k + " 大的数字是: " + result); - } -} \ No newline at end of file From a4ebe6c685b740b897df2f09d8bc2b3ae766fa0b Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Fri, 30 May 2025 12:42:13 +0800 Subject: [PATCH 20/22] update --- Rocket.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index add3805..914de88 100644 --- a/Rocket.md +++ b/Rocket.md @@ -279,7 +279,7 @@ SQL的执行顺序:from---where--group by---having---select---order by * 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的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0和ib_logfile1。为了得到更高的可靠性,用户可以设置多个的镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写重做日志文件1,当达到文件的最后时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。 -* MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 +* MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。MVCC的可以实现不同的隔离级别,这取决于是否针对同一个事物生成一个还是多个ReadView。 ### binlog和redolog的区别 @@ -859,6 +859,11 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 6. NEVER:无事务执行,如果当前有事务则抛出Exception。 7. NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 +### Spring中的@Conditional注解 +1. @Conditional 注解是一个非常强大的功能,用于根据特定条件动态决定是否创建某个Bean。它允许开发者在运行时根据不同的环境、配置或其他因素来灵活地控制Bean的实例化过程。 +2. 它的核心作用是基于条件判断来决定是否加载对应的Bean。如果条件成立,则创建并注册该Bean;如果条件不成立,则跳过该Bean的创建。 +3. @Conditional 注解需要配合一个或多个条件类(实现了 Condition 接口的类)一起使用。条件类需要实现 Condition 接口的 matches 方法,该方法返回一个布尔值,用于判断条件是否满足。 + ### Spring中Bean的生命周期 1. 实例化 Instantiation From e372fd07b37138e2f00a7302981bb6b50cf04056 Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Thu, 5 Jun 2025 21:45:27 +0800 Subject: [PATCH 21/22] update --- Rocket.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index 914de88..f8cd7a7 100644 --- a/Rocket.md +++ b/Rocket.md @@ -89,7 +89,10 @@ quicklist 的设计,其实是结合了链表和 ziplist 各自的优势。简 listpack 也叫紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据,同时为了节省内存空间,listpack 列表项使用了多种编码方式,来表示不同长度的数据,这些数据包括整数和字符串。在 listpack 中,因为每个列表项只记录自己的长度,而不会像 ziplist 中的列表项那样,会记录前一项的长度。所以,当我们在 listpack 中新增或修改元素时,实际上只会涉及每个列表项自己的操作,而不会影响后续列表项的长度变化,这就避免了连锁更新。 -### +### Redis的跳表和Mysql的B+树结构 +1. B+树是多叉平衡搜索树,只需要3层左右就能存放2kw左右的数据,同样情况下跳表则需要24层左右,假设层高对应磁盘IO,那么B+树的读性能会比跳表要好,因此mysql选了B+树做索引。 +2. redis的读写全在内存里进行操作,不涉及磁盘IO,同时跳表实现简单,相比B+树、AVL树、少了旋转树结构的开销,因此redis使用跳表来实现ZSET,而不是树结构。 +3. 存储引擎RocksDB内部使用了跳表,对比使用B+树的innodb,虽然写性能更好,但读性能属实差了些。在读多写少的场景下,B+树依旧是最佳选择。 ### Redis 的数据过期策略 @@ -324,6 +327,7 @@ MySQL为了保证ACID中的一致性和持久性,使用了WAL(Write-Ahead Logg * 平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。 * 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。 * B+树:在B树的基础上,将非叶节点改造为不存储数据纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。 +* B+树是由多个页组成的多层级结构,每个页16Kb,对于主键索引来说,最末级的叶子结点放行数据,非叶子结点放的则是索引信息(主键id和页号),用于加速查询。 ### B+树的叶子节点都可以存哪些东西 From 30f3afaa3fc5aa9ce357f6569488c7fa785e56e5 Mon Sep 17 00:00:00 2001 From: "guangxin.yuan" Date: Sat, 7 Jun 2025 18:03:51 +0800 Subject: [PATCH 22/22] update --- Rocket.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Rocket.md b/Rocket.md index f8cd7a7..5785a8e 100644 --- a/Rocket.md +++ b/Rocket.md @@ -480,7 +480,11 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。 -但不是说GC必须STW,你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。 +### 如果不STW可能会出现的问题 + +1. 数据一致性问题: 如果不STW,应用程序线程和垃圾回收线程同时运行,可能会导致数据不一致。例如,在垃圾回收线程正在标记对象时,应用程序线程可能对对象的引用关系进行了修改。比如原本一个对象A引用对象B,垃圾回收线程在标记过程中认为对象B是可回收的,但应用程序线程在垃圾回收线程标记完成后,又重新创建了一个引用指向对象B,这就导致了垃圾回收器错误地回收了对象B,而应用程序线程还在使用它,从而引发错误。 +2. 内存泄漏风险增加: 如果垃圾回收线程不能准确地识别存活对象,可能会导致一些本应该被回收的对象没有被回收或者不该被回收的对象会被错误的垃圾回收。例如,当应用程序线程在垃圾回收过程中动态地创建和销毁对象时,垃圾回收线程如果没有暂停应用程序线程,可能会遗漏一些已经没有引用的对象。这些对象会一直占用内存,随着时间的推移,可能导致内存泄漏,最终使Java虚拟机的堆内存耗尽,引发OutOfMemoryError错误。 +3. 垃圾回收效率降低: 在并发环境中,垃圾回收线程和应用程序线程的交互会变得更加复杂。垃圾回收线程需要处理应用程序线程对对象的动态修改,这会增加垃圾回收的复杂性和开销。例如,垃圾回收线程需要不断检查应用程序线程对对象引用的修改,以确保垃圾回收的准确性。这种并发操作可能会导致垃圾回收的效率降低,使得垃圾回收时间变长,虽然没有STW,但可能会以更频繁的垃圾回收或者更长的垃圾回收周期来弥补,从而影响应用程序的整体性能。 ### 垃圾回收算法 @@ -926,6 +930,12 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 3. 当两者相等,则所有broker都启用,吞吐达到瓶颈。 4. 继续增加,则broker会不均衡,有点会分到更多的partition,顺序IO退化成随机IO。 +### Kafka的rebalance机制 + +1. 消费者的再平衡是指分区的所属权从一个消费者转移到另一消费者的行为,它为消费组具备高可用性和伸缩性提供保障, 使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。不过在再平衡发生期间, 消费组内的消费者是无法读取消息的。 +2. 再平衡会在以下情况下被触发:消费者加入或离开消费者组,消费者心跳超时或会话过期,分区数量变化,消费者处理超时。 +3. 再平衡的影响:传统再平衡会使所有消费者在再平衡期间会停止消费,直到新的分区分配完成。增量式再平衡可以让部分消费者可以继续消费未被重新分配的分区,减少停顿时间。 + ### Kafka中partition数量和消费者数量设置 消费者的数量不应该比分区数多,因为多出来的消费者是空闲的,没有任何帮助