diff --git a/2019/06/18/openresty/index.html b/2019/06/18/openresty/index.html index 29a1f6ea8a..63813653ec 100644 --- a/2019/06/18/openresty/index.html +++ b/2019/06/18/openresty/index.html @@ -1,4 +1,4 @@ -openresty | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

openresty

目前公司要对一些新的产品功能做灰度测试,因为在后端业务代码层面添加判断比较麻烦,所以想在nginx上做点手脚,就想到了openresty
前后也踩了不少坑,这边先写一点

首先是日志
error_log logs/error.log debug;
需要nginx开启日志的debug才能看到日志

使用 lua_code_cache off即可, 另外注意只有使用 content_by_lua_file 才会生效

http {
+openresty | Nicksxs's Blog

Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

openresty

目前公司要对一些新的产品功能做灰度测试,因为在后端业务代码层面添加判断比较麻烦,所以想在nginx上做点手脚,就想到了openresty
前后也踩了不少坑,这边先写一点

首先是日志
error_log logs/error.log debug;
需要nginx开启日志的debug才能看到日志

使用 lua_code_cache off即可, 另外注意只有使用 content_by_lua_file 才会生效

http {
   lua_code_cache off;
 }
 
@@ -20,4 +20,4 @@ location ~*local t = json.decode(str)
 if t then
     ngx.say(" --> ", type(t))
-end

cjson.safe包会在解析失败的时候返回nil

  • 还有一个是redis链接时如果host使用的是域名的话会提示“failed to connect: no resolver defined to resolve “redis.xxxxxx.com””,这里需要使用nginx的resolver指令,
    resolver 8.8.8.8 valid=3600s;

  • 还有一点补充下
    就是业务在使用redis的时候使用了db的特性,所以在lua访问redis的时候也需要执行db,这里lua的redis库也支持了这个特性,可以使用instance:select(config:get(‘db’))来切换db

  • 性能优化tips
    建议是尽量少使用阶段钩子,例如content_by_lua_file,*_by_lua

  • 发现一个不错的openresty站点
    地址

  • \ No newline at end of file +end

    cjson.safe包会在解析失败的时候返回nil

  • 还有一个是redis链接时如果host使用的是域名的话会提示“failed to connect: no resolver defined to resolve “redis.xxxxxx.com””,这里需要使用nginx的resolver指令,
    resolver 8.8.8.8 valid=3600s;

  • 还有一点补充下
    就是业务在使用redis的时候使用了db的特性,所以在lua访问redis的时候也需要执行db,这里lua的redis库也支持了这个特性,可以使用instance:select(config:get(‘db’))来切换db

  • 性能优化tips
    建议是尽量少使用阶段钩子,例如content_by_lua_file,*_by_lua

  • 发现一个不错的openresty站点
    地址

  • \ No newline at end of file diff --git a/2020/05/31/聊聊-Dubbo-的-SPI/index.html b/2020/05/31/聊聊-Dubbo-的-SPI/index.html index 1478a4e725..9f60a9fff8 100644 --- a/2020/05/31/聊聊-Dubbo-的-SPI/index.html +++ b/2020/05/31/聊聊-Dubbo-的-SPI/index.html @@ -1,4 +1,4 @@ -聊聊 Dubbo 的 SPI | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊聊 Dubbo 的 SPI

    SPI全称是Service Provider Interface,咋眼看跟api是不是有点相似,api是application interface,这两个其实在某些方面有类似的地方,也有蛮大的区别,比如我们基于 dubbo 的微服务,一般我们可以提供服务,然后非泛化调用的话,我们可以把 api 包提供给应用调用方,他们根据接口签名传对应参数并配置好对应的服务发现如 zk 等就可以调用我们的服务了,然后 spi 会有点类似但是是反过来的关系,相当于是一种规范,比如我约定完成这个功能需要两个有两个接口,一个是连接的,一个是断开的,其实就可以用 jdbc 的驱动举例,比较老套了,然后各个厂家去做具体的实现吧,到时候根据我接口的全限定名的文件来加载实际的实现类,然后运行的时候调用对应实现类的方法就完了

    3sKdpg

    看上面的图,java.sql.Driver就是 spi,对应在classpath 的 META-INF/services 目录下的这个文件,里边的内容就是具体的实现类

    1590735097909

    简单介绍了 Java的 SPI,再来说说 dubbo 的,dubbo 中为啥要用 SPI 呢,主要是为了框架的可扩展性和性能方面的考虑,比如协议层 dubbo 默认使用 dubbo 协议,同时也支持很多其他协议,也支持用户自己实现协议,那么跟 Java 的 SPI 会有什么区别呢,我们也来看个文件

    bqxWMp

    是不是看着很想,又有点不一样,在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名,在 Dubbo的 SPI配置文件中是 key=value 的形式,我们只需要对应的 key 就能加载对应的实现,

    /**
    +聊聊 Dubbo 的 SPI | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊聊 Dubbo 的 SPI

    SPI全称是Service Provider Interface,咋眼看跟api是不是有点相似,api是application interface,这两个其实在某些方面有类似的地方,也有蛮大的区别,比如我们基于 dubbo 的微服务,一般我们可以提供服务,然后非泛化调用的话,我们可以把 api 包提供给应用调用方,他们根据接口签名传对应参数并配置好对应的服务发现如 zk 等就可以调用我们的服务了,然后 spi 会有点类似但是是反过来的关系,相当于是一种规范,比如我约定完成这个功能需要两个有两个接口,一个是连接的,一个是断开的,其实就可以用 jdbc 的驱动举例,比较老套了,然后各个厂家去做具体的实现吧,到时候根据我接口的全限定名的文件来加载实际的实现类,然后运行的时候调用对应实现类的方法就完了

    3sKdpg

    看上面的图,java.sql.Driver就是 spi,对应在classpath 的 META-INF/services 目录下的这个文件,里边的内容就是具体的实现类

    1590735097909

    简单介绍了 Java的 SPI,再来说说 dubbo 的,dubbo 中为啥要用 SPI 呢,主要是为了框架的可扩展性和性能方面的考虑,比如协议层 dubbo 默认使用 dubbo 协议,同时也支持很多其他协议,也支持用户自己实现协议,那么跟 Java 的 SPI 会有什么区别呢,我们也来看个文件

    bqxWMp

    是不是看着很想,又有点不一样,在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名,在 Dubbo的 SPI配置文件中是 key=value 的形式,我们只需要对应的 key 就能加载对应的实现,

    /**
          * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}.
          *
          * @param name
    diff --git a/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/index.html b/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/index.html
    index 969df0f8da..2351ac7182 100644
    --- a/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/index.html
    +++ b/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/index.html
    @@ -1,4 +1,4 @@
    -聊聊 Dubbo 的 SPI 续之自适应拓展 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊聊 Dubbo 的 SPI 续之自适应拓展

    Adaptive

    这个应该是 Dubbo SPI 里最玄妙的东西了,一开始没懂,自适应扩展点加载,
    dubbo://123.123.123.123:1234/com.nicksxs.demo.service.HelloWorldService?anyhost=true&application=demo&default.loadbalance=random&default.service.filter=LoggerFilter&dubbo=2.5.3&interface=com.nicksxs.demo.service.HelloWorldService&logger=slf4j&methods=method1,method2,method3,method4&pid=4292&retries=0&side=provider&threadpool=fixed&threads=200&timeout=2000&timestamp=1590647155886
    那我从比较能理解的角度或者说思路去讲讲我的理解,因为直接将原理如果脱离了使用,对于我这样的理解能力比较差的可能会比较吃力,从使用场景开始讲可能会比较舒服了,这里可以看到参数里有蛮多的,举个例子,比如这个 threadpool = fixed,说明线程池使用的是 fixed 对应的实现,也就是下图的这个

    这样子似乎没啥问题了,反正就是用dubbo 的 spi 加载嘛,好像没啥问题,其实问题还是存在的,或者说不太优雅,比如要先判断我这个 fixed 对应的实现类是哪个,这里可能就有个 if-else 判断了,但是 dubbo 的开发人员似乎不太想这么做这个事情,

    譬如我们在引用一个服务时,在ReferenceConfig 中的

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    就获取了自适应拓展,

    public T getAdaptiveExtension() {
    +聊聊 Dubbo 的 SPI 续之自适应拓展 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊聊 Dubbo 的 SPI 续之自适应拓展

    Adaptive

    这个应该是 Dubbo SPI 里最玄妙的东西了,一开始没懂,自适应扩展点加载,
    dubbo://123.123.123.123:1234/com.nicksxs.demo.service.HelloWorldService?anyhost=true&application=demo&default.loadbalance=random&default.service.filter=LoggerFilter&dubbo=2.5.3&interface=com.nicksxs.demo.service.HelloWorldService&logger=slf4j&methods=method1,method2,method3,method4&pid=4292&retries=0&side=provider&threadpool=fixed&threads=200&timeout=2000&timestamp=1590647155886
    那我从比较能理解的角度或者说思路去讲讲我的理解,因为直接将原理如果脱离了使用,对于我这样的理解能力比较差的可能会比较吃力,从使用场景开始讲可能会比较舒服了,这里可以看到参数里有蛮多的,举个例子,比如这个 threadpool = fixed,说明线程池使用的是 fixed 对应的实现,也就是下图的这个

    这样子似乎没啥问题了,反正就是用dubbo 的 spi 加载嘛,好像没啥问题,其实问题还是存在的,或者说不太优雅,比如要先判断我这个 fixed 对应的实现类是哪个,这里可能就有个 if-else 判断了,但是 dubbo 的开发人员似乎不太想这么做这个事情,

    譬如我们在引用一个服务时,在ReferenceConfig 中的

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    就获取了自适应拓展,

    public T getAdaptiveExtension() {
             Object instance = cachedAdaptiveInstance.get();
             if (instance == null) {
                 if (createAdaptiveInstanceError == null) {
    diff --git a/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html b/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html
    index bc1ceaefbc..7ba50281c8 100644
    --- a/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html
    +++ b/2020/06/26/聊一下-RocketMQ-的-Consumer/index.html
    @@ -1,4 +1,4 @@
    -聊一下 RocketMQ 的 DefaultMQPushConsumer 源码 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊一下 RocketMQ 的 DefaultMQPushConsumer 源码

    首先看下官方的小 demo

    public static void main(String[] args) throws InterruptedException, MQClientException {
    +聊一下 RocketMQ 的 DefaultMQPushConsumer 源码 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊一下 RocketMQ 的 DefaultMQPushConsumer 源码

    首先看下官方的小 demo

    public static void main(String[] args) throws InterruptedException, MQClientException {
     
             /*
              * Instantiate with specified consumer group name.
    @@ -737,4 +737,4 @@
                     throw new RemotingTimeoutException(info);
                 }
             }
    -    }
    \ No newline at end of file + }
    \ No newline at end of file diff --git a/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html b/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html index ad73660cf5..a36f692966 100644 --- a/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html +++ b/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/index.html @@ -1,4 +1,4 @@ -聊一下 RocketMQ 的 NameServer 源码 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊一下 RocketMQ 的 NameServer 源码

    前面介绍了,nameserver相当于dubbo的注册中心,用与管理broker,broker会在启动的时候注册到nameserver,并且会发送心跳给namaserver,nameserver负责保存活跃的broker,包括master和slave,同时保存topic和topic下的队列,以及filter列表,然后为producer和consumer的请求提供服务。

    启动过程

    public static void main(String[] args) {
    +聊一下 RocketMQ 的 NameServer 源码 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    聊一下 RocketMQ 的 NameServer 源码

    前面介绍了,nameserver相当于dubbo的注册中心,用与管理broker,broker会在启动的时候注册到nameserver,并且会发送心跳给namaserver,nameserver负责保存活跃的broker,包括master和slave,同时保存topic和topic下的队列,以及filter列表,然后为producer和consumer的请求提供服务。

    启动过程

    public static void main(String[] args) {
             main0(args);
         }
     
    @@ -563,4 +563,4 @@
             response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
                 + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
             return response;
    -    }

    首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

    简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

    \ No newline at end of file + }

    首先调用org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#pickupTopicRouteDataorg.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#topicQueueTable获取到org.apache.rocketmq.common.protocol.route.QueueData这里面存了 brokerName,再通过org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#brokerAddrTable里获取到 broker 的地址信息等,然后再获取 orderMessage 的配置。

    简要分析了下 RocketMQ 的 NameServer 的代码,比较粗粒度。

    \ No newline at end of file diff --git a/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html b/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html index 7c63050292..6d1034897b 100644 --- a/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html +++ b/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析

    题目介绍

    给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    说明: 叶子节点是指没有子节点的节点。

    示例:
    给定二叉树 [3,9,20,null,null,15,7],

      3
    +Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析

    题目介绍

    给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    说明: 叶子节点是指没有子节点的节点。

    示例:
    给定二叉树 [3,9,20,null,null,15,7],

      3
      / \
     9  20
       /  \
    @@ -20,4 +20,4 @@
         }
         // 前面返回后,左右取大者
         return Math.max(left + 1, right + 1);
    -}

    分析

    其实对于树这类题,一般是以递归形式比较方便,只是要注意退出条件

    \ No newline at end of file +}

    分析

    其实对于树这类题,一般是以递归形式比较方便,只是要注意退出条件

    \ No newline at end of file diff --git a/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/index.html b/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/index.html index 3d6763df55..a3d29afa06 100644 --- a/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/index.html +++ b/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 234 回文链表(Palindrome Linked List) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 234 回文链表(Palindrome Linked List) 题解分析

    题目介绍

    Given a singly linked list, determine if it is a palindrome.
    给定一个单向链表,判断是否是回文链表

    例一 Example 1:

    Input: 1->2
    Output: false

    例二 Example 2:

    Input: 1->2->2->1
    Output: true

    挑战下自己

    Follow up:
    Could you do it in O(n) time and O(1) space?

    简要分析

    首先这是个单向链表,如果是双向的就可以一个从头到尾,一个从尾到头,显然那样就没啥意思了,然后想过要不找到中点,然后用一个栈,把前一半塞进栈里,但是这种其实也比较麻烦,比如长度是奇偶数,然后如何找到中点,这倒是可以借助于双指针,还是比较麻烦,再想一想,回文链表,就跟最开始的一样,链表只有单向的,我用个栈不就可以逆向了么,先把链表整个塞进栈里,然后在一个个 pop 出来跟链表从头开始比较,全对上了就是回文了

    /**
    +Leetcode 234 回文链表(Palindrome Linked List) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 234 回文链表(Palindrome Linked List) 题解分析

    题目介绍

    Given a singly linked list, determine if it is a palindrome.
    给定一个单向链表,判断是否是回文链表

    例一 Example 1:

    Input: 1->2
    Output: false

    例二 Example 2:

    Input: 1->2->2->1
    Output: true

    挑战下自己

    Follow up:
    Could you do it in O(n) time and O(1) space?

    简要分析

    首先这是个单向链表,如果是双向的就可以一个从头到尾,一个从尾到头,显然那样就没啥意思了,然后想过要不找到中点,然后用一个栈,把前一半塞进栈里,但是这种其实也比较麻烦,比如长度是奇偶数,然后如何找到中点,这倒是可以借助于双指针,还是比较麻烦,再想一想,回文链表,就跟最开始的一样,链表只有单向的,我用个栈不就可以逆向了么,先把链表整个塞进栈里,然后在一个个 pop 出来跟链表从头开始比较,全对上了就是回文了

    /**
      * Definition for singly-linked list.
      * public class ListNode {
      *     int val;
    @@ -31,4 +31,4 @@
             }
             return true;
         }
    -}
    \ No newline at end of file +}
    \ No newline at end of file diff --git a/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html b/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html index 59b09e84d2..9d9adb1dae 100644 --- a/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html +++ b/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析

    题目介绍

    Given preorder and inorder traversal of a tree, construct the binary tree.
    给定一棵树的前序和中序遍历,构造出一棵二叉树

    注意

    You may assume that duplicates do not exist in the tree.
    你可以假设树中没有重复的元素。(PS: 不然就没法做了呀)

    例子:

    preorder = [3,9,20,15,7]
    +Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析

    题目介绍

    Given preorder and inorder traversal of a tree, construct the binary tree.
    给定一棵树的前序和中序遍历,构造出一棵二叉树

    注意

    You may assume that duplicates do not exist in the tree.
    你可以假设树中没有重复的元素。(PS: 不然就没法做了呀)

    例子:

    preorder = [3,9,20,15,7]
     inorder = [9,3,15,20,7]

    返回的二叉树

      3
      / \
     9  20
    @@ -32,4 +32,4 @@ inorder = [9,3,15,20,7]
    \ No newline at end of file +}
    \ No newline at end of file diff --git a/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/index.html b/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/index.html index 4e637a77ce..646c3925a9 100644 --- a/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/index.html +++ b/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析

    题目介绍

    写一个程序找出两个单向链表的交叉起始点,可能是我英语不好,图里画的其实还有一点是交叉以后所有节点都是相同的
    Write a program to find the node at which the intersection of two singly linked lists begins.

    For example, the following two linked lists:

    begin to intersect at node c1.

    Example 1:

    Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
    +Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析

    题目介绍

    写一个程序找出两个单向链表的交叉起始点,可能是我英语不好,图里画的其实还有一点是交叉以后所有节点都是相同的
    Write a program to find the node at which the intersection of two singly linked lists begins.

    For example, the following two linked lists:

    begin to intersect at node c1.

    Example 1:

    Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
     Output: Reference of the node with value = 8
     Input Explanation: The intersected node's value is 8 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [4,1,8,4,5]. From the head of B, it reads as [5,6,1,8,4,5]. There are 2 nodes before the intersected node in A; There are 3 nodes before the intersected node in B.

    分析题解

    一开始没什么头绪,感觉只能最原始的遍历,后来看了一些文章,发现比较简单的方式就是先找两个链表的长度差,因为从相交点开始肯定是长度一致的,这是个很好的解题突破口,找到长度差以后就是先跳过长链表的较长部分,然后开始同步遍历比较 A,B 链表;

    代码

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
             if (headA == null || headB == null) {
    @@ -42,4 +42,4 @@ Input Explanation: The intersected node's value is 8 (note that this must no
                 }
             }
             return null;
    -    }

    总结

    可能缺少这种思维,做的还是比较少,所以没法一下子反应过来,需要锻炼,我的第一反应是两重遍历,不过那样复杂度就高了,这里应该是只有 O(N) 的复杂度。

    \ No newline at end of file + }

    总结

    可能缺少这种思维,做的还是比较少,所以没法一下子反应过来,需要锻炼,我的第一反应是两重遍历,不过那样复杂度就高了,这里应该是只有 O(N) 的复杂度。

    \ No newline at end of file diff --git a/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html b/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html index 79733b27c6..f081e62bde 100644 --- a/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html +++ b/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/index.html @@ -1,4 +1,4 @@ -Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析

    题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    The path sum of a path is the sum of the node’s values in the path.

    Given the root of a binary tree, return the maximum path sum of any path.

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    路径和 是路径中各节点值的总和。

    给你一个二叉树的根节点 root ,返回其 最大路径和

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    代码

    int ansNew = Integer.MIN_VALUE;
    +Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析 | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析

    题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    The path sum of a path is the sum of the node’s values in the path.

    Given the root of a binary tree, return the maximum path sum of any path.

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    路径和 是路径中各节点值的总和。

    给你一个二叉树的根节点 root ,返回其 最大路径和

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    代码

    int ansNew = Integer.MIN_VALUE;
     public int maxPathSum(TreeNode root) {
             maxSumNew(root);
             return ansNew;
    @@ -21,4 +21,4 @@
         int res = Math.max(left + right + root.val, currentSum);
         ans = Math.max(res, ans);
         return currentSum;
    -}

    这里非常重要的就是 ansNew 是最后的一个结果,而对于 maxSumNew 这个函数的返回值其实是需要包含了一个连续结果,因为要返回继续去算路径和,所以返回的是 currentSum,最终结果是 ansNew

    结果图

    难得有个 100%,贴个图哈哈

    \ No newline at end of file +}

    这里非常重要的就是 ansNew 是最后的一个结果,而对于 maxSumNew 这个函数的返回值其实是需要包含了一个连续结果,因为要返回继续去算路径和,所以返回的是 currentSum,最终结果是 ansNew

    结果图

    难得有个 100%,贴个图哈哈

    \ No newline at end of file diff --git a/baidusitemap.xml b/baidusitemap.xml index 2ffc2e7d54..d78858cd61 100644 --- a/baidusitemap.xml +++ b/baidusitemap.xml @@ -89,15 +89,15 @@ 2022-06-11 - https://nicksxs.me/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ + https://nicksxs.me/2021/10/03/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%B8%89/ 2022-06-11 - https://nicksxs.me/2021/10/03/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E4%B8%89/ + https://nicksxs.me/2021/12/12/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/ 2022-06-11 - https://nicksxs.me/2021/12/12/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/ + https://nicksxs.me/2021/09/04/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8/ 2022-06-11 @@ -585,15 +585,15 @@ 2020-01-12 - https://nicksxs.me/2019/12/10/Redis-Part-1/ + https://nicksxs.me/2015/03/11/Reverse-Bits/ 2020-01-12 - https://nicksxs.me/2015/03/11/Reverse-Bits/ + https://nicksxs.me/2015/03/13/Reverse-Integer/ 2020-01-12 - https://nicksxs.me/2015/03/13/Reverse-Integer/ + https://nicksxs.me/2015/01/14/Two-Sum/ 2020-01-12 @@ -601,7 +601,7 @@ 2020-01-12 - https://nicksxs.me/2015/01/14/Two-Sum/ + https://nicksxs.me/2016/08/14/docker-mysql-cluster/ 2020-01-12 @@ -609,11 +609,11 @@ 2020-01-12 - https://nicksxs.me/2016/08/14/docker-mysql-cluster/ + https://nicksxs.me/2016/10/11/minimum-size-subarray-sum-209/ 2020-01-12 - https://nicksxs.me/2016/10/11/minimum-size-subarray-sum-209/ + https://nicksxs.me/2019/12/10/Redis-Part-1/ 2020-01-12 @@ -637,11 +637,11 @@ 2020-01-12 - https://nicksxs.me/2019/09/23/AbstractQueuedSynchronizer/ + https://nicksxs.me/2014/12/30/Clone-Graph-Part-I/ 2020-01-12 - https://nicksxs.me/2014/12/30/Clone-Graph-Part-I/ + https://nicksxs.me/2019/09/23/AbstractQueuedSynchronizer/ 2020-01-12 @@ -649,11 +649,11 @@ 2020-01-12 - https://nicksxs.me/2015/03/11/Number-Of-1-Bits/ + https://nicksxs.me/2015/01/04/Path-Sum/ 2020-01-12 - https://nicksxs.me/2015/01/04/Path-Sum/ + https://nicksxs.me/2015/03/11/Number-Of-1-Bits/ 2020-01-12 diff --git a/categories/Java/GC/index.html b/categories/Java/GC/index.html index c19c169f16..0d3a4f8a70 100644 --- a/categories/Java/GC/index.html +++ b/categories/Java/GC/index.html @@ -1 +1 @@ -分类: gc | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    \ No newline at end of file +分类: gc | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    ,"url":"https://nicksxs.me/categories/Java/GC/"} \ No newline at end of file diff --git a/categories/Java/index.html b/categories/Java/index.html index f6804b2988..0c25ec0cfa 100644 --- a/categories/Java/index.html +++ b/categories/Java/index.html @@ -1 +1 @@ -分类: java | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    \ No newline at end of file +分类: java | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    js/third-party/fancybox.js"> \ No newline at end of file diff --git a/categories/Redis/index.html b/categories/Redis/index.html index cfc0e1a5ea..738f8e7944 100644 --- a/categories/Redis/index.html +++ b/categories/Redis/index.html @@ -1 +1 @@ -分类: redis | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    \ No newline at end of file +分类: redis | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    xo-generator-searchdb/1.4.0/search.js" integrity="sha256-vXZMYLEqsROAXkEw93GGIvaB2ab+QW6w3+1ahD9nXXA=" crossorigin="anonymous"> \ No newline at end of file diff --git a/categories/leetcode/java/linked-list/index.html b/categories/leetcode/java/linked-list/index.html index 1c9cf0f6ab..c2025bbf3d 100644 --- a/categories/leetcode/java/linked-list/index.html +++ b/categories/leetcode/java/linked-list/index.html @@ -1 +1 @@ -分类: linked list | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    linked list 分类

    2020
    \ No newline at end of file +分类: linked list | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    linked list 分类

    2020
    ript> \ No newline at end of file diff --git a/categories/linked-list/index.html b/categories/linked-list/index.html index e2aaa09d62..41593dac42 100644 --- a/categories/linked-list/index.html +++ b/categories/linked-list/index.html @@ -1 +1 @@ -分类: linked list | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    linked list 分类

    2020
    \ No newline at end of file +分类: linked list | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%

    linked list 分类

    2020
    ytics.js"> \ No newline at end of file diff --git a/categories/php/index.html b/categories/php/index.html index 33611c96ea..b4bb22c6d9 100644 --- a/categories/php/index.html +++ b/categories/php/index.html @@ -1 +1 @@ -分类: PHP | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    \ No newline at end of file +分类: PHP | Nicksxs's Blog

    Nicksxs's Blog

    What hurts more, the pain of hard work or the pain of regret?

    0%
    w","server_url":"https://leancloud.cn","security":true} \ No newline at end of file diff --git a/code/Solution278.java b/code/Solution278.java new file mode 100644 index 0000000000..afa88d43d7 --- /dev/null +++ b/code/Solution278.java @@ -0,0 +1,22 @@ +public class Solution278 { + + public int firstBadVersion(int n) { + // 类似于双指针法 + int left = 1, right = n, mid; + while (left < right) { + // 取中点 + mid = left + (right - left) / 2; + // 如果不是错误版本,就往右找 + if (!isBadVersion(mid)) { + left = mid + 1; + } else { + // 如果是的话就往左查找 + right = mid; + } + } + // 这里考虑交界情况是,在上面循环中如果 left 是好的,right 是坏的,那进入循环的时候 mid == left + // 然后 left = mid + 1 就会等于 right,循环条件就跳出了,此时 left 就是那个起始的错误点了 + // 其实这两个是同一个值 + return left; + } +} diff --git a/code/Solution885.java b/code/Solution885.java index 713fcb49e7..6d930cacfc 100644 --- a/code/Solution885.java +++ b/code/Solution885.java @@ -1,44 +1,45 @@ -public lass Solution885 { - public int[][] spiralMatrixIII(int rows, int cols, int rStart, int cStart) { - int size = rows * cols; - int x = rStart, y = cStart; - // 返回的二维矩阵 - int[][] matrix = new int[size][2]; - // 传入的参数就是入口第一个 - matrix[0][0] = rStart; - matrix[0][1] = cStart; - // 作为数量 - int z = 1; - // 步进,1,1,2,2,3,3,4 ... 螺旋矩阵的增长 - int a = 1; - // 方向 1 表示右,2 表示下,3 表示左,4 表示上 - int dir = 1; - while (z < size) { - for (int i = 0; i < 2; i++) { - for (int j= 0; j < a; j++) { - // 处理方向 - if (dir % 4 == 1) { - y++; - } else if (dir % 4 == 2) { - x++; - } else if (dir % 4 == 3) { - y--; - } else { - x--; - } - // 如果在实际矩阵内 - if (x < rows && y < cols && x >= 0 && y >= 0) { - matrix[z][0] = x; - matrix[z][1] = y; - z++; - } - } - // 转变方向 - dir++; - } - // 步进++ - a++; +public class Solution885 { + + public int[][] spiralMatrixIII(int rows, int cols, int rStart, int cStart) { + int size = rows * cols; + int x = rStart, y = cStart; + // 返回的二维矩阵 + int[][] matrix = new int[size][2]; + // 传入的参数就是入口第一个 + matrix[0][0] = rStart; + matrix[0][1] = cStart; + // 作为数量 + int z = 1; + // 步进,1,1,2,2,3,3,4 ... 螺旋矩阵的增长 + int a = 1; + // 方向 1 表示右,2 表示下,3 表示左,4 表示上 + int dir = 1; + while (z < size) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < a; j++) { + // 处理方向 + if (dir % 4 == 1) { + y++; + } else if (dir % 4 == 2) { + x++; + } else if (dir % 4 == 3) { + y--; + } else { + x--; + } + // 如果在实际矩阵内 + if (x < rows && y < cols && x >= 0 && y >= 0) { + matrix[z][0] = x; + matrix[z][1] = y; + z++; + } } - return matrix; + // 转变方向 + dir++; + } + // 步进++ + a++; } -} \ No newline at end of file + return matrix; + } +} diff --git a/leancloud.memo b/leancloud.memo index 2ca4ed10b9..1178db97c4 100644 --- a/leancloud.memo +++ b/leancloud.memo @@ -175,4 +175,5 @@ {"title":"Leetcode 16 最接近的三数之和 ( 3Sum Closest *Medium* ) 题解分析","url":"/2022/08/06/Leetcode-16-最接近的三数之和-3Sum-Closest-Medium-题解分析/"}, {"title":"Leetcode 278 第一个错误的版本 ( First Bad Version *Easy* ) 题解分析","url":"/2022/08/14/Leetcode-278-第一个错误的版本-First-Bad-Version-Easy-题解分析/"}, {"title":"一个 nginx 的简单记忆点","url":"/2022/08/21/一个-nginx-的简单记忆点/"}, +{"title":"Leetcode 885 螺旋矩阵 III ( Spiral Matrix III *Medium* ) 题解分析","url":"/2022/08/23/Leetcode-885-螺旋矩阵-III-Spiral-Matrix-III-Medium-题解分析/"}, ] \ No newline at end of file diff --git a/leancloud_counter_security_urls.json b/leancloud_counter_security_urls.json index 8693287271..fd8245333b 100644 --- a/leancloud_counter_security_urls.json +++ b/leancloud_counter_security_urls.json @@ -1 +1 @@ -[{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"title":"Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析","url":"/2022/07/22/Leetcode-1260-二维网格迁移-Shift-2D-Grid-Easy-题解分析/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 16 最接近的三数之和 ( 3Sum Closest *Medium* ) 题解分析","url":"/2022/08/06/Leetcode-16-最接近的三数之和-3Sum-Closest-Medium-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 20 有效的括号 ( Valid Parentheses *Easy* ) 题解分析","url":"/2022/07/02/Leetcode-20-有效的括号-Valid-Parentheses-Easy-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 278 第一个错误的版本 ( First Bad Version *Easy* ) 题解分析","url":"/2022/08/14/Leetcode-278-第一个错误的版本-First-Bad-Version-Easy-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Leetcode 698 划分为k个相等的子集 ( Partition to K Equal Sum Subsets *Medium* ) 题解分析","url":"/2022/06/19/Leetcode-698-划分为k个相等的子集-Partition-to-K-Equal-Sum-Subsets-Medium-题解分析/"},{"title":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"Leetcode 885 螺旋矩阵 III ( Spiral Matrix III *Medium* ) 题解分析","url":"/2022/08/23/Leetcode-885-螺旋矩阵-III-Spiral-Matrix-III-Medium-题解分析/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"dubbo 客户端配置的一个重要知识点","url":"/2022/06/11/dubbo-客户端配置的一个重要知识点/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"mybatis 的 foreach 使用的注意点","url":"/2022/07/09/mybatis-的-foreach-使用的注意点/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"redis 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"《长安的荔枝》读后感","url":"/2022/07/17/《长安的荔枝》读后感/"},{"title":"一个 nginx 的简单记忆点","url":"/2022/08/21/一个-nginx-的简单记忆点/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"是何原因竟让两人深夜奔袭十公里","url":"/2022/06/05/是何原因竟让两人深夜奔袭十公里/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"聊一下 SpringBoot 设置非 web 应用的方法","url":"/2022/07/31/聊一下-SpringBoot-设置非-web-应用的方法/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊我的远程工作体验","url":"/2022/06/26/聊聊我的远程工作体验/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"}] \ No newline at end of file +[{"title":"2020 年终总结","url":"/2021/03/31/2020-年终总结/"},{"title":"2019年终总结","url":"/2020/02/01/2019年终总结/"},{"title":"村上春树《1Q84》读后感","url":"/2019/12/18/1Q84读后感/"},{"title":"2020年中总结","url":"/2020/07/11/2020年中总结/"},{"title":"34_Search_for_a_Range","url":"/2016/08/14/34-Search-for-a-Range/"},{"title":"AQS篇二 之 Condition 浅析笔记","url":"/2021/02/21/AQS-之-Condition-浅析笔记/"},{"title":"2021 年中总结","url":"/2021/07/18/2021-年中总结/"},{"title":"add-two-number","url":"/2015/04/14/Add-Two-Number/"},{"title":"Apollo 的 value 注解是怎么自动更新的","url":"/2020/11/01/Apollo-的-value-注解是怎么自动更新的/"},{"title":"Clone Graph Part I","url":"/2014/12/30/Clone-Graph-Part-I/"},{"title":"AQS篇一","url":"/2021/02/14/AQS篇一/"},{"title":"Comparator使用小记","url":"/2020/04/05/Comparator使用小记/"},{"title":"AbstractQueuedSynchronizer","url":"/2019/09/23/AbstractQueuedSynchronizer/"},{"title":"2021 年终总结","url":"/2022/01/22/2021-年终总结/"},{"title":"Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?","url":"/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/"},{"title":"G1收集器概述","url":"/2020/02/09/G1收集器概述/"},{"title":"JVM源码分析之G1垃圾收集器分析一","url":"/2019/12/07/JVM-G1-Part-1/"},{"title":"Disruptor 系列二","url":"/2022/02/27/Disruptor-系列二/"},{"title":"Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析","url":"/2021/10/31/Leetcode-028-实现-strStr-Implement-strStr-题解分析/"},{"title":"Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析","url":"/2021/11/28/Leetcode-053-最大子序和-Maximum-Subarray-题解分析/"},{"title":"Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析","url":"/2020/10/25/Leetcode-104-二叉树的最大深度-Maximum-Depth-of-Binary-Tree-题解分析/"},{"title":"Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析","url":"/2020/12/13/Leetcode-105-从前序与中序遍历序列构造二叉树-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal-题解分析/"},{"title":"Dubbo 使用的几个记忆点","url":"/2022/04/02/Dubbo-使用的几个记忆点/"},{"title":"Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析","url":"/2021/10/07/Leetcode-021-合并两个有序链表-Merge-Two-Sorted-Lists-题解分析/"},{"title":"Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析","url":"/2021/01/24/Leetcode-124-二叉树中的最大路径和-Binary-Tree-Maximum-Path-Sum-题解分析/"},{"title":"Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析","url":"/2021/03/14/Leetcode-121-买卖股票的最佳时机-Best-Time-to-Buy-and-Sell-Stock-题解分析/"},{"title":"Leetcode 155 最小栈(Min Stack) 题解分析","url":"/2020/12/06/Leetcode-155-最小栈-Min-Stack-题解分析/"},{"title":"Disruptor 系列一","url":"/2022/02/13/Disruptor-系列一/"},{"title":"Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析","url":"/2021/01/10/Leetcode-160-相交链表-intersection-of-two-linked-lists-题解分析/"},{"title":"Leetcode 2 Add Two Numbers 题解分析","url":"/2020/10/11/Leetcode-2-Add-Two-Numbers-题解分析/"},{"title":"Leetcode 16 最接近的三数之和 ( 3Sum Closest *Medium* ) 题解分析","url":"/2022/08/06/Leetcode-16-最接近的三数之和-3Sum-Closest-Medium-题解分析/"},{"title":"Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析","url":"/2022/05/01/Leetcode-1115-交替打印-FooBar-Print-FooBar-Alternately-Medium-题解分析/"},{"title":"Leetcode 234 回文链表(Palindrome Linked List) 题解分析","url":"/2020/11/15/Leetcode-234-回文联表-Palindrome-Linked-List-题解分析/"},{"title":"Leetcode 236 二叉树的最近公共祖先(Lowest Common Ancestor of a Binary Tree) 题解分析","url":"/2021/05/23/Leetcode-236-二叉树的最近公共祖先-Lowest-Common-Ancestor-of-a-Binary-Tree-题解分析/"},{"title":"Leetcode 278 第一个错误的版本 ( First Bad Version *Easy* ) 题解分析","url":"/2022/08/14/Leetcode-278-第一个错误的版本-First-Bad-Version-Easy-题解分析/"},{"title":"Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析","url":"/2022/07/22/Leetcode-1260-二维网格迁移-Shift-2D-Grid-Easy-题解分析/"},{"title":"Leetcode 3 Longest Substring Without Repeating Characters 题解分析","url":"/2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-题解分析/"},{"title":"Leetcode 42 接雨水 (Trapping Rain Water) 题解分析","url":"/2021/07/04/Leetcode-42-接雨水-Trapping-Rain-Water-题解分析/"},{"title":"Leetcode 48 旋转图像(Rotate Image) 题解分析","url":"/2021/05/01/Leetcode-48-旋转图像-Rotate-Image-题解分析/"},{"title":"Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析","url":"/2022/03/27/Leetcode-4-寻找两个正序数组的中位数-Median-of-Two-Sorted-Arrays-Hard-题解分析/"},{"title":"Leetcode 698 划分为k个相等的子集 ( Partition to K Equal Sum Subsets *Medium* ) 题解分析","url":"/2022/06/19/Leetcode-698-划分为k个相等的子集-Partition-to-K-Equal-Sum-Subsets-Medium-题解分析/"},{"title":"Leetcode 20 有效的括号 ( Valid Parentheses *Easy* ) 题解分析","url":"/2022/07/02/Leetcode-20-有效的括号-Valid-Parentheses-Easy-题解分析/"},{"title":"Leetcode 885 螺旋矩阵 III ( Spiral Matrix III *Medium* ) 题解分析","url":"/2022/08/23/Leetcode-885-螺旋矩阵-III-Spiral-Matrix-III-Medium-题解分析/"},{"title":"leetcode no.3","url":"/2015/04/15/Leetcode-No-3/"},{"title":"Linux 下 grep 命令的一点小技巧","url":"/2020/08/06/Linux-下-grep-命令的一点小技巧/"},{"title":"MFC 模态对话框","url":"/2014/12/24/MFC 模态对话框/"},{"title":"Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析","url":"/2022/03/07/Leetcode-349-两个数组的交集-Intersection-of-Two-Arrays-Easy-题解分析/"},{"title":"Path Sum","url":"/2015/01/04/Path-Sum/"},{"title":"Maven实用小技巧","url":"/2020/02/16/Maven实用小技巧/"},{"title":"Reverse Bits","url":"/2015/03/11/Reverse-Bits/"},{"title":"Reverse Integer","url":"/2015/03/13/Reverse-Integer/"},{"title":"two sum","url":"/2015/01/14/Two-Sum/"},{"title":"ambari-summary","url":"/2017/05/09/ambari-summary/"},{"title":"Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析","url":"/2022/03/13/Leetcode-83-删除排序链表中的重复元素-Remove-Duplicates-from-Sorted-List-Easy-题解分析/"},{"title":"docker-mysql-cluster","url":"/2016/08/14/docker-mysql-cluster/"},{"title":"binary-watch","url":"/2016/09/29/binary-watch/"},{"title":"docker比一般多一点的初学者介绍","url":"/2020/03/08/docker比一般多一点的初学者介绍/"},{"title":"docker比一般多一点的初学者介绍三","url":"/2020/03/21/docker比一般多一点的初学者介绍三/"},{"title":"docker比一般多一点的初学者介绍二","url":"/2020/03/15/docker比一般多一点的初学者介绍二/"},{"title":"docker使用中发现的echo命令的一个小技巧及其他","url":"/2020/03/29/echo命令的一个小技巧/"},{"title":"Number of 1 Bits","url":"/2015/03/11/Number-Of-1-Bits/"},{"title":"gogs使用webhook部署react单页应用","url":"/2020/02/22/gogs使用webhook部署react单页应用/"},{"title":"invert-binary-tree","url":"/2015/06/22/invert-binary-tree/"},{"title":"minimum-size-subarray-sum-209","url":"/2016/10/11/minimum-size-subarray-sum-209/"},{"title":"C++ 指针使用中的一个小问题","url":"/2014/12/23/my-new-post/"},{"title":"mybatis 的 $ 和 # 是有啥区别","url":"/2020/09/06/mybatis-的-和-是有啥区别/"},{"title":"Redis_分布式锁","url":"/2019/12/10/Redis-Part-1/"},{"title":"mybatis 的缓存是怎么回事","url":"/2020/10/03/mybatis-的缓存是怎么回事/"},{"title":"openresty","url":"/2019/06/18/openresty/"},{"title":"pcre-intro-and-a-simple-package","url":"/2015/01/16/pcre-intro-and-a-simple-package/"},{"title":"php-abstract-class-and-interface","url":"/2016/11/10/php-abstract-class-and-interface/"},{"title":"rabbitmq-tips","url":"/2017/04/25/rabbitmq-tips/"},{"title":"mybatis 的 foreach 使用的注意点","url":"/2022/07/09/mybatis-的-foreach-使用的注意点/"},{"title":"redis 的 rdb 和 COW 介绍","url":"/2021/08/15/redis-的-rdb-和-COW-介绍/"},{"title":"redis数据结构介绍-第一部分 SDS,链表,字典","url":"/2019/12/26/redis数据结构介绍/"},{"title":"redis数据结构介绍三-第三部分 整数集合","url":"/2020/01/10/redis数据结构介绍三/"},{"title":"redis数据结构介绍五-第五部分 对象","url":"/2020/01/20/redis数据结构介绍五/"},{"title":"redis数据结构介绍二-第二部分 跳表","url":"/2020/01/04/redis数据结构介绍二/"},{"title":"redis数据结构介绍六 快表","url":"/2020/01/22/redis数据结构介绍六/"},{"title":"redis淘汰策略复习","url":"/2021/08/01/redis淘汰策略复习/"},{"title":"redis数据结构介绍四-第四部分 压缩表","url":"/2020/01/19/redis数据结构介绍四/"},{"title":"redis系列介绍七-过期策略","url":"/2020/04/12/redis系列介绍七/"},{"title":"redis过期策略复习","url":"/2021/07/25/redis过期策略复习/"},{"title":"redis系列介绍八-淘汰策略","url":"/2020/04/18/redis系列介绍八/"},{"title":"rust学习笔记-所有权二","url":"/2021/04/18/rust学习笔记-所有权二/"},{"title":"rust学习笔记-所有权一","url":"/2021/04/18/rust学习笔记/"},{"title":"rust学习笔记-所有权三之切片","url":"/2021/05/16/rust学习笔记-所有权三之切片/"},{"title":"spark-little-tips","url":"/2017/03/28/spark-little-tips/"},{"title":"summary-ranges-228","url":"/2016/10/12/summary-ranges-228/"},{"title":"spring event 介绍","url":"/2022/01/30/spring-event-介绍/"},{"title":"swoole-websocket-test","url":"/2016/07/13/swoole-websocket-test/"},{"title":"《垃圾回收算法手册读书》笔记之整理算法","url":"/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/"},{"title":"wordpress 忘记密码的一种解决方法","url":"/2021/12/05/wordpress-忘记密码的一种解决方法/"},{"title":"dubbo 客户端配置的一个重要知识点","url":"/2022/06/11/dubbo-客户端配置的一个重要知识点/"},{"title":"nginx 日志小记","url":"/2022/04/17/nginx-日志小记/"},{"title":"介绍一下 RocketMQ","url":"/2020/06/21/介绍一下-RocketMQ/"},{"title":"介绍下最近比较实用的端口转发","url":"/2021/11/14/介绍下最近比较实用的端口转发/"},{"title":"从丁仲礼被美国制裁聊点啥","url":"/2020/12/20/从丁仲礼被美国制裁聊点啥/"},{"title":"从清华美院学姐聊聊我们身边的恶人","url":"/2020/11/29/从清华美院学姐聊聊我们身边的恶人/"},{"title":"关于公共交通再吐个槽","url":"/2021/03/21/关于公共交通再吐个槽/"},{"title":"关于读书打卡与分享","url":"/2021/02/07/关于读书打卡与分享/"},{"title":"上次的其他 外行聊国足","url":"/2022/03/06/上次的其他-外行聊国足/"},{"title":"一个 nginx 的简单记忆点","url":"/2022/08/21/一个-nginx-的简单记忆点/"},{"title":"周末我在老丈人家打了天小工","url":"/2020/08/16/周末我在老丈人家打了天小工/"},{"title":"在老丈人家的小工记三","url":"/2020/09/13/在老丈人家的小工记三/"},{"title":"在老丈人家的小工记五","url":"/2020/10/18/在老丈人家的小工记五/"},{"title":"《长安的荔枝》读后感","url":"/2022/07/17/《长安的荔枝》读后感/"},{"title":"分享记录一下一个 git 操作方法","url":"/2022/02/06/分享记录一下一个-git-操作方法/"},{"title":"寄生虫观后感","url":"/2020/03/01/寄生虫观后感/"},{"title":"我是如何走上跑步这条不归路的","url":"/2020/07/26/我是如何走上跑步这条不归路的/"},{"title":"分享记录一下一个 scp 操作方法","url":"/2022/02/06/分享记录一下一个-scp-操作方法/"},{"title":"在老丈人家的小工记四","url":"/2020/09/26/在老丈人家的小工记四/"},{"title":"看完了扫黑风暴,聊聊感想","url":"/2021/10/24/看完了扫黑风暴-聊聊感想/"},{"title":"搬运两个 StackOverflow 上的 Mysql 编码相关的问题解答","url":"/2022/01/16/搬运两个-StackOverflow-上的-Mysql-编码相关的问题解答/"},{"title":"是何原因竟让两人深夜奔袭十公里","url":"/2022/06/05/是何原因竟让两人深夜奔袭十公里/"},{"title":"聊一下 RocketMQ 的 DefaultMQPushConsumer 源码","url":"/2020/06/26/聊一下-RocketMQ-的-Consumer/"},{"title":"聊一下 RocketMQ 的消息存储三","url":"/2021/10/03/聊一下-RocketMQ-的消息存储三/"},{"title":"聊一下 RocketMQ 的 NameServer 源码","url":"/2020/07/05/聊一下-RocketMQ-的-NameServer-源码/"},{"title":"聊一下 RocketMQ 的消息存储二","url":"/2021/09/12/聊一下-RocketMQ-的消息存储二/"},{"title":"聊一下 RocketMQ 的顺序消息","url":"/2021/08/29/聊一下-RocketMQ-的顺序消息/"},{"title":"给小电驴上牌","url":"/2022/03/20/给小电驴上牌/"},{"title":"聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点","url":"/2021/09/19/聊一下-SpringBoot-中使用的-cglib-作为动态代理中的一个注意点/"},{"title":"屯菜惊魂记","url":"/2022/04/24/屯菜惊魂记/"},{"title":"聊一下 RocketMQ 的消息存储四","url":"/2021/10/17/聊一下-RocketMQ-的消息存储四/"},{"title":"聊在东京奥运会闭幕式这天-二","url":"/2021/08/19/聊在东京奥运会闭幕式这天-二/"},{"title":"聊一下 SpringBoot 中动态切换数据源的方法","url":"/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/"},{"title":"聊在东京奥运会闭幕式这天","url":"/2021/08/08/聊在东京奥运会闭幕式这天/"},{"title":"聊聊 Dubbo 的容错机制","url":"/2020/11/22/聊聊-Dubbo-的容错机制/"},{"title":"聊聊 Dubbo 的 SPI 续之自适应拓展","url":"/2020/06/06/聊聊-Dubbo-的-SPI-续之自适应拓展/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字-二","url":"/2021/06/27/聊聊-Java-中绕不开的-Synchronized-关键字-二/"},{"title":"聊聊 Java 的 equals 和 hashCode 方法","url":"/2021/01/03/聊聊-Java-的-equals-和-hashCode-方法/"},{"title":"聊聊 Dubbo 的 SPI","url":"/2020/05/31/聊聊-Dubbo-的-SPI/"},{"title":"聊聊 Java 的类加载机制一","url":"/2020/11/08/聊聊-Java-的类加载机制/"},{"title":"聊聊 Java 的类加载机制二","url":"/2021/06/13/聊聊-Java-的类加载机制二/"},{"title":"聊聊 Java 自带的那些*逆天*工具","url":"/2020/08/02/聊聊-Java-自带的那些逆天工具/"},{"title":"聊聊 Linux 下的 top 命令","url":"/2021/03/28/聊聊-Linux-下的-top-命令/"},{"title":"聊聊 RocketMQ 的 Broker 源码","url":"/2020/07/19/聊聊-RocketMQ-的-Broker-源码/"},{"title":"聊聊 Sharding-Jdbc 的简单使用","url":"/2021/12/12/聊聊-Sharding-Jdbc-的简单使用/"},{"title":"聊聊 Java 中绕不开的 Synchronized 关键字","url":"/2021/06/20/聊聊-Java-中绕不开的-Synchronized-关键字/"},{"title":"聊聊 dubbo 的线程池","url":"/2021/04/04/聊聊-dubbo-的线程池/"},{"title":"聊聊 mysql 的 MVCC 续篇","url":"/2020/05/02/聊聊-mysql-的-MVCC-续篇/"},{"title":"聊聊 mysql 的 MVCC 续续篇之锁分析","url":"/2020/05/10/聊聊-mysql-的-MVCC-续续篇之加锁分析/"},{"title":"聊聊 mysql 的 MVCC","url":"/2020/04/26/聊聊-mysql-的-MVCC/"},{"title":"聊聊 mysql 索引的一些细节","url":"/2020/12/27/聊聊-mysql-索引的一些细节/"},{"title":"聊一下 SpringBoot 设置非 web 应用的方法","url":"/2022/07/31/聊一下-SpringBoot-设置非-web-应用的方法/"},{"title":"聊聊 redis 缓存的应用问题","url":"/2021/01/31/聊聊-redis-缓存的应用问题/"},{"title":"聊聊 SpringBoot 自动装配","url":"/2021/07/11/聊聊SpringBoot-自动装配/"},{"title":"聊聊一次 brew update 引发的血案","url":"/2020/06/13/聊聊一次-brew-update-引发的血案/"},{"title":"聊聊Java中的单例模式","url":"/2019/12/21/聊聊Java中的单例模式/"},{"title":"聊聊传说中的 ThreadLocal","url":"/2021/05/30/聊聊传说中的-ThreadLocal/"},{"title":"聊聊如何识别和意识到日常生活中的各类危险","url":"/2021/06/06/聊聊如何识别和意识到日常生活中的各类危险/"},{"title":"聊聊厦门旅游的好与不好","url":"/2021/04/11/聊聊厦门旅游的好与不好/"},{"title":"聊聊我刚学会的应用诊断方法","url":"/2020/05/22/聊聊我刚学会的应用诊断方法/"},{"title":"聊聊我理解的分布式事务","url":"/2020/05/17/聊聊我理解的分布式事务/"},{"title":"聊聊最近平淡的生活之又聊通勤","url":"/2021/11/07/聊聊最近平淡的生活/"},{"title":"聊聊 Sharding-Jdbc 分库分表下的分页方案","url":"/2022/01/09/聊聊-Sharding-Jdbc-分库分表下的分页方案/"},{"title":"聊聊最近平淡的生活之看《神探狄仁杰》","url":"/2021/12/19/聊聊最近平淡的生活之看《神探狄仁杰》/"},{"title":"聊聊最近平淡的生活之看看老剧","url":"/2021/11/21/聊聊最近平淡的生活之看看老剧/"},{"title":"聊一下 RocketMQ 的消息存储之 MMAP","url":"/2021/09/04/聊一下-RocketMQ-的消息存储/"},{"title":"聊聊最近平淡的生活之《花束般的恋爱》观后感","url":"/2021/12/31/聊聊最近平淡的生活之《花束般的恋爱》观后感/"},{"title":"聊聊给亲戚朋友的老电脑重装系统那些事儿","url":"/2021/05/09/聊聊给亲戚朋友的老电脑重装系统那些事儿/"},{"title":"聊聊 Sharding-Jdbc 的简单原理初篇","url":"/2021/12/26/聊聊-Sharding-Jdbc-的简单原理初篇/"},{"title":"聊聊那些加塞狗","url":"/2021/01/17/聊聊那些加塞狗/"},{"title":"聊聊部分公交车的设计bug","url":"/2021/12/05/聊聊部分公交车的设计bug/"},{"title":"聊聊我的远程工作体验","url":"/2022/06/26/聊聊我的远程工作体验/"},{"title":"记录下 zookeeper 集群迁移和易错点","url":"/2022/05/29/记录下-zookeeper-集群迁移/"},{"title":"这周末我又在老丈人家打了天小工","url":"/2020/08/30/这周末我又在老丈人家打了天小工/"},{"title":"重看了下《蛮荒记》说说感受","url":"/2021/10/10/重看了下《蛮荒记》说说感受/"},{"title":"闲聊下乘公交的用户体验","url":"/2021/02/28/闲聊下乘公交的用户体验/"},{"title":"记录下 Java Stream 的一些高效操作","url":"/2022/05/15/记录下-Java-Lambda-的一些高效操作/"},{"title":"闲话篇-也算碰到了为老不尊和坏人变老了的典型案例","url":"/2022/05/22/闲话篇-也算碰到了为老不尊和坏人变老了的典型案例/"},{"title":"聊聊这次换车牌及其他","url":"/2022/02/20/聊聊这次换车牌及其他/"},{"title":"闲话篇-路遇神逻辑骑车带娃爹","url":"/2022/05/08/闲话篇-路遇神逻辑骑车带娃爹/"},{"title":"难得的大扫除","url":"/2022/04/10/难得的大扫除/"}] \ No newline at end of file diff --git a/page/25/index.html b/page/25/index.html index 9c3860b813..4e6eac51fe 100644 --- a/page/25/index.html +++ b/page/25/index.html @@ -9,7 +9,7 @@ git checkout master cd $last_dir -}

    对应自己的版本改改版本号就可以了,非常好用。

    Adaptive

    这个应该是 Dubbo SPI 里最玄妙的东西了,一开始没懂,自适应扩展点加载,
    dubbo://123.123.123.123:1234/com.nicksxs.demo.service.HelloWorldService?anyhost=true&application=demo&default.loadbalance=random&default.service.filter=LoggerFilter&dubbo=2.5.3&interface=com.nicksxs.demo.service.HelloWorldService&logger=slf4j&methods=method1,method2,method3,method4&pid=4292&retries=0&side=provider&threadpool=fixed&threads=200&timeout=2000&timestamp=1590647155886
    那我从比较能理解的角度或者说思路去讲讲我的理解,因为直接将原理如果脱离了使用,对于我这样的理解能力比较差的可能会比较吃力,从使用场景开始讲可能会比较舒服了,这里可以看到参数里有蛮多的,举个例子,比如这个 threadpool = fixed,说明线程池使用的是 fixed 对应的实现,也就是下图的这个

    这样子似乎没啥问题了,反正就是用dubbo 的 spi 加载嘛,好像没啥问题,其实问题还是存在的,或者说不太优雅,比如要先判断我这个 fixed 对应的实现类是哪个,这里可能就有个 if-else 判断了,但是 dubbo 的开发人员似乎不太想这么做这个事情,

    譬如我们在引用一个服务时,在ReferenceConfig 中的

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    就获取了自适应拓展,

    public T getAdaptiveExtension() {
    +}

    对应自己的版本改改版本号就可以了,非常好用。

    Adaptive

    这个应该是 Dubbo SPI 里最玄妙的东西了,一开始没懂,自适应扩展点加载,
    dubbo://123.123.123.123:1234/com.nicksxs.demo.service.HelloWorldService?anyhost=true&application=demo&default.loadbalance=random&default.service.filter=LoggerFilter&dubbo=2.5.3&interface=com.nicksxs.demo.service.HelloWorldService&logger=slf4j&methods=method1,method2,method3,method4&pid=4292&retries=0&side=provider&threadpool=fixed&threads=200&timeout=2000&timestamp=1590647155886
    那我从比较能理解的角度或者说思路去讲讲我的理解,因为直接将原理如果脱离了使用,对于我这样的理解能力比较差的可能会比较吃力,从使用场景开始讲可能会比较舒服了,这里可以看到参数里有蛮多的,举个例子,比如这个 threadpool = fixed,说明线程池使用的是 fixed 对应的实现,也就是下图的这个

    这样子似乎没啥问题了,反正就是用dubbo 的 spi 加载嘛,好像没啥问题,其实问题还是存在的,或者说不太优雅,比如要先判断我这个 fixed 对应的实现类是哪个,这里可能就有个 if-else 判断了,但是 dubbo 的开发人员似乎不太想这么做这个事情,

    譬如我们在引用一个服务时,在ReferenceConfig 中的

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    就获取了自适应拓展,

    public T getAdaptiveExtension() {
             Object instance = cachedAdaptiveInstance.get();
             if (instance == null) {
                 if (createAdaptiveInstanceError == null) {
    @@ -123,7 +123,7 @@
             return extension.refer(arg0, arg1);
         }
     }
    -

    SPI全称是Service Provider Interface,咋眼看跟api是不是有点相似,api是application interface,这两个其实在某些方面有类似的地方,也有蛮大的区别,比如我们基于 dubbo 的微服务,一般我们可以提供服务,然后非泛化调用的话,我们可以把 api 包提供给应用调用方,他们根据接口签名传对应参数并配置好对应的服务发现如 zk 等就可以调用我们的服务了,然后 spi 会有点类似但是是反过来的关系,相当于是一种规范,比如我约定完成这个功能需要两个有两个接口,一个是连接的,一个是断开的,其实就可以用 jdbc 的驱动举例,比较老套了,然后各个厂家去做具体的实现吧,到时候根据我接口的全限定名的文件来加载实际的实现类,然后运行的时候调用对应实现类的方法就完了

    3sKdpg

    看上面的图,java.sql.Driver就是 spi,对应在classpath 的 META-INF/services 目录下的这个文件,里边的内容就是具体的实现类

    1590735097909

    简单介绍了 Java的 SPI,再来说说 dubbo 的,dubbo 中为啥要用 SPI 呢,主要是为了框架的可扩展性和性能方面的考虑,比如协议层 dubbo 默认使用 dubbo 协议,同时也支持很多其他协议,也支持用户自己实现协议,那么跟 Java 的 SPI 会有什么区别呢,我们也来看个文件

    bqxWMp

    是不是看着很想,又有点不一样,在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名,在 Dubbo的 SPI配置文件中是 key=value 的形式,我们只需要对应的 key 就能加载对应的实现,

    /**
    +

    SPI全称是Service Provider Interface,咋眼看跟api是不是有点相似,api是application interface,这两个其实在某些方面有类似的地方,也有蛮大的区别,比如我们基于 dubbo 的微服务,一般我们可以提供服务,然后非泛化调用的话,我们可以把 api 包提供给应用调用方,他们根据接口签名传对应参数并配置好对应的服务发现如 zk 等就可以调用我们的服务了,然后 spi 会有点类似但是是反过来的关系,相当于是一种规范,比如我约定完成这个功能需要两个有两个接口,一个是连接的,一个是断开的,其实就可以用 jdbc 的驱动举例,比较老套了,然后各个厂家去做具体的实现吧,到时候根据我接口的全限定名的文件来加载实际的实现类,然后运行的时候调用对应实现类的方法就完了

    3sKdpg

    看上面的图,java.sql.Driver就是 spi,对应在classpath 的 META-INF/services 目录下的这个文件,里边的内容就是具体的实现类

    1590735097909

    简单介绍了 Java的 SPI,再来说说 dubbo 的,dubbo 中为啥要用 SPI 呢,主要是为了框架的可扩展性和性能方面的考虑,比如协议层 dubbo 默认使用 dubbo 协议,同时也支持很多其他协议,也支持用户自己实现协议,那么跟 Java 的 SPI 会有什么区别呢,我们也来看个文件

    bqxWMp

    是不是看着很想,又有点不一样,在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名,在 Dubbo的 SPI配置文件中是 key=value 的形式,我们只需要对应的 key 就能加载对应的实现,

    /**
          * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}.
          *
          * @param name
    diff --git a/search.xml b/search.xml
    index c7cc55b11e..4c8295b756 100644
    --- a/search.xml
    +++ b/search.xml
    @@ -1,23 +1,28 @@
     
     
       
    -    村上春树《1Q84》读后感
    -    /2019/12/18/1Q84%E8%AF%BB%E5%90%8E%E6%84%9F/
    -    看完了村上春树的《1Q84》,这应该是第五本看的他的书了,继 跑步,挪威的森林,刺杀骑士团长,海边的卡夫卡之后,不是其中最长的,好像是海边的卡夫卡还是刺杀骑士团长比较长一点,都是在微信读书上看的,比较方便,最开始在上面看的是高晓松的《鱼羊野史》,不知道为啥取这个名字,但是还是满吸引我的,不过由于去年的种种,没有很多心思把它看完,而且本身的组织形式就是比较松散的,看到哪算哪,其实一些野史部分是我比较喜欢,有些谈到人物的就不太有兴趣,而且类似于大祥哥吃的东西,反正都是哇,怎么这么好吃,嗯,太爱(niu)你(bi)了,高晓松就是这个人是我最喜欢的 xxx 家,我也没去细究过他有没有说重复过,反正是不太爱,后来因为这书还一度对战争史有了浓厚的兴趣,然而事实告诉我,大部头的战争史,其实正史我是真的啃不下去,我可能只对其中 10%的内容感兴趣,不过终于也在今年把它看完了,好像高晓松的晓说也最终季了,貌似其中讲朝鲜战争的还被和谐了,看样子是说出了一些故事(truth)。

    -

    本来只是想把 《1Q84》的读后感写下,现在觉得还是把这篇当成我今年的读书总结吧,不过先从《1Q84》说起。

    -

    严格来讲,这不是很书面化的读后感,可能我想写的也只是像聊天一样的说下我读过的书,包括的技术博客其实也是类似的,以后或许会转变,但是目前水平如此吧,写多了可能会变好,也可能不会。

    -

    开始正文吧,这书有点类似于海边的卡夫卡,一开始是通过两条故事线,穿插着叙述,一条是青豆的,不算是个职业杀手的女杀手,要去解决一个经常家暴的斯文败类,穿着描述得比较性感吧,杀人方式是通过比较长的细针,从脖子后面一个精巧的位置插入,可以造成是未知原因死亡的假象,可能会推断成心梗之类的,这里有个前置的细节,就是青豆是乘坐一辆很高级的出租车,内饰什么的都非常有质感,有点不像一辆出租车,然后车里放了一首比较小众的歌,雅纳切克的《小交响曲》,但是青豆知道它,这跟后面的情节也有些许关系,这是女主人公青豆的出场;相应的男主的出场印象不是太深刻,男主叫天吾,是个不知名的作家,跟一个叫小松的编辑有比较好的关系,虽然天吾还没有拿到比较有分量的奖项,但是小松很看好他,也让他帮忙审校一个新作家奖的投稿文章,虽然天吾自身还没获得过这个奖,天吾还有个正式工作,是当数学老师,天吾在学生时代是个数学天才,但后面有对文学产生了兴趣,文学还不足以养活自己,靠着教课还是能保持温饱;

    -

    接下来是正式故事的起点了,就是小松收到了一部小说投稿,名叫《空气蛹》,是个叫深绘里的女孩子投的稿,小松对他赋予了很高的评价,这里好像记岔了,好像是天吾对这部小说很有好感,但是小松比较怀疑,然后小松看了之后也有了浓厚的兴趣,这里就是开端了,小松想让天吾来重写润色这部《空气蛹》,因为故事本身很有分量,但是描写手法叙事方式等都很拙劣,而天吾正好擅长这个,小松对天吾的评价是,描写技巧无可挑剔,就是故事主体的火花还没际遇迸发,需要一个导火索,这个就可以类比我们程序员,很多比较初中级的程序员主要擅长在原来的代码上修修改改或者给他分配个小功能,比较高级的程序员就需要能做一些项目的架构设计,核心的技术方案设计,以前我也觉得写文档这个比较无聊,但是当一个项目真的比较庞大,复杂的时候,整体和核心部分的架构设计和方案还是需要有文档沉淀的,不然别人不知道没法接受,自己过段时间也会忘记。

    -

    对于小松的这个建议,他的初衷是想搅一搅这个死气沉沉套路颇深的文坛,因为本身《空气蛹》这部小说的内容很吸引人,小松想通过天吾的润色补充让这部小说冲击新人奖,有种恶作剧的意图,天吾对此表示很多担心和顾虑,小松的这个建议其实也是一种文学作假,有两方面的担心,一方面是原作者深绘里是否同意如此操作,一方面是外界如果发现了这个事实会有什么样的后果,但是小松表示不用担心,前一步由小松牵线,让天吾跟原作者深绘里当面沟通这个代写是否被允许,结果当然是被允许了,这里有了对深绘里的初步描写,按我的理解是比较仙的感觉,然后语言沟通有些吃力,或者说有她自己的特色,当面沟通时貌似是让深绘里回去再考虑下,然后后面再由天吾去深绘里寄宿的戎野老师家沟通具体的细节。

    -

    2019年12月18日23:37:19 更新
    去到戎野老师家之后,天吾知道了关于深绘里的一些事情,深绘里的父亲与戎野老师应该是老友,深绘里的父亲在当初成立了一个叫”先驱”的公社,一个独立运行的社会组织,以运营农场作为物资来源,追求更为松散的共同体,即不过分激进地公有制,进行松散的共同生活,承认私有财产,简而言之就是这样一个能稳定存活下来的独立社会组织,但是随着稳定运行,内部的激进派和稳健派开始出现分歧,不可磨合,后来两派就分裂了,深绘里的父亲,深田保留在了稳健派,但是此时其实深田保内心是矛盾的,以为一开始其实是他倡导的独立革命才组织起了这群人,然而现在他又认清了现实社会已经不太相信能通过革命来独立的可能性,后来激进派便开始越加封闭,而且进行军事训练和思想教育,而后这个先驱的激进派别便有了新的名字”黎明”,深绘里也是在此时从先驱逃离来投靠戎野老师
    暂时先写到这,未完待续~

    + 2020 年终总结 + /2021/03/31/2020-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + 拖更原因

    这篇年终总结本来应该在农历过完年就出来的,结果是对没有受疫情影响的春节放假时间空闲情况预估太良好,虽然公司调了几天假,但是因为春节期间疫情状况比较好,本来酒店都不让接待聚餐什么的,后来统统放开,结果就是从初一到初六每天要不就是去亲戚家,要不就是去酒店饭店吃饭,计划很丰满,现实很骨感,时间感觉一下就没了,然后年后感觉有点犯懒了,所以才拖到现在。

    +

    生活-健身跑步

    去年(19 年)的时候跑步突破了 300 公里,然后20 年给自己定了个 400 公里的目标,结果意料之中的没成功,原因可能疫情算一点吧,后面买了跑步机之后,基本周末回家都能跑一下,但是最后还是只跑了300 多公里,总的keep 记录跑量也没超过 1000 公里,所以跑步这个目标还是没成功的,不过还算是比去年多跑一点,这样也算后面好突破点,后面的目标就不定的太高了,每年能比前一年多一点就好,其实跑步已经从一种减肥方式变成一种习惯了,一周一次的跑步已经比较难有效减重了,但是对于保持精力和身体状态还是很有效和重要的,只是对于目前的体重还是要多减下去一些跑步才好,太重了对膝盖负担太大了,可惜还是时间呐,游泳骑车什么的都需要更苛刻的条件和时间,饮食呢控制起来比较难(贪吃
    终于在 3 月底之前跑到了 1000 公里,迟了三个月,不过也总算达到了,只是体重控制还是不行,有试着走走楼梯,但是感觉对膝盖负担比较大,得再想想用什么方式

    +

    +

    技术成长

    一直提不起笔来写这篇年终总结还有个比较大的原因是觉得20 年的成长不如预期,大小目标都没怎么完成,比如深入了解 jvm,是想能有些深入的见解,而不再是某些点的比较片面的理解,系统性的归纳总结也比较少,每个方向或多或少有些看法和理解,但是不全面,一些东西看过了也会忘记,需要温故而知新,比如 AQS 的内容,第一次读其实理解比较浅,后面就强迫自己去读,去写,才有了一些比之前更深入的理解,因为很多文章都是带有作者思路的引导,适不适合自己都要看是否能从他的思路把它看懂,有些就差别很大,这个跟看书也一样,有些书大众一致推荐,一般情况下大多是经典的好的,但是也有可能是不太适合自己的,可能有时候机缘巧合看到的反而让人茅塞顿开,在 todo 里已经积攒了好多的点和面需要去学习实践,一方面是自己懒,一方面是时间也相对偏少,看看 21 年能不能有所提升,加强“时间管理”,哈哈

    +

    技术上主要是看了 mysql 的 mvcc 相关内容,rocketmq 的,redis 的代码,还有 mybatis 等,其实每一个都能写很多,也有很多值得学习的,需要全面系统学习,之前想好好画一个思维导图,将整个技术体系都梳理下,还只做了一点点,方式也有点问题,应该从大到小,而不是深度优先,细节有很多,每一个方面都有自己比较熟悉擅长的,也有不太了解的,可以做一个评分,这个也是亟待改善的,希望今年能完成。

    +

    博客

    博客方面 20 年一年整是写了 53 篇,差不多是一周一篇的节奏,这个还是不错的,虽然博客质量参差不齐,但是这个更新频率还是比较好的,并且也定了个潜规则,可以一周技术一周生活,这样能缓解水文的频率,提高些技术文章的质量,虽然结果并没有好多少,不过感觉还是可以这么坚持的,能提高一些技术文章的质量那就更好了

    ]]>
    生活 - 读后感 - 村上春树 + 年终总结 + 2020 + 年终总结 + 2020 - 读后感 + 生活 + 年终总结 + 2020 + 2021 + 拖更
    @@ -50,28 +55,23 @@ - 2020 年终总结 - /2021/03/31/2020-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 拖更原因

    这篇年终总结本来应该在农历过完年就出来的,结果是对没有受疫情影响的春节放假时间空闲情况预估太良好,虽然公司调了几天假,但是因为春节期间疫情状况比较好,本来酒店都不让接待聚餐什么的,后来统统放开,结果就是从初一到初六每天要不就是去亲戚家,要不就是去酒店饭店吃饭,计划很丰满,现实很骨感,时间感觉一下就没了,然后年后感觉有点犯懒了,所以才拖到现在。

    -

    生活-健身跑步

    去年(19 年)的时候跑步突破了 300 公里,然后20 年给自己定了个 400 公里的目标,结果意料之中的没成功,原因可能疫情算一点吧,后面买了跑步机之后,基本周末回家都能跑一下,但是最后还是只跑了300 多公里,总的keep 记录跑量也没超过 1000 公里,所以跑步这个目标还是没成功的,不过还算是比去年多跑一点,这样也算后面好突破点,后面的目标就不定的太高了,每年能比前一年多一点就好,其实跑步已经从一种减肥方式变成一种习惯了,一周一次的跑步已经比较难有效减重了,但是对于保持精力和身体状态还是很有效和重要的,只是对于目前的体重还是要多减下去一些跑步才好,太重了对膝盖负担太大了,可惜还是时间呐,游泳骑车什么的都需要更苛刻的条件和时间,饮食呢控制起来比较难(贪吃
    终于在 3 月底之前跑到了 1000 公里,迟了三个月,不过也总算达到了,只是体重控制还是不行,有试着走走楼梯,但是感觉对膝盖负担比较大,得再想想用什么方式

    -

    -

    技术成长

    一直提不起笔来写这篇年终总结还有个比较大的原因是觉得20 年的成长不如预期,大小目标都没怎么完成,比如深入了解 jvm,是想能有些深入的见解,而不再是某些点的比较片面的理解,系统性的归纳总结也比较少,每个方向或多或少有些看法和理解,但是不全面,一些东西看过了也会忘记,需要温故而知新,比如 AQS 的内容,第一次读其实理解比较浅,后面就强迫自己去读,去写,才有了一些比之前更深入的理解,因为很多文章都是带有作者思路的引导,适不适合自己都要看是否能从他的思路把它看懂,有些就差别很大,这个跟看书也一样,有些书大众一致推荐,一般情况下大多是经典的好的,但是也有可能是不太适合自己的,可能有时候机缘巧合看到的反而让人茅塞顿开,在 todo 里已经积攒了好多的点和面需要去学习实践,一方面是自己懒,一方面是时间也相对偏少,看看 21 年能不能有所提升,加强“时间管理”,哈哈

    -

    技术上主要是看了 mysql 的 mvcc 相关内容,rocketmq 的,redis 的代码,还有 mybatis 等,其实每一个都能写很多,也有很多值得学习的,需要全面系统学习,之前想好好画一个思维导图,将整个技术体系都梳理下,还只做了一点点,方式也有点问题,应该从大到小,而不是深度优先,细节有很多,每一个方面都有自己比较熟悉擅长的,也有不太了解的,可以做一个评分,这个也是亟待改善的,希望今年能完成。

    -

    博客

    博客方面 20 年一年整是写了 53 篇,差不多是一周一篇的节奏,这个还是不错的,虽然博客质量参差不齐,但是这个更新频率还是比较好的,并且也定了个潜规则,可以一周技术一周生活,这样能缓解水文的频率,提高些技术文章的质量,虽然结果并没有好多少,不过感觉还是可以这么坚持的,能提高一些技术文章的质量那就更好了

    + 村上春树《1Q84》读后感 + /2019/12/18/1Q84%E8%AF%BB%E5%90%8E%E6%84%9F/ + 看完了村上春树的《1Q84》,这应该是第五本看的他的书了,继 跑步,挪威的森林,刺杀骑士团长,海边的卡夫卡之后,不是其中最长的,好像是海边的卡夫卡还是刺杀骑士团长比较长一点,都是在微信读书上看的,比较方便,最开始在上面看的是高晓松的《鱼羊野史》,不知道为啥取这个名字,但是还是满吸引我的,不过由于去年的种种,没有很多心思把它看完,而且本身的组织形式就是比较松散的,看到哪算哪,其实一些野史部分是我比较喜欢,有些谈到人物的就不太有兴趣,而且类似于大祥哥吃的东西,反正都是哇,怎么这么好吃,嗯,太爱(niu)你(bi)了,高晓松就是这个人是我最喜欢的 xxx 家,我也没去细究过他有没有说重复过,反正是不太爱,后来因为这书还一度对战争史有了浓厚的兴趣,然而事实告诉我,大部头的战争史,其实正史我是真的啃不下去,我可能只对其中 10%的内容感兴趣,不过终于也在今年把它看完了,好像高晓松的晓说也最终季了,貌似其中讲朝鲜战争的还被和谐了,看样子是说出了一些故事(truth)。

    +

    本来只是想把 《1Q84》的读后感写下,现在觉得还是把这篇当成我今年的读书总结吧,不过先从《1Q84》说起。

    +

    严格来讲,这不是很书面化的读后感,可能我想写的也只是像聊天一样的说下我读过的书,包括的技术博客其实也是类似的,以后或许会转变,但是目前水平如此吧,写多了可能会变好,也可能不会。

    +

    开始正文吧,这书有点类似于海边的卡夫卡,一开始是通过两条故事线,穿插着叙述,一条是青豆的,不算是个职业杀手的女杀手,要去解决一个经常家暴的斯文败类,穿着描述得比较性感吧,杀人方式是通过比较长的细针,从脖子后面一个精巧的位置插入,可以造成是未知原因死亡的假象,可能会推断成心梗之类的,这里有个前置的细节,就是青豆是乘坐一辆很高级的出租车,内饰什么的都非常有质感,有点不像一辆出租车,然后车里放了一首比较小众的歌,雅纳切克的《小交响曲》,但是青豆知道它,这跟后面的情节也有些许关系,这是女主人公青豆的出场;相应的男主的出场印象不是太深刻,男主叫天吾,是个不知名的作家,跟一个叫小松的编辑有比较好的关系,虽然天吾还没有拿到比较有分量的奖项,但是小松很看好他,也让他帮忙审校一个新作家奖的投稿文章,虽然天吾自身还没获得过这个奖,天吾还有个正式工作,是当数学老师,天吾在学生时代是个数学天才,但后面有对文学产生了兴趣,文学还不足以养活自己,靠着教课还是能保持温饱;

    +

    接下来是正式故事的起点了,就是小松收到了一部小说投稿,名叫《空气蛹》,是个叫深绘里的女孩子投的稿,小松对他赋予了很高的评价,这里好像记岔了,好像是天吾对这部小说很有好感,但是小松比较怀疑,然后小松看了之后也有了浓厚的兴趣,这里就是开端了,小松想让天吾来重写润色这部《空气蛹》,因为故事本身很有分量,但是描写手法叙事方式等都很拙劣,而天吾正好擅长这个,小松对天吾的评价是,描写技巧无可挑剔,就是故事主体的火花还没际遇迸发,需要一个导火索,这个就可以类比我们程序员,很多比较初中级的程序员主要擅长在原来的代码上修修改改或者给他分配个小功能,比较高级的程序员就需要能做一些项目的架构设计,核心的技术方案设计,以前我也觉得写文档这个比较无聊,但是当一个项目真的比较庞大,复杂的时候,整体和核心部分的架构设计和方案还是需要有文档沉淀的,不然别人不知道没法接受,自己过段时间也会忘记。

    +

    对于小松的这个建议,他的初衷是想搅一搅这个死气沉沉套路颇深的文坛,因为本身《空气蛹》这部小说的内容很吸引人,小松想通过天吾的润色补充让这部小说冲击新人奖,有种恶作剧的意图,天吾对此表示很多担心和顾虑,小松的这个建议其实也是一种文学作假,有两方面的担心,一方面是原作者深绘里是否同意如此操作,一方面是外界如果发现了这个事实会有什么样的后果,但是小松表示不用担心,前一步由小松牵线,让天吾跟原作者深绘里当面沟通这个代写是否被允许,结果当然是被允许了,这里有了对深绘里的初步描写,按我的理解是比较仙的感觉,然后语言沟通有些吃力,或者说有她自己的特色,当面沟通时貌似是让深绘里回去再考虑下,然后后面再由天吾去深绘里寄宿的戎野老师家沟通具体的细节。

    +

    2019年12月18日23:37:19 更新
    去到戎野老师家之后,天吾知道了关于深绘里的一些事情,深绘里的父亲与戎野老师应该是老友,深绘里的父亲在当初成立了一个叫”先驱”的公社,一个独立运行的社会组织,以运营农场作为物资来源,追求更为松散的共同体,即不过分激进地公有制,进行松散的共同生活,承认私有财产,简而言之就是这样一个能稳定存活下来的独立社会组织,但是随着稳定运行,内部的激进派和稳健派开始出现分歧,不可磨合,后来两派就分裂了,深绘里的父亲,深田保留在了稳健派,但是此时其实深田保内心是矛盾的,以为一开始其实是他倡导的独立革命才组织起了这群人,然而现在他又认清了现实社会已经不太相信能通过革命来独立的可能性,后来激进派便开始越加封闭,而且进行军事训练和思想教育,而后这个先驱的激进派别便有了新的名字”黎明”,深绘里也是在此时从先驱逃离来投靠戎野老师
    暂时先写到这,未完待续~

    ]]>
    生活 - 年终总结 - 2020 - 年终总结 - 2020 + 读后感 + 村上春树 - 生活 - 年终总结 - 2020 - 2021 - 拖更 + 读后感
    @@ -93,23 +93,6 @@ 年中总结 - - 2021 年终总结 - /2022/01/22/2021-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ - 又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

    -

    工作篇

    工作没什么大变化,有了些微的提升,可能因为是来了之后做了些项目对比公司与来还算是比较重要的,但是技术难度上没有特别突出的点,可能最开始用 openresty+lua 做了个 ab 测的工具,还是让我比较满意的,后面一般都是业务型的需求,今年可能在业务相关的技术逻辑上有了一些深度的了解,而原来一直想做的业务架构升级和通用型技术中间件这样的优化还是停留在想象中,前面说的 ab 测应该算是个半成品,还是没能多走出这一步,得需要多做一些实在的事情,比如轻量级的业务框架,能够对原先不熟悉的业务逻辑,代码逻辑有比较深入的理解,而不是一直都是让特定的同学负责特定的逻辑,很多时候还是在偷懒,习惯以一些简单安全的方案去做事情,在技术上还是要有所追求,还有就是能够在新语言,主要是 rust,swift 这类的能有些小玩具可以做,rust 的话是因为今年看了一本相关的书,后面三分之一其实消化得不好,这本书整体来说是很不错的,只是 rust 本身在所有权这块,还有引用包装等方面是设计得比较难懂,也可能是我基础差,所以还是想在复习下,可以做一个简单的命令行工具这种,然后 swift 是想说可以做点 mac 的小软件,原生的毕竟性能好点,又小。基于 web 做的客户端大部分都是又丑又大,极少数能好看点,但也是很重,起码 7~80M 的大小,原生的估计能除以 10。
    整体的职业规划貌似陷入了比较大的困惑期,在目前公司发展前景不是很大,但是出去貌似也没有比较适合我的机会,总的来说还是杭州比较卷,个人觉得有自己的时间是非常重要的,而且这个不光是用来自我提升的,还是让自己有足够的时间做缓冲,有足够的时间锻炼减肥,时间少的情况下,不光会在仅有的时间里暴饮暴食,还没空锻炼,身体是革命的本钱,现在其实能特别明显地感觉到身体状态下滑,容易疲劳,焦虑。所以是否也许有可能以后要往外企这类的方向去发展。
    工作上其实还是有个不大不小的缺点,就是容易激动,容易焦虑,前一点可能有稍稍地改观,因为工作中的很多现状其实是我个人难以改变的,即使觉得不合理,但是结构在那里,还不如自己放宽心,尽量做好事情就行。第二点的话还是做得比较差,一直以来抗压能力都比较差,跟成长环境,家庭环境都有比较大的关系,而且说实在的特别是父母,基本也没有在这方面给我正向的帮助,比较擅长给我施压,从小就是通过压力让我好好读书,当个乖学生,考个好学校,并没有能真正地理解我的压力,教我或者帮助我解压,只会在那说着不着边际的空话,甚至经常反过来对我施压。还是希望能慢慢解开,这点可能对我身体也有影响,也许需要看一些心理疏导相关的书籍。工作篇暂时到这,后续还有其他篇,未完待续哈哈😀

    -]]>
    - - 生活 - 年终总结 - - - 生活 - 年终总结 - 2021 - 拖更 - -
    34_Search_for_a_Range /2016/08/14/34-Search-for-a-Range/ @@ -654,356 +637,85 @@ public: - AQS篇一 - /2021/02/14/AQS%E7%AF%87%E4%B8%80/ - 很多东西都是时看时新,而且时间长了也会忘,所以再来复习下,也会有一些新的角度看法这次来聊下AQS的内容,主要是这几个点,

    -

    第一个线程

    第一个线程抢到锁了,此时state跟阻塞队列是怎么样的,其实这里是之前没理解对的地方

    -
    /**
    -         * Fair version of tryAcquire.  Don't grant access unless
    -         * recursive call or no waiters or is first.
    -         */
    -        protected final boolean tryAcquire(int acquires) {
    -            final Thread current = Thread.currentThread();
    -            int c = getState();
    -            // 这里如果state还是0说明锁还空着
    -            if (c == 0) {
    -                // 因为是公平锁版本的,先去看下是否阻塞队列里有排着队的
    -                if (!hasQueuedPredecessors() &&
    -                    compareAndSetState(0, acquires)) {
    -                    // 没有排队的,并且state使用cas设置成功的就标记当前占有锁的线程是我
    -                    setExclusiveOwnerThread(current);
    -                    // 然后其实就返回了,包括阻塞队列的head和tail节点和waitStatus都没有设置
    -                    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;
    -        }
    -    }
    + 2021 年中总结 + /2021/07/18/2021-%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + 又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

    +

    这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

    +

    技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

    +

    另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

    +

    还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

    +

    下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

    +]]>
    + + 生活 + 年中总结 + 2021 + + + 生活 + 2021 + 年中总结 + 技术 + 读书 + +
    + + add-two-number + /2015/04/14/Add-Two-Number/ + problem

    You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

    +

    Input:(2 -> 4 -> 3) + (5 -> 6 -> 4)
    Output: 7 -> 0 -> 8

    +

    分析(不用英文装逼了)
    这个代码是抄来的,链接原作是这位大大。

    + +

    一开始没看懂题,后来发现是要进位的,自己写的时候想把长短不同时长的串接到结果
    串的后面,试了下因为进位会有些问题比较难搞定,这样的话就是在其中一个为空的
    时候还是会循环操作,在链表太大的时候可能会有问题,就这样(逃
    原来是有个小错误没发现,改进后的代码也AC了,棒棒哒!

    +

    正确代码

    /**
    + * Definition for singly-linked list.
    + * struct ListNode {
    + *     int val;
    + *     ListNode *next;
    + *     ListNode(int x) : val(x), next(NULL) {}
    + * };
    + */
    +class Solution {
    +public:
    +    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
    +        ListNode dummy(0);
    +        ListNode* p = &dummy;
     
    -

    第二个线程

    当第二个线程进来的时候应该是怎么样,结合代码来看

    -
    /**
    -     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    -     * by invoking at least once {@link #tryAcquire},
    -     * returning on success.  Otherwise the thread is queued, possibly
    -     * repeatedly blocking and unblocking, invoking {@link
    -     * #tryAcquire} until success.  This method can be used
    -     * to implement method {@link Lock#lock}.
    -     *
    -     * @param arg the acquire argument.  This value is conveyed to
    -     *        {@link #tryAcquire} but is otherwise uninterpreted and
    -     *        can represent anything you like.
    -     */
    -    public final void acquire(int arg) {
    -        // 前面第一种情况是tryAcquire直接成功了,这个if判断第一个条件就是false,就不往下执行了
    -        // 如果是第二个线程,第一个条件获取锁不成功,条件判断!tryAcquire(arg) == true,就会走
    -        // acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    -        if (!tryAcquire(arg) &&
    -            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    -            selfInterrupt();
    -    }
    + int cn = 0; + while(l1 || l2){ + int val = cn + (l1 ? l1->val : 0) + (l2 ? l2->val : 0); + cn = val / 10; + val = val % 10; + p->next = new ListNode(val); + p = p->next; + if(l1){ + l1 = l1->next; + } + if(l2){ + l2 = l2->next; + } + } + if(cn != 0){ + p->next = new ListNode(cn); + p = p->next; + } + return dummy.next; + } +};
    -

    然后来看下addWaiter的逻辑

    -
    /**
    -     * Creates and enqueues node for current thread and given mode.
    -     *
    -     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
    -     * @return the new node
    -     */
    -    private Node addWaiter(Node mode) {
    -        // 这里是包装成一个node
    -        Node node = new Node(Thread.currentThread(), mode);
    -        // Try the fast path of enq; backup to full enq on failure
    -        // 最快的方式就是把当前线程的节点放在阻塞队列的最后
    -        Node pred = tail;
    -        // 只有当tail,也就是pred不为空的时候可以直接接上
    -        if (pred != null) {
    -            node.prev = pred;
    -            // 如果这里cas成功了,就直接接上返回了
    -            if (compareAndSetTail(pred, node)) {
    -                pred.next = node;
    -                return node;
    -            }
    -        }
    -        // 不然就会继续走到这里
    -        enq(node);
    -        return node;
    -    }
    - -

    然后就是enq的逻辑了

    -
    /**
    -     * Inserts node into queue, initializing if necessary. See picture above.
    -     * @param node the node to insert
    -     * @return node's predecessor
    -     */
    -    private Node enq(final Node node) {
    -        for (;;) {
    -            // 如果状态没变化的话,tail这时还是null的
    -            Node t = tail;
    -            if (t == null) { // Must initialize
    -                // 这里就会初始化头结点,就是个空节点
    -                if (compareAndSetHead(new Node()))
    -                    // tail也赋值成head
    -                    tail = head;
    -            } else {
    -                // 这里就设置tail了
    -                node.prev = t;
    -                if (compareAndSetTail(t, node)) {
    -                    t.next = node;
    -                    return t;
    -                }
    -            }
    -        }
    -    }
    - -

    所以从这里可以看出来,其实head头结点不是个真实的带有线程的节点,并且不是在第一个线程进来的时候设置的

    -

    解锁

    通过代码来看下

    -
    /**
    -     * Attempts to release this lock.
    -     *
    -     * <p>If the current thread is the holder of this lock then the hold
    -     * count is decremented.  If the hold count is now zero then the lock
    -     * is released.  If the current thread is not the holder of this
    -     * lock then {@link IllegalMonitorStateException} is thrown.
    -     *
    -     * @throws IllegalMonitorStateException if the current thread does not
    -     *         hold this lock
    -     */
    -    public void unlock() {
    -        // 释放锁
    -        sync.release(1);
    -    }
    -/**
    -     * Releases in exclusive mode.  Implemented by unblocking one or
    -     * more threads if {@link #tryRelease} returns true.
    -     * This method can be used to implement method {@link Lock#unlock}.
    -     *
    -     * @param arg the release argument.  This value is conveyed to
    -     *        {@link #tryRelease} but is otherwise uninterpreted and
    -     *        can represent anything you like.
    -     * @return the value returned from {@link #tryRelease}
    -     */
    -    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) {
    -            int c = getState() - releases;
    -            if (Thread.currentThread() != getExclusiveOwnerThread())
    -                throw new IllegalMonitorStateException();
    -            boolean free = false;
    -    		// 判断是否完全释放锁,因为可重入
    -            if (c == 0) {
    -                free = true;
    -                setExclusiveOwnerThread(null);
    -            }
    -            setState(c);
    -            return free;
    -        }
    -// 这段代码和上面的一致,只是为了顺序性,又拷下来看下
    -
    -public final boolean release(int arg) {
    -        // 尝试去释放,如果是完全释放,返回的就是true,否则是false
    -        if (tryRelease(arg)) {
    -            Node h = head;
    -            // 这里判断头结点是否为空以及waitStatus的状态,前面说了head节点其实是
    -            // 在第二个线程进来的时候初始化的,如果是空的话说明没后续节点,并且waitStatus
    -            // 也表示了后续的等待状态
    -            if (h != null && h.waitStatus != 0)
    -                unparkSuccessor(h);
    -            return true;
    -        }
    -        return false;
    -    }
    -
    -/**
    -     * Wakes up node's successor, if one exists.
    -     *
    -     * @param node the node
    -     */
    -// 唤醒后继节点
    -    private void unparkSuccessor(Node node) {
    -        /*
    -         * If status is negative (i.e., possibly needing signal) try
    -         * to clear in anticipation of signalling.  It is OK if this
    -         * fails or if status is changed by waiting thread.
    -         */
    -        int ws = node.waitStatus;
    -        if (ws < 0)
    -            compareAndSetWaitStatus(node, ws, 0);
    -
    -        /*
    -         * Thread to unpark is held in successor, which is normally
    -         * just the next node.  But if cancelled or apparently null,
    -         * traverse backwards from tail to find the actual
    -         * non-cancelled successor.
    -         */
    -        Node s = node.next;
    -        // 如果后继节点是空或者当前节点取消等待了
    -        if (s == null || s.waitStatus > 0) {
    -            s = null;
    -            // 从后往前找,找到非取消的节点,注意这里不是找到就退出,而是一直找到头
    -            // 所以不必担心中间有取消的
    -            for (Node t = tail; t != null && t != node; t = t.prev)
    -                if (t.waitStatus <= 0)
    -                    s = t;
    -        }
    -        if (s != null)
    -            // 将其唤醒
    -            LockSupport.unpark(s.thread);
    -    }
    - - - - -]]>
    - - Java - 并发 - - - java - 并发 - j.u.c - aqs - -
    - - AbstractQueuedSynchronizer - /2019/09/23/AbstractQueuedSynchronizer/ - 最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
    首先 AQS 只要有这些属性

    -
    // 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
    -private transient volatile Node head;
    -
    -// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
    -private transient volatile Node tail;
    -
    -// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
    -// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
    -private volatile int state;
    -
    -// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
    -// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
    -// if (currentThread == getExclusiveOwnerThread()) {state++}
    -private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
    -

    大概了解了 aqs 底层的双向等待队列,
    结构是这样的

    每个 node 里面主要是的代码结构也比较简单

    -
    static final class Node {
    -    // 标识节点当前在共享模式下
    -    static final Node SHARED = new Node();
    -    // 标识节点当前在独占模式下
    -    static final Node EXCLUSIVE = null;
    -
    -    // ======== 下面的几个int常量是给waitStatus用的 ===========
    -    /** waitStatus value to indicate thread has cancelled */
    -    // 代码此线程取消了争抢这个锁
    -    static final int CANCELLED =  1;
    -    /** waitStatus value to indicate successor's thread needs unparking */
    -    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
    -    static final int SIGNAL    = -1;
    -    /** waitStatus value to indicate thread is waiting on condition */
    -    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
    -    static final int CONDITION = -2;
    -    /**
    -     * waitStatus value to indicate the next acquireShared should
    -     * unconditionally propagate
    -     */
    -    // 同样的不分析,略过吧
    -    static final int PROPAGATE = -3;
    -    // =====================================================
    -
    -
    -    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
    -    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
    -    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
    -    volatile int waitStatus;
    -    // 前驱节点的引用
    -    volatile Node prev;
    -    // 后继节点的引用
    -    volatile Node next;
    -    // 这个就是线程本尊
    -    volatile Thread thread;
    -
    -}
    -

    其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
    这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

    -]]>
    - - java - - - java - aqs - -
    - - add-two-number - /2015/04/14/Add-Two-Number/ - problem

    You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

    -

    Input:(2 -> 4 -> 3) + (5 -> 6 -> 4)
    Output: 7 -> 0 -> 8

    -

    分析(不用英文装逼了)
    这个代码是抄来的,链接原作是这位大大。

    - -

    一开始没看懂题,后来发现是要进位的,自己写的时候想把长短不同时长的串接到结果
    串的后面,试了下因为进位会有些问题比较难搞定,这样的话就是在其中一个为空的
    时候还是会循环操作,在链表太大的时候可能会有问题,就这样(逃
    原来是有个小错误没发现,改进后的代码也AC了,棒棒哒!

    -

    正确代码

    /**
    - * Definition for singly-linked list.
    - * struct ListNode {
    - *     int val;
    - *     ListNode *next;
    - *     ListNode(int x) : val(x), next(NULL) {}
    - * };
    - */
    -class Solution {
    -public:
    -    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
    -        ListNode dummy(0);
    -        ListNode* p = &dummy;
    -
    -        int cn = 0;
    -        while(l1 || l2){
    -            int val = cn + (l1 ? l1->val : 0) + (l2 ? l2->val : 0);
    -            cn = val / 10;
    -            val = val % 10;
    -            p->next = new ListNode(val);
    -            p = p->next;
    -            if(l1){
    -                l1 = l1->next;
    -            }
    -            if(l2){
    -                l2 = l2->next;
    -            }
    -        }
    -        if(cn != 0){
    -            p->next = new ListNode(cn);
    -            p = p->next;
    -        }
    -        return dummy.next;
    -    }
    -};
    - -

    失败的代码

    /**
    - * Definition for singly-linked list.
    - * struct ListNode {
    - *     int val;
    - *     ListNode *next;
    - *     ListNode(int x) : val(x), next(NULL) {}
    - * };
    - */
    -class Solution {
    -public:
    -    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
    -        ListNode dummy(0);
    -        ListNode* p = &dummy;
    +

    失败的代码

    /**
    + * Definition for singly-linked list.
    + * struct ListNode {
    + *     int val;
    + *     ListNode *next;
    + *     ListNode(int x) : val(x), next(NULL) {}
    + * };
    + */
    +class Solution {
    +public:
    +    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
    +        ListNode dummy(0);
    +        ListNode* p = &dummy;
     
             int cn = 0;
             int flag = 0;
    @@ -1191,18 +903,246 @@ Node *clone(Node *graph) {
           
       
       
    -    Comparator使用小记
    -    /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/
    -    在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

    -

    比如这样子

    -

    我的对象是Entity

    -
    public class Entity {
    -
    -    private Long id;
    -
    -    private Long sortValue;
    -
    -    public Long getId() {
    +    AQS篇一
    +    /2021/02/14/AQS%E7%AF%87%E4%B8%80/
    +    很多东西都是时看时新,而且时间长了也会忘,所以再来复习下,也会有一些新的角度看法这次来聊下AQS的内容,主要是这几个点,

    +

    第一个线程

    第一个线程抢到锁了,此时state跟阻塞队列是怎么样的,其实这里是之前没理解对的地方

    +
    /**
    +         * Fair version of tryAcquire.  Don't grant access unless
    +         * recursive call or no waiters or is first.
    +         */
    +        protected final boolean tryAcquire(int acquires) {
    +            final Thread current = Thread.currentThread();
    +            int c = getState();
    +            // 这里如果state还是0说明锁还空着
    +            if (c == 0) {
    +                // 因为是公平锁版本的,先去看下是否阻塞队列里有排着队的
    +                if (!hasQueuedPredecessors() &&
    +                    compareAndSetState(0, acquires)) {
    +                    // 没有排队的,并且state使用cas设置成功的就标记当前占有锁的线程是我
    +                    setExclusiveOwnerThread(current);
    +                    // 然后其实就返回了,包括阻塞队列的head和tail节点和waitStatus都没有设置
    +                    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;
    +        }
    +    }
    + +

    第二个线程

    当第二个线程进来的时候应该是怎么样,结合代码来看

    +
    /**
    +     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    +     * by invoking at least once {@link #tryAcquire},
    +     * returning on success.  Otherwise the thread is queued, possibly
    +     * repeatedly blocking and unblocking, invoking {@link
    +     * #tryAcquire} until success.  This method can be used
    +     * to implement method {@link Lock#lock}.
    +     *
    +     * @param arg the acquire argument.  This value is conveyed to
    +     *        {@link #tryAcquire} but is otherwise uninterpreted and
    +     *        can represent anything you like.
    +     */
    +    public final void acquire(int arg) {
    +        // 前面第一种情况是tryAcquire直接成功了,这个if判断第一个条件就是false,就不往下执行了
    +        // 如果是第二个线程,第一个条件获取锁不成功,条件判断!tryAcquire(arg) == true,就会走
    +        // acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    +        if (!tryAcquire(arg) &&
    +            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    +            selfInterrupt();
    +    }
    + +

    然后来看下addWaiter的逻辑

    +
    /**
    +     * Creates and enqueues node for current thread and given mode.
    +     *
    +     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
    +     * @return the new node
    +     */
    +    private Node addWaiter(Node mode) {
    +        // 这里是包装成一个node
    +        Node node = new Node(Thread.currentThread(), mode);
    +        // Try the fast path of enq; backup to full enq on failure
    +        // 最快的方式就是把当前线程的节点放在阻塞队列的最后
    +        Node pred = tail;
    +        // 只有当tail,也就是pred不为空的时候可以直接接上
    +        if (pred != null) {
    +            node.prev = pred;
    +            // 如果这里cas成功了,就直接接上返回了
    +            if (compareAndSetTail(pred, node)) {
    +                pred.next = node;
    +                return node;
    +            }
    +        }
    +        // 不然就会继续走到这里
    +        enq(node);
    +        return node;
    +    }
    + +

    然后就是enq的逻辑了

    +
    /**
    +     * Inserts node into queue, initializing if necessary. See picture above.
    +     * @param node the node to insert
    +     * @return node's predecessor
    +     */
    +    private Node enq(final Node node) {
    +        for (;;) {
    +            // 如果状态没变化的话,tail这时还是null的
    +            Node t = tail;
    +            if (t == null) { // Must initialize
    +                // 这里就会初始化头结点,就是个空节点
    +                if (compareAndSetHead(new Node()))
    +                    // tail也赋值成head
    +                    tail = head;
    +            } else {
    +                // 这里就设置tail了
    +                node.prev = t;
    +                if (compareAndSetTail(t, node)) {
    +                    t.next = node;
    +                    return t;
    +                }
    +            }
    +        }
    +    }
    + +

    所以从这里可以看出来,其实head头结点不是个真实的带有线程的节点,并且不是在第一个线程进来的时候设置的

    +

    解锁

    通过代码来看下

    +
    /**
    +     * Attempts to release this lock.
    +     *
    +     * <p>If the current thread is the holder of this lock then the hold
    +     * count is decremented.  If the hold count is now zero then the lock
    +     * is released.  If the current thread is not the holder of this
    +     * lock then {@link IllegalMonitorStateException} is thrown.
    +     *
    +     * @throws IllegalMonitorStateException if the current thread does not
    +     *         hold this lock
    +     */
    +    public void unlock() {
    +        // 释放锁
    +        sync.release(1);
    +    }
    +/**
    +     * Releases in exclusive mode.  Implemented by unblocking one or
    +     * more threads if {@link #tryRelease} returns true.
    +     * This method can be used to implement method {@link Lock#unlock}.
    +     *
    +     * @param arg the release argument.  This value is conveyed to
    +     *        {@link #tryRelease} but is otherwise uninterpreted and
    +     *        can represent anything you like.
    +     * @return the value returned from {@link #tryRelease}
    +     */
    +    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) {
    +            int c = getState() - releases;
    +            if (Thread.currentThread() != getExclusiveOwnerThread())
    +                throw new IllegalMonitorStateException();
    +            boolean free = false;
    +    		// 判断是否完全释放锁,因为可重入
    +            if (c == 0) {
    +                free = true;
    +                setExclusiveOwnerThread(null);
    +            }
    +            setState(c);
    +            return free;
    +        }
    +// 这段代码和上面的一致,只是为了顺序性,又拷下来看下
    +
    +public final boolean release(int arg) {
    +        // 尝试去释放,如果是完全释放,返回的就是true,否则是false
    +        if (tryRelease(arg)) {
    +            Node h = head;
    +            // 这里判断头结点是否为空以及waitStatus的状态,前面说了head节点其实是
    +            // 在第二个线程进来的时候初始化的,如果是空的话说明没后续节点,并且waitStatus
    +            // 也表示了后续的等待状态
    +            if (h != null && h.waitStatus != 0)
    +                unparkSuccessor(h);
    +            return true;
    +        }
    +        return false;
    +    }
    +
    +/**
    +     * Wakes up node's successor, if one exists.
    +     *
    +     * @param node the node
    +     */
    +// 唤醒后继节点
    +    private void unparkSuccessor(Node node) {
    +        /*
    +         * If status is negative (i.e., possibly needing signal) try
    +         * to clear in anticipation of signalling.  It is OK if this
    +         * fails or if status is changed by waiting thread.
    +         */
    +        int ws = node.waitStatus;
    +        if (ws < 0)
    +            compareAndSetWaitStatus(node, ws, 0);
    +
    +        /*
    +         * Thread to unpark is held in successor, which is normally
    +         * just the next node.  But if cancelled or apparently null,
    +         * traverse backwards from tail to find the actual
    +         * non-cancelled successor.
    +         */
    +        Node s = node.next;
    +        // 如果后继节点是空或者当前节点取消等待了
    +        if (s == null || s.waitStatus > 0) {
    +            s = null;
    +            // 从后往前找,找到非取消的节点,注意这里不是找到就退出,而是一直找到头
    +            // 所以不必担心中间有取消的
    +            for (Node t = tail; t != null && t != node; t = t.prev)
    +                if (t.waitStatus <= 0)
    +                    s = t;
    +        }
    +        if (s != null)
    +            // 将其唤醒
    +            LockSupport.unpark(s.thread);
    +    }
    + + + + +]]>
    + + Java + 并发 + + + java + 并发 + j.u.c + aqs + + + + Comparator使用小记 + /2020/04/05/Comparator%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/ + 在Java8的stream之前,将对象进行排序的时候,可能需要对象实现Comparable接口,或者自己实现一个Comparator,

    +

    比如这样子

    +

    我的对象是Entity

    +
    public class Entity {
    +
    +    private Long id;
    +
    +    private Long sortValue;
    +
    +    public Long getId() {
             return id;
         }
     
    @@ -1292,131 +1232,86 @@ Node *clone(Node *graph) {
           
       
       
    -    Disruptor 系列一
    -    /2022/02/13/Disruptor-%E7%B3%BB%E5%88%97%E4%B8%80/
    -    很久之前就听说过这个框架,不过之前有点跟消息队列混起来,这个也是种队列,但不是跟 rocketmq,nsq 那种一样的,而是在进程内部提供队列服务的,偏向于取代ArrayBlockingQueue,因为这个阻塞队列是使用了锁来控制阻塞,关于并发其实有一些通用的最佳实践,就是用锁,即使是 JDK 提供的锁,也是比较耗资源的,当然这是跟不加锁的对比,同样是锁,JDK 的实现还是性能比较优秀的。常见的阻塞队列中例如 ArrayBlockingQueueLinkedBlockingQueue 都有锁的身影的存在,区别在于 ArrayBlockingQueue 是一把锁,后者是两把锁,不过重点不在几把锁,这里其实是两个问题,一个是所谓的 lock free, 对于一个单生产者的 disruptor 来说,因为写入是只有一个线程的,是可以不用加锁,多生产者的时候使用的是 cas 来获取对应的写入坑位,另一个是解决“伪共享”问题,后面可以详细点分析,先介绍下使用
    首先是数据源

    -
    public class LongEvent {
    -    private long value;
    +    AbstractQueuedSynchronizer
    +    /2019/09/23/AbstractQueuedSynchronizer/
    +    最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
    首先 AQS 只要有这些属性

    +
    // 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
    +private transient volatile Node head;
     
    -    public void set(long value) {
    -        this.value = value;
    -    }
    +// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
    +private transient volatile Node tail;
     
    -    public long getValue() {
    -        return value;
    -    }
    -
    -    public void setValue(long value) {
    -        this.value = value;
    -    }
    -}
    -

    事件生产

    -
    public class LongEventFactory implements EventFactory<LongEvent>
    -{
    -    public LongEvent newInstance()
    -    {
    -        return new LongEvent();
    -    }
    -}
    -

    事件处理器

    -
    public class LongEventHandler implements EventHandler<LongEvent> {
    -
    -    // event 事件,
    -    // sequence 当前的序列 
    -    // 是否当前批次最后一个数据
    -    public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
    -    {
    -        String str = String.format("long event : %s l:%s b:%s", event.getValue(), sequence, endOfBatch);
    -        System.out.println(str);
    -    }
    -}
    -
    -

    主方法代码

    -
    package disruptor;
    -
    -import com.lmax.disruptor.RingBuffer;
    -import com.lmax.disruptor.dsl.Disruptor;
    -import com.lmax.disruptor.util.DaemonThreadFactory;
    -
    -import java.nio.ByteBuffer;
    +// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
    +// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
    +private volatile int state;
     
    -public class LongEventMain
    -{
    -    public static void main(String[] args) throws Exception
    -    {
    -        // 这个需要是 2 的幂次,这样在定位的时候只需要位移操作,也能减少各种计算操作
    -        int bufferSize = 1024; 
    +// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
    +// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
    +// if (currentThread == getExclusiveOwnerThread()) {state++}
    +private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
    +

    大概了解了 aqs 底层的双向等待队列,
    结构是这样的

    每个 node 里面主要是的代码结构也比较简单

    +
    static final class Node {
    +    // 标识节点当前在共享模式下
    +    static final Node SHARED = new Node();
    +    // 标识节点当前在独占模式下
    +    static final Node EXCLUSIVE = null;
     
    -        Disruptor<LongEvent> disruptor = 
    -                new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
    +    // ======== 下面的几个int常量是给waitStatus用的 ===========
    +    /** waitStatus value to indicate thread has cancelled */
    +    // 代码此线程取消了争抢这个锁
    +    static final int CANCELLED =  1;
    +    /** waitStatus value to indicate successor's thread needs unparking */
    +    // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
    +    static final int SIGNAL    = -1;
    +    /** waitStatus value to indicate thread is waiting on condition */
    +    // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
    +    static final int CONDITION = -2;
    +    /**
    +     * waitStatus value to indicate the next acquireShared should
    +     * unconditionally propagate
    +     */
    +    // 同样的不分析,略过吧
    +    static final int PROPAGATE = -3;
    +    // =====================================================
     
    -        // 类似于注册处理器
    -        disruptor.handleEventsWith(new LongEventHandler());
    -        // 或者直接用 lambda
    -        disruptor.handleEventsWith((event, sequence, endOfBatch) ->
    -                System.out.println("Event: " + event));
    -        // 启动我们的 disruptor
    -        disruptor.start(); 
     
    +    // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
    +    // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
    +    //    ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
    +    volatile int waitStatus;
    +    // 前驱节点的引用
    +    volatile Node prev;
    +    // 后继节点的引用
    +    volatile Node next;
    +    // 这个就是线程本尊
    +    volatile Thread thread;
     
    -        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); 
    -        ByteBuffer bb = ByteBuffer.allocate(8);
    -        for (long l = 0; true; l++)
    -        {
    -            bb.putLong(0, l);
    -            // 生产事件
    -            ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
    -            Thread.sleep(1000);
    -        }
    -    }
    -}
    -

    运行下可以看到运行结果

    这里其实就只是最简单的使用,生产者只有一个,然后也不是批量的。

    +}
    +

    其实可以主要关注这个 waitStatus 因为这个是后面的节点给前面的节点设置的,等于-1 的时候代表后面有节点等待,需要去唤醒,
    这里使用了一个变种的 CLH 队列实现,CLH 队列相关内容可以查看这篇 自旋锁、排队自旋锁、MCS锁、CLH锁

    ]]>
    - Java + java - Java - Disruptor + java + aqs
    - Disruptor 系列二 - /2022/02/27/Disruptor-%E7%B3%BB%E5%88%97%E4%BA%8C/ - 这里开始慢慢深入的讲一下 disruptor,首先是 lock free , 相比于前面介绍的两个阻塞队列,
    disruptor 本身是不直接使用锁的,因为本身的设计是单个线程去生产,通过 cas 来维护头指针,
    不直接维护尾指针,这样就减少了锁的使用,提升了性能;第二个是这次介绍的重点,
    减少 false sharing 的情况,也就是常说的 伪共享 问题,那么什么叫 伪共享 呢,
    这里要扯到一些 cpu 缓存的知识,

    譬如我在用的这个笔记本

    这里就可能看到 L2 Cache 就是针对每个核的

    这里可以看到现代 CPU 的结构里,分为三级缓存,越靠近 cpu 的速度越快,存储容量越小,
    而 L1 跟 L2 是 CPU 核专属的每个核都有自己的 L1 和 L2 的,其中 L1 还分为数据和指令,
    像我上面的图中显示的 L1 Cache 只有 64KB 大小,其中数据 32KB,指令 32KB,
    而 L2 则有 256KB,L3 有 4MB,其中的 Line Size 是我们这里比较重要的一个值,
    CPU 其实会就近地从 Cache 中读取数据,碰到 Cache Miss 就再往下一级 Cache 读取,
    每次读取是按照缓存行 Cache Line 读取,并且也遵循了“就近原则”,
    也就是相近的数据有可能也会马上被读取,所以以行的形式读取,然而这也造成了 false sharing
    因为类似于 ArrayBlockingQueue,需要有 takeIndex , putIndex , count , 因为在同一个类中,
    很有可能存在于同一个 Cache Line 中,但是这几个值会被不同的线程修改,
    导致从 Cache 取出来以后立马就会被失效,所谓的就近原则也就没用了,
    因为需要反复地标记 dirty 脏位,然后把 Cache 刷掉,就造成了false sharing这种情况
    而在 disruptor 中则使用了填充的方式,让我的头指针能够不产生false sharing

    -
    class LhsPadding
    -{
    -    protected long p1, p2, p3, p4, p5, p6, p7;
    -}
    -
    -class Value extends LhsPadding
    -{
    -    protected volatile long value;
    -}
    -
    -class RhsPadding extends Value
    -{
    -    protected long p9, p10, p11, p12, p13, p14, p15;
    -}
    -
    -/**
    - * <p>Concurrent sequence class used for tracking the progress of
    - * the ring buffer and event processors.  Support a number
    - * of concurrent operations including CAS and order writes.
    - *
    - * <p>Also attempts to be more efficient with regards to false
    - * sharing by adding padding around the volatile field.
    - */
    -public class Sequence extends RhsPadding
    -{
    -

    通过代码可以看到,sequence 中其实真正有意义的是 value 字段,因为需要在多线程环境下可见也
    使用了volatile 关键字,而 LhsPaddingRhsPadding 分别在value 前后填充了各
    7 个 long 型的变量,long 型的变量在 Java 中是占用 8 bytes,这样就相当于不管怎么样,
    value 都会单独使用一个缓存行,使得其不会产生 false sharing 的问题。

    + 2021 年终总结 + /2022/01/22/2021-%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + 又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

    +

    工作篇

    工作没什么大变化,有了些微的提升,可能因为是来了之后做了些项目对比公司与来还算是比较重要的,但是技术难度上没有特别突出的点,可能最开始用 openresty+lua 做了个 ab 测的工具,还是让我比较满意的,后面一般都是业务型的需求,今年可能在业务相关的技术逻辑上有了一些深度的了解,而原来一直想做的业务架构升级和通用型技术中间件这样的优化还是停留在想象中,前面说的 ab 测应该算是个半成品,还是没能多走出这一步,得需要多做一些实在的事情,比如轻量级的业务框架,能够对原先不熟悉的业务逻辑,代码逻辑有比较深入的理解,而不是一直都是让特定的同学负责特定的逻辑,很多时候还是在偷懒,习惯以一些简单安全的方案去做事情,在技术上还是要有所追求,还有就是能够在新语言,主要是 rust,swift 这类的能有些小玩具可以做,rust 的话是因为今年看了一本相关的书,后面三分之一其实消化得不好,这本书整体来说是很不错的,只是 rust 本身在所有权这块,还有引用包装等方面是设计得比较难懂,也可能是我基础差,所以还是想在复习下,可以做一个简单的命令行工具这种,然后 swift 是想说可以做点 mac 的小软件,原生的毕竟性能好点,又小。基于 web 做的客户端大部分都是又丑又大,极少数能好看点,但也是很重,起码 7~80M 的大小,原生的估计能除以 10。
    整体的职业规划貌似陷入了比较大的困惑期,在目前公司发展前景不是很大,但是出去貌似也没有比较适合我的机会,总的来说还是杭州比较卷,个人觉得有自己的时间是非常重要的,而且这个不光是用来自我提升的,还是让自己有足够的时间做缓冲,有足够的时间锻炼减肥,时间少的情况下,不光会在仅有的时间里暴饮暴食,还没空锻炼,身体是革命的本钱,现在其实能特别明显地感觉到身体状态下滑,容易疲劳,焦虑。所以是否也许有可能以后要往外企这类的方向去发展。
    工作上其实还是有个不大不小的缺点,就是容易激动,容易焦虑,前一点可能有稍稍地改观,因为工作中的很多现状其实是我个人难以改变的,即使觉得不合理,但是结构在那里,还不如自己放宽心,尽量做好事情就行。第二点的话还是做得比较差,一直以来抗压能力都比较差,跟成长环境,家庭环境都有比较大的关系,而且说实在的特别是父母,基本也没有在这方面给我正向的帮助,比较擅长给我施压,从小就是通过压力让我好好读书,当个乖学生,考个好学校,并没有能真正地理解我的压力,教我或者帮助我解压,只会在那说着不着边际的空话,甚至经常反过来对我施压。还是希望能慢慢解开,这点可能对我身体也有影响,也许需要看一些心理疏导相关的书籍。工作篇暂时到这,后续还有其他篇,未完待续哈哈😀

    ]]>
    - Java + 生活 + 年终总结 - Java - Disruptor + 生活 + 年终总结 + 2021 + 拖更
    @@ -1886,28 +1781,6 @@ Node *clone(Node *graph) { Web - - Dubbo 使用的几个记忆点 - /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/ - 因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

    -

    dubbo 只拉取不注册

    <dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
    -

    就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

    -

    dubbo 只注册不拉取

    <dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
    -

    这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

    -

    权重配置

    <dubbo:provider loadbalance="random" weight="50"/>
    -

    首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

    -]]>
    - - Java - Dubbo - - - Java - Dubbo - RPC - 负载均衡 - -
    G1收集器概述 /2020/02/09/G1%E6%94%B6%E9%9B%86%E5%99%A8%E6%A6%82%E8%BF%B0/ @@ -2598,81 +2471,48 @@ Node *clone(Node *graph) { - Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析 - /2021/10/07/Leetcode-021-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8-Merge-Two-Sorted-Lists-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

    -

    将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    -

    示例 1

    -
    -

    输入:l1 = [1,2,4], l2 = [1,3,4]
    输出:[1,1,2,3,4,4]

    -
    -

    示例 2

    -

    输入: l1 = [], l2 = []
    输出: []

    -
    -

    示例 3

    -

    输入: l1 = [], l2 = [0]
    输出: [0]

    -
    -

    简要分析

    这题是 Easy 的,看着也挺简单,两个链表进行合并,就是比较下大小,可能将就点的话最好就在两个链表中原地合并

    -

    题解代码

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    -        // 下面两个if判断了入参的边界,如果其一为null,直接返回另一个就可以了
    -        if (l1 == null) {
    -            return l2;
    -        }
    -        if (l2 == null) {
    -            return l1;
    -        }
    -        // new 一个合并后的头结点
    -        ListNode merged = new ListNode();
    -        // 这个是当前节点
    -        ListNode current = merged;
    -        // 一开始给这个while加了l1和l2不全为null的条件,后面想了下不需要
    -        // 因为内部前两个if就是跳出条件
    -        while (true) {
    -            if (l1 == null) {
    -                // 这里其实跟开头类似,只不过这里需要将l2剩余部分接到merged链表后面
    -                // 所以不能是直接current = l2,这样就是把后面的直接丢了
    -                current.val = l2.val;
    -                current.next = l2.next;
    -                break;
    -            }
    -            if (l2 == null) {
    -                current.val = l1.val;
    -                current.next = l1.next;
    -                break;
    -            }
    -            // 这里是两个链表都不为空的时候,就比较下大小
    -            if (l1.val < l2.val) {
    -                current.val = l1.val;
    -                l1 = l1.next;
    -            } else {
    -                current.val = l2.val;
    -                l2 = l2.next;
    -            }
    -            // 这里是new个新的,其实也可以放在循环头上
    -            current.next = new ListNode();
    -            current = current.next;
    -        }
    -        current = null;
    -        // 返回这个头结点
    -        return merged;
    -    }
    + Disruptor 系列二 + /2022/02/27/Disruptor-%E7%B3%BB%E5%88%97%E4%BA%8C/ + 这里开始慢慢深入的讲一下 disruptor,首先是 lock free , 相比于前面介绍的两个阻塞队列,
    disruptor 本身是不直接使用锁的,因为本身的设计是单个线程去生产,通过 cas 来维护头指针,
    不直接维护尾指针,这样就减少了锁的使用,提升了性能;第二个是这次介绍的重点,
    减少 false sharing 的情况,也就是常说的 伪共享 问题,那么什么叫 伪共享 呢,
    这里要扯到一些 cpu 缓存的知识,

    譬如我在用的这个笔记本

    这里就可能看到 L2 Cache 就是针对每个核的

    这里可以看到现代 CPU 的结构里,分为三级缓存,越靠近 cpu 的速度越快,存储容量越小,
    而 L1 跟 L2 是 CPU 核专属的每个核都有自己的 L1 和 L2 的,其中 L1 还分为数据和指令,
    像我上面的图中显示的 L1 Cache 只有 64KB 大小,其中数据 32KB,指令 32KB,
    而 L2 则有 256KB,L3 有 4MB,其中的 Line Size 是我们这里比较重要的一个值,
    CPU 其实会就近地从 Cache 中读取数据,碰到 Cache Miss 就再往下一级 Cache 读取,
    每次读取是按照缓存行 Cache Line 读取,并且也遵循了“就近原则”,
    也就是相近的数据有可能也会马上被读取,所以以行的形式读取,然而这也造成了 false sharing
    因为类似于 ArrayBlockingQueue,需要有 takeIndex , putIndex , count , 因为在同一个类中,
    很有可能存在于同一个 Cache Line 中,但是这几个值会被不同的线程修改,
    导致从 Cache 取出来以后立马就会被失效,所谓的就近原则也就没用了,
    因为需要反复地标记 dirty 脏位,然后把 Cache 刷掉,就造成了false sharing这种情况
    而在 disruptor 中则使用了填充的方式,让我的头指针能够不产生false sharing

    +
    class LhsPadding
    +{
    +    protected long p1, p2, p3, p4, p5, p6, p7;
    +}
     
    -

    结果

    -]]> - - Java - leetcode - - - leetcode - java - 题解 - - - - Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析 - /2021/10/31/Leetcode-028-%E5%AE%9E%E7%8E%B0-strStr-Implement-strStr-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Implement strStr().

    +class Value extends LhsPadding +{ + protected volatile long value; +} + +class RhsPadding extends Value +{ + protected long p9, p10, p11, p12, p13, p14, p15; +} + +/** + * <p>Concurrent sequence class used for tracking the progress of + * the ring buffer and event processors. Support a number + * of concurrent operations including CAS and order writes. + * + * <p>Also attempts to be more efficient with regards to false + * sharing by adding padding around the volatile field. + */ +public class Sequence extends RhsPadding +{
    +

    通过代码可以看到,sequence 中其实真正有意义的是 value 字段,因为需要在多线程环境下可见也
    使用了volatile 关键字,而 LhsPaddingRhsPadding 分别在value 前后填充了各
    7 个 long 型的变量,long 型的变量在 Java 中是占用 8 bytes,这样就相当于不管怎么样,
    value 都会单独使用一个缓存行,使得其不会产生 false sharing 的问题。

    +]]>
    + + Java + + + Java + Disruptor + +
    + + Leetcode 028 实现 strStr() ( Implement strStr() ) 题解分析 + /2021/10/31/Leetcode-028-%E5%AE%9E%E7%8E%B0-strStr-Implement-strStr-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    Implement strStr().

    Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

    Clarification:

    What should we return when needle is an empty string? This is a great question to ask during an interview.

    For the purpose of this problem, we will return 0 when needle is an empty string. This is consistent to C’s strstr() and Java’s indexOf().

    @@ -2744,6 +2584,45 @@ Output: 0
    + + Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 + /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

    +

    A subarray is a contiguous part of an array.

    +

    示例

    Example 1:

    +
    +

    Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
    Output: 6
    Explanation: [4,-1,2,1] has the largest sum = 6.

    +
    +

    Example 2:

    +
    +

    Input: nums = [1]
    Output: 1

    +
    +

    Example 3:

    +
    +

    Input: nums = [5,4,-1,7,8]
    Output: 23

    +
    +

    说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

    +

    代码

    public int maxSubArray(int[] nums) {
    +        int max = nums[0];
    +        int sum = nums[0];
    +        for (int i = 1; i < nums.length; i++) {
    +            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
    +            sum = Math.max(nums[i], sum + nums[i]);
    +            // max 是用来承载最大值的
    +            max = Math.max(max, sum);
    +        }
    +        return max;
    +    }
    ]]>
    + + Java + leetcode + + + leetcode + java + 题解 + +
    Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析 /2020/10/25/Leetcode-104-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6-Maximum-Depth-of-Binary-Tree-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ @@ -2789,49 +2668,10 @@ Output: 0 - - Leetcode 053 最大子序和 ( Maximum Subarray ) 题解分析 - /2021/11/28/Leetcode-053-%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C-Maximum-Subarray-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

    -

    A subarray is a contiguous part of an array.

    -

    示例

    Example 1:

    -
    -

    Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
    Output: 6
    Explanation: [4,-1,2,1] has the largest sum = 6.

    -
    -

    Example 2:

    -
    -

    Input: nums = [1]
    Output: 1

    -
    -

    Example 3:

    -
    -

    Input: nums = [5,4,-1,7,8]
    Output: 23

    -
    -

    说起来这个题其实非常有渊源,大学数据结构的第一个题就是这个,而最佳的算法就是传说中的 online 算法,就是遍历一次就完了,最基本的做法就是记下来所有的连续子数组,然后求出最大的那个。

    -

    代码

    public int maxSubArray(int[] nums) {
    -        int max = nums[0];
    -        int sum = nums[0];
    -        for (int i = 1; i < nums.length; i++) {
    -            // 这里最重要的就是这一行了,其实就是如果前面的 sum 是小于 0 的,那么就不需要前面的 sum,反正加上了还不如不加大
    -            sum = Math.max(nums[i], sum + nums[i]);
    -            // max 是用来承载最大值的
    -            max = Math.max(max, sum);
    -        }
    -        return max;
    -    }
    ]]>
    - - Java - leetcode - - - leetcode - java - 题解
    @@ -2891,9 +2731,9 @@ inorder = [9,3,15,20,7] - Leetcode 121 买卖股票的最佳时机(Best Time to Buy and Sell Stock) 题解分析 - /2021/03/14/Leetcode-121-%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA-Best-Time-to-Buy-and-Sell-Stock-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    You are given an array prices where prices[i] is the price of a given stock on the ith day.

    -

    You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.

    -

    Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.

    -

    给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

    -

    你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

    -

    返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

    -

    简单分析

    其实这个跟二叉树的最长路径和有点类似,需要找到整体的最大收益,但是在迭代过程中需要一个当前的值

    -
    int maxSofar = 0;
    -public int maxProfit(int[] prices) {
    -    if (prices.length <= 1) {
    -        return 0;
    -    }
    -    int maxIn = prices[0];
    -    int maxOut = prices[0];
    -    for (int i = 1; i < prices.length; i++) {
    -        if (maxIn > prices[i]) {
    -            // 当循环当前值小于之前的买入值时就当成买入值,同时卖出也要更新
    -            maxIn = prices[i];
    -            maxOut = prices[i];
    +    Dubbo 使用的几个记忆点
    +    /2022/04/02/Dubbo-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%87%A0%E4%B8%AA%E8%AE%B0%E5%BF%86%E7%82%B9/
    +    因为后台使用的 dubbo 作为 rpc 框架,并且会有一些日常使用情景有一些小的技巧,在这里做下记录作笔记用

    +

    dubbo 只拉取不注册

    <dubbo:registry address="zookeeper://127.0.0.1:2181" register="false" />
    +

    就是只要 register="false" 就可以了,这样比如我们在开发环境想运行服务,但又不想让开发环境正常的请求调用到本地来,当然这不是唯一的方式,通过 dubbo 2.7 以上的 tag 路由也可以实现或者自行改造拉取和注册服务的逻辑,因为注册到注册中心的其实是一串带参数的 url,还是比较方便改造的。相反的就是只注册,不拉取

    +

    dubbo 只注册不拉取

    <dubbo:registry address="zookeeper://127.0.0.1:2181" subscribe="false" />
    +

    这个使用场景就是如果我这个服务只作为 provider,没有任何调用其他的服务,其实就可以这么设置

    +

    权重配置

    <dubbo:provider loadbalance="random" weight="50"/>
    +

    首先这是在使用了随机的负载均衡策略的时候可以进行配置,并且是对于多个 provider 的情况下,这样其实也可以部分解决上面的只拉取不注册的问题,我把自己的权重调成 0 或者很低

    +]]>
    + + Java + Dubbo + + + Java + Dubbo + RPC + 负载均衡 + + + + Leetcode 021 合并两个有序链表 ( Merge Two Sorted Lists ) 题解分析 + /2021/10/07/Leetcode-021-%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8-Merge-Two-Sorted-Lists-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

    +

    将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    +

    示例 1

    +
    +

    输入:l1 = [1,2,4], l2 = [1,3,4]
    输出:[1,1,2,3,4,4]

    +
    +

    示例 2

    +

    输入: l1 = [], l2 = []
    输出: []

    +
    +

    示例 3

    +

    输入: l1 = [], l2 = [0]
    输出: [0]

    +
    +

    简要分析

    这题是 Easy 的,看着也挺简单,两个链表进行合并,就是比较下大小,可能将就点的话最好就在两个链表中原地合并

    +

    题解代码

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    +        // 下面两个if判断了入参的边界,如果其一为null,直接返回另一个就可以了
    +        if (l1 == null) {
    +            return l2;
             }
    -        if (prices[i] > maxOut) {
    -            // 表示一个可卖出点,即比买入值高时
    -            maxOut = prices[i];
    -            // 需要设置一个历史值
    -            maxSofar = Math.max(maxSofar, maxOut - maxIn);
    +        if (l2 == null) {
    +            return l1;
             }
    -    }
    -    return maxSofar;
    -}
    + // new 一个合并后的头结点 + ListNode merged = new ListNode(); + // 这个是当前节点 + ListNode current = merged; + // 一开始给这个while加了l1和l2不全为null的条件,后面想了下不需要 + // 因为内部前两个if就是跳出条件 + while (true) { + if (l1 == null) { + // 这里其实跟开头类似,只不过这里需要将l2剩余部分接到merged链表后面 + // 所以不能是直接current = l2,这样就是把后面的直接丢了 + current.val = l2.val; + current.next = l2.next; + break; + } + if (l2 == null) { + current.val = l1.val; + current.next = l1.next; + break; + } + // 这里是两个链表都不为空的时候,就比较下大小 + if (l1.val < l2.val) { + current.val = l1.val; + l1 = l1.next; + } else { + current.val = l2.val; + l2 = l2.next; + } + // 这里是new个新的,其实也可以放在循环头上 + current.next = new ListNode(); + current = current.next; + } + current = null; + // 返回这个头结点 + return merged; + }
    -

    总结下

    一开始看到 easy 就觉得是很简单,就没有 maxSofar ,但是一提交就出现问题了
    对于[2, 4, 1]这种就会变成 0,所以还是需要一个历史值来存放历史最大值,这题有点动态规划的意思

    +

    结果

    ]]>
    Java leetcode - java - DP - DP leetcode java 题解 - DP
    - Leetcode 1115 交替打印 FooBar ( Print FooBar Alternately *Medium* ) 题解分析 - /2022/05/01/Leetcode-1115-%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0-FooBar-Print-FooBar-Alternately-Medium-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 无聊想去 roll 一题就看到了有并发题,就找到了这题,其实一眼看我的想法也是用信号量,但是用 condition 应该也是可以处理的,不过这类问题好像本地有点难调,因为它好像是抽取代码执行的,跟直观的逻辑比较不一样
    Suppose you are given the following code:

    -
    class FooBar {
    -  public void foo() {
    -    for (int i = 0; i < n; i++) {
    -      print("foo");
    -    }
    -  }
    -
    -  public void bar() {
    -    for (int i = 0; i < n; i++) {
    -      print("bar");
    -    }
    -  }
    -}
    -

    The same instance of FooBar will be passed to two different threads:

    -
      -
    • thread A will call foo(), while
    • -
    • thread B will call bar().
      Modify the given program to output "foobar" n times.
    • -
    -

    示例

    Example 1:

    -

    Input: n = 1
    Output: “foobar”
    Explanation: There are two threads being fired asynchronously. One of them calls foo(), while the other calls bar().
    “foobar” is being output 1 time.

    -
    -

    Example 2:

    -

    Input: n = 2
    Output: “foobarfoobar”
    Explanation: “foobar” is being output 2 times.

    -
    -

    题解

    简析

    其实用信号量是很直观的,就是让打印 foo 的线程先拥有信号量,打印后就等待,给 bar 信号量 + 1,然后 bar 线程运行打印消耗 bar 信号量,再给 foo 信号量 + 1

    -

    code

    class FooBar {
    -    
    -    private final Semaphore foo = new Semaphore(1);
    -    private final Semaphore bar = new Semaphore(0);
    -    private int n;
    -
    -    public FooBar(int n) {
    -        this.n = n;
    -    }
    -
    -    public void foo(Runnable printFoo) throws InterruptedException {
    -        
    -        for (int i = 0; i < n; i++) {
    -            foo.acquire();
    -        	// printFoo.run() outputs "foo". Do not change or remove this line.
    -        	printFoo.run();
    -            bar.release();
    -        }
    -    }
    -
    -    public void bar(Runnable printBar) throws InterruptedException {
    -        
    -        for (int i = 0; i < n; i++) {
    -            bar.acquire();
    -            // printBar.run() outputs "bar". Do not change or remove this line.
    -        	printBar.run();
    -            foo.release();
    -        }
    -    }
    -}
    -]]>
    - - Java - leetcode - - - leetcode - java - 题解 - Print FooBar Alternately - -
    - - Leetcode 1260 二维网格迁移 ( Shift 2D Grid *Easy* ) 题解分析 - /2022/07/22/Leetcode-1260-%E4%BA%8C%E7%BB%B4%E7%BD%91%E6%A0%BC%E8%BF%81%E7%A7%BB-Shift-2D-Grid-Easy-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    Given a 2D grid of size m x n and an integer k. You need to shift the grid k times.

    -

    In one shift operation:

    -

    Element at grid[i][j] moves to grid[i][j + 1].
    Element at grid[i][n - 1] moves to grid[i + 1][0].
    Element at grid[m - 1][n - 1] moves to grid[0][0].
    Return the 2D grid after applying shift operation k times.

    -

    示例

    Example 1:

    -
    -

    Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
    Output: [[9,1,2],[3,4,5],[6,7,8]]

    -
    -

    Example 2:

    -
    -

    Input: grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
    Output: [[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]

    -
    -

    Example 3:

    -

    Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
    Output: [[1,2,3],[4,5,6],[7,8,9]]

    -
    -

    提示

      -
    • m == grid.length
    • -
    • n == grid[i].length
    • -
    • 1 <= m <= 50
    • -
    • 1 <= n <= 50
    • -
    • -1000 <= grid[i][j] <= 1000
    • -
    • 0 <= k <= 100
    • -
    -

    解析

    这个题主要是矩阵或者说数组的操作,并且题目要返回的是个 List,所以也不用原地操作,只需要找对位置就可以了,k 是多少就相当于让这个二维数组头尾衔接移动 k 个元素

    -

    代码

    public List<List<Integer>> shiftGrid(int[][] grid, int k) {
    -        // 行数
    -        int m = grid.length;
    -        // 列数
    -        int n = grid[0].length;
    -        // 偏移值,取下模
    -        k = k % (m * n);
    -        // 反向取下数量,因为我打算直接从头填充新的矩阵
    -        /*
    -         *    比如
    -         *    1 2 3
    -         *    4 5 6
    -         *    7 8 9
    -         *    需要变成
    -         *    9 1 2
    -         *    3 4 5
    -         *    6 7 8
    -         *    就要从 9 开始填充
    -         */
    -        int reverseK = m * n - k;
    -        List<List<Integer>> matrix = new ArrayList<>();
    -        // 这类就是两层循环
    -        for (int i = 0; i < m; i++) {
    -            List<Integer> line = new ArrayList<>();
    -            for (int j = 0; j < n; j++) {
    -                // 数量会随着循环迭代增长, 确认是第几个
    -                int currentNum = reverseK + i * n +  (j + 1);
    -                // 这里处理下到达矩阵末尾后减掉 m * n
    -                if (currentNum > m * n) {
    -                    currentNum -= m * n;
    -                }
    -                // 根据矩阵列数 n 算出在原来矩阵的位置
    -                int last = (currentNum - 1) % n;
    -                int passLine = (currentNum - 1) / n;
    -
    -                line.add(grid[passLine][last]);
    -            }
    -            matrix.add(line);
    -        }
    -        return matrix;
    -    }
    - -

    结果数据


    比较慢

    -]]>
    - - Java - leetcode - - - leetcode - java - 题解 - Shift 2D Grid - -
    - - Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析 - /2021/01/24/Leetcode-124-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C-Binary-Tree-Maximum-Path-Sum-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    -

    The path sum of a path is the sum of the node’s values in the path.

    -

    Given the root of a binary tree, return the maximum path sum of any path.

    -

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    -

    路径和 是路径中各节点值的总和。

    -

    给你一个二叉树的根节点 root ,返回其 最大路径和

    -

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    -

    代码

    int ansNew = Integer.MIN_VALUE;
    -public int maxPathSum(TreeNode root) {
    -        maxSumNew(root);
    -        return ansNew;
    +    Leetcode 124 二叉树中的最大路径和(Binary Tree Maximum Path Sum) 题解分析
    +    /2021/01/24/Leetcode-124-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C-Binary-Tree-Maximum-Path-Sum-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/
    +    题目介绍

    A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

    +

    The path sum of a path is the sum of the node’s values in the path.

    +

    Given the root of a binary tree, return the maximum path sum of any path.

    +

    路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

    +

    路径和 是路径中各节点值的总和。

    +

    给你一个二叉树的根节点 root ,返回其 最大路径和

    +

    简要分析

    其实这个题目会被误解成比较简单,左子树最大的,或者右子树最大的,或者两边加一下,仔细想想都不对,其实有可能是产生于左子树中,或者右子树中,这两个都是指跟左子树根还有右子树根没关系的,这么说感觉不太容易理解,画个图

    可以看到图里,其实最长路径和是左边这个子树组成的,跟根节点还有右子树完全没关系,然后再想一种情况,如果是整棵树就是图中的左子树,那么这个最长路径和就是左子树加右子树加根节点了,所以不是我一开始想得那么简单,在代码实现中也需要一些技巧

    +

    代码

    int ansNew = Integer.MIN_VALUE;
    +public int maxPathSum(TreeNode root) {
    +        maxSumNew(root);
    +        return ansNew;
         }
         
     public int maxSumNew(TreeNode root) {
    @@ -3150,78 +2883,58 @@ inorder = [9,3,15,20,7]