diff --git a/2019/06/18/openresty/index.html b/2019/06/18/openresty/index.html index b337f8f883..c22a56e946 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/08/02/聊聊-Java-自带的那些逆天工具/index.html b/2020/08/02/聊聊-Java-自带的那些逆天工具/index.html index 52d3004661..848affae2b 100644 --- a/2020/08/02/聊聊-Java-自带的那些逆天工具/index.html +++ b/2020/08/02/聊聊-Java-自带的那些逆天工具/index.html @@ -1,4 +1,4 @@ -聊聊 Java 自带的那些*逆天*工具 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Java 自带的那些*逆天*工具

    原谅我的标题党,其实这些工具的确很厉害,之前其实介绍过一点相关的,是从我一次问题排查的过程中用到的,但是最近又有碰到一次排查问题,发现其实用 idea 直接 dump thread 是不现实的,毕竟服务器环境的没法这么操作,那就得用 Java 的那些工具了

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    public static void main(String[] args) throws InterruptedException {
    +聊聊 Java 自带的那些*逆天*工具 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Java 自带的那些*逆天*工具

    原谅我的标题党,其实这些工具的确很厉害,之前其实介绍过一点相关的,是从我一次问题排查的过程中用到的,但是最近又有碰到一次排查问题,发现其实用 idea 直接 dump thread 是不现实的,毕竟服务器环境的没法这么操作,那就得用 Java 的那些工具了

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    public static void main(String[] args) throws InterruptedException {
             SpringApplication.run(ThreadDumpDemoApplication.class, args);
             ReentrantLock lock1 = new ReentrantLock();
             ReentrantLock lock2 = new ReentrantLock();
    @@ -495,4 +495,4 @@ JNI global references: java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
             at com.nicksxs.thread_dump_demo.ThreadDumpDemoApplication$1.run(ThreadDumpDemoApplication.java:22)
     
    -Found 1 deadlock.

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    小tips

    对于一些应用内存已经占满了,jstack 和 jmap 可能会连不上的情况,可以使用-F参数强制打印线程或者 dump 文件,但是要注意这两者使用的用户必须与 java 进程启动用户一致,并且使用的 jdk 也要一致

    \ No newline at end of file +Found 1 deadlock.

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    小tips

    对于一些应用内存已经占满了,jstack 和 jmap 可能会连不上的情况,可以使用-F参数强制打印线程或者 dump 文件,但是要注意这两者使用的用户必须与 java 进程启动用户一致,并且使用的 jdk 也要一致

    \ No newline at end of file diff --git a/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html b/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html index f159680504..17ca442549 100644 --- a/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html +++ b/2020/08/06/Linux-下-grep-命令的一点小技巧/index.html @@ -1,5 +1,5 @@ -Linux 下 grep 命令的一点小技巧 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Linux 下 grep 命令的一点小技巧

    用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

    譬如

    
    +Linux 下 grep 命令的一点小技巧 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Linux 下 grep 命令的一点小技巧

    用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

    譬如

    
     grep 'xxx' xxxx.log
     

    然后有挺多情况比如想要找日志里带一些符号什么的,就需要用到一些特殊的

    比如这样\"userId\":\"123456\",因为比如用户 ID 有时候会跟其他的 id 一样,只用具体的值 123456 来查的话干扰信息太多了,如果直接这样

    
     grep '\"userId\":\"123456\"' xxxx.log
    -

    好像不行,盲猜就是符号的问题,特别是\"这两个,

    之前一直是想试一下,但是没成功,昨天在排查一个问题的时候发现了,只要把这些都转义了就行了

    grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log

    \ No newline at end of file +

    好像不行,盲猜就是符号的问题,特别是\"这两个,

    之前一直是想试一下,但是没成功,昨天在排查一个问题的时候发现了,只要把这些都转义了就行了

    grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log

    \ No newline at end of file diff --git a/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/index.html b/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/index.html index 4765d008e0..07e1ee3450 100644 --- a/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/index.html +++ b/2020/08/22/Filter-Intercepter-Aop-啥-啥-啥-这些都是啥/index.html @@ -1,4 +1,4 @@ -Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥? | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?

    本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    
    +Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥? | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?

    本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    
     package javax.servlet;
     
     import java.io.IOException;
    @@ -160,7 +160,7 @@
          */
         public void destroy();
     }
    -

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    Filter

    // ---------------------------------------------------- FilterChain Methods
    +

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    Filter

    // ---------------------------------------------------- FilterChain Methods
     
         /**
          * Invoke the next filter in this chain, passing the specified request
    @@ -428,4 +428,4 @@
         public String hello() {
             return "hello world";
         }
    -}

    好了,请求一下,看看 stdout,

    搞定完事儿~

    \ No newline at end of file +}

    好了,请求一下,看看 stdout,

    搞定完事儿~

    \ No newline at end of file diff --git a/2020/09/06/mybatis-的-和-是有啥区别/index.html b/2020/09/06/mybatis-的-和-是有啥区别/index.html index 4970b6fc99..4df8b7eef8 100644 --- a/2020/09/06/mybatis-的-和-是有啥区别/index.html +++ b/2020/09/06/mybatis-的-和-是有啥区别/index.html @@ -1,4 +1,4 @@ -mybatis 的 $ 和 # 是有啥区别 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    mybatis 的 $ 和 # 是有啥区别

    这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
    +mybatis 的 $ 和 # 是有啥区别 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    mybatis 的 $ 和 # 是有啥区别

    这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
     public SqlSource parseScriptNode() {
         MixedSqlNode rootSqlNode = parseDynamicTags(context);
         SqlSource sqlSource;
    @@ -69,4 +69,4 @@ public class DynamicSqlSource implements SqlSource {
       }
     
     }
    -

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    \ No newline at end of file +

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    \ No newline at end of file diff --git a/2020/11/22/聊聊-Dubbo-的容错机制/index.html b/2020/11/22/聊聊-Dubbo-的容错机制/index.html index 977728e40c..d6893a63f2 100644 --- a/2020/11/22/聊聊-Dubbo-的容错机制/index.html +++ b/2020/11/22/聊聊-Dubbo-的容错机制/index.html @@ -1,4 +1,4 @@ -聊聊 Dubbo 的容错机制 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Dubbo 的容错机制

    之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
    • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
    • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
    • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
    • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

    集群容错模式

    Failover Cluster

    失败自动切换,当出现失败,重试其它服务器 1。通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。

    重试次数配置如下:

    <dubbo:service retries=”2” />
    这里重点看下 Failover Cluster集群模式的实现

    public class FailoverCluster implements Cluster {
    +聊聊 Dubbo 的容错机制 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Dubbo 的容错机制

    之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
    • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
    • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
    • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
    • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

    集群容错模式

    Failover Cluster

    失败自动切换,当出现失败,重试其它服务器 1。通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。

    重试次数配置如下:

    <dubbo:service retries=”2” />
    这里重点看下 Failover Cluster集群模式的实现

    public class FailoverCluster implements Cluster {
     
         public final static String NAME = "failover";
     
    diff --git a/2020/11/29/从清华美院学姐聊聊我们身边的恶人/index.html b/2020/11/29/从清华美院学姐聊聊我们身边的恶人/index.html
    index 1b5783b9fd..efb5861716 100644
    --- a/2020/11/29/从清华美院学姐聊聊我们身边的恶人/index.html
    +++ b/2020/11/29/从清华美院学姐聊聊我们身边的恶人/index.html
    @@ -1 +1 @@
    -从清华美院学姐聊聊我们身边的恶人 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    从清华美院学姐聊聊我们身边的恶人

    前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    \ No newline at end of file +从清华美院学姐聊聊我们身边的恶人 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    从清华美院学姐聊聊我们身边的恶人

    前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    \ No newline at end of file diff --git a/2020/12/27/聊聊-mysql-索引的一些细节/index.html b/2020/12/27/聊聊-mysql-索引的一些细节/index.html index d72565b63c..f2c56a1e68 100644 --- a/2020/12/27/聊聊-mysql-索引的一些细节/index.html +++ b/2020/12/27/聊聊-mysql-索引的一些细节/index.html @@ -1,4 +1,4 @@ -聊聊 mysql 索引的一些细节 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 mysql 索引的一些细节

    前几天同事问了我个 mysql 索引的问题,虽然大概知道,但是还是想来实践下,就是 is null,is not null 这类查询是否能用索引,可能之前有些网上的文章说都是不能用索引,但是其实不是,我们来看个小试验

    CREATE TABLE `null_index_t` (
    +聊聊 mysql 索引的一些细节 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 mysql 索引的一些细节

    前几天同事问了我个 mysql 索引的问题,虽然大概知道,但是还是想来实践下,就是 is null,is not null 这类查询是否能用索引,可能之前有些网上的文章说都是不能用索引,但是其实不是,我们来看个小试验

    CREATE TABLE `null_index_t` (
       `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
       `null_key` varchar(255) DEFAULT NULL,
       `null_key1` varchar(255) DEFAULT NULL,
    @@ -31,4 +31,4 @@
     end while;
     end 
     $
    -call nullIndex1();

    然后看下我们的 is null 查询

    EXPLAIN select * from null_index_t WHERE null_key is null;


    再来看看另一个

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    从这里能看出来啥呢,可以思考下

    从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
    我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

    然后再来看看执行结果

    EXPLAIN select * from null_index_t WHERE null_key is null;

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
    其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

    \ No newline at end of file +call nullIndex1();

    然后看下我们的 is null 查询

    EXPLAIN select * from null_index_t WHERE null_key is null;


    再来看看另一个

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    从这里能看出来啥呢,可以思考下

    从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
    我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

    然后再来看看执行结果

    EXPLAIN select * from null_index_t WHERE null_key is null;

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
    其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

    \ 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 0d7616d348..57f42db3ee 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) {
    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 e0f2d1d717..6b9b0b29ee 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/2021/02/21/AQS-之-Condition-浅析笔记/index.html b/2021/02/21/AQS-之-Condition-浅析笔记/index.html index 8de9c48285..bd2330af4f 100644 --- a/2021/02/21/AQS-之-Condition-浅析笔记/index.html +++ b/2021/02/21/AQS-之-Condition-浅析笔记/index.html @@ -1,4 +1,4 @@ -AQS篇二 之 Condition 浅析笔记 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    AQS篇二 之 Condition 浅析笔记

    Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

    import java.util.concurrent.locks.Condition;
    +AQS篇二 之 Condition 浅析笔记 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    AQS篇二 之 Condition 浅析笔记

    Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

    import java.util.concurrent.locks.Condition;
     import java.util.concurrent.locks.Lock;
     import java.util.concurrent.locks.ReentrantLock;
     
    @@ -48,7 +48,7 @@
             /** First node of condition queue. */
             private transient Node firstWaiter;
             /** Last node of condition queue. */
    -        private transient Node lastWaiter;

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    /**
    +        private transient Node lastWaiter;

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    /**
      * Implements interruptible condition wait.
      * <ol>
      * <li> If current thread is interrupted, throw InterruptedException.
    diff --git a/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/index.html b/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/index.html
    index 84f694e798..f02366a7d3 100644
    --- a/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/index.html
    +++ b/2021/03/07/《垃圾回收算法手册读书》笔记之整理算法/index.html
    @@ -1 +1 @@
    -《垃圾回收算法手册读书》笔记之整理算法 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    《垃圾回收算法手册读书》笔记之整理算法

    最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    \ No newline at end of file +《垃圾回收算法手册读书》笔记之整理算法 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    《垃圾回收算法手册读书》笔记之整理算法

    最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    \ No newline at end of file diff --git a/2021/03/28/聊聊-Linux-下的-top-命令/index.html b/2021/03/28/聊聊-Linux-下的-top-命令/index.html index 86b8be8ba9..2354b30c82 100644 --- a/2021/03/28/聊聊-Linux-下的-top-命令/index.html +++ b/2021/03/28/聊聊-Linux-下的-top-命令/index.html @@ -1,6 +1,6 @@ -聊聊 Linux 下的 top 命令 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Linux 下的 top 命令

    top 命令在日常的 Linux 使用中,特别是做一些服务器的简单状态查看,排查故障都起了比较大的作用,但是由于这个命令看到的东西比较多,一般只会看部分,或者说像我这样就会比较片面地看一些信息,比如默认是进程维度的,可以在启动命令的时候加-H进入线程模式

    -H  :Threads-mode operation
    +聊聊 Linux 下的 top 命令 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 Linux 下的 top 命令

    top 命令在日常的 Linux 使用中,特别是做一些服务器的简单状态查看,排查故障都起了比较大的作用,但是由于这个命令看到的东西比较多,一般只会看部分,或者说像我这样就会比较片面地看一些信息,比如默认是进程维度的,可以在启动命令的时候加-H进入线程模式

    -H  :Threads-mode operation
                 Instructs top to display individual threads.  Without this command-line option a summation of all threads in each process  is  shown.   Later
    -            this can be changed with the `H' interactive command.

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    SORTING of task window
    +            this can be changed with the `H' interactive command.

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    SORTING of task window
     
               For  compatibility,  this top supports most of the former top sort keys.  Since this is primarily a service to former top users, these commands
               do not appear on any help screen.
    diff --git a/2021/03/31/2020-年终总结/index.html b/2021/03/31/2020-年终总结/index.html
    index a62b29932f..27b67400ee 100644
    --- a/2021/03/31/2020-年终总结/index.html
    +++ b/2021/03/31/2020-年终总结/index.html
    @@ -1 +1 @@
    -2020 年终总结 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    2020 年终总结

    拖更原因

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

    生活-健身跑步

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

    技术成长

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

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

    博客

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

    \ No newline at end of file +2020 年终总结 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    2020 年终总结

    拖更原因

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

    生活-健身跑步

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

    技术成长

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

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

    博客

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

    \ No newline at end of file diff --git a/2021/04/04/聊聊-dubbo-的线程池/index.html b/2021/04/04/聊聊-dubbo-的线程池/index.html index fab2fc0929..2126e3e6c1 100644 --- a/2021/04/04/聊聊-dubbo-的线程池/index.html +++ b/2021/04/04/聊聊-dubbo-的线程池/index.html @@ -1,4 +1,4 @@ -聊聊 dubbo 的线程池 | Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    聊聊 dubbo 的线程池

    之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
    看下代码目录主要是这几个

    \ No newline at end of file diff --git a/page/14/index.html b/page/14/index.html index 9fc9f20b2e..9ae1c82d68 100644 --- a/page/14/index.html +++ b/page/14/index.html @@ -1,4 +1,4 @@ -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    这几天去了趟厦门,原来几年前就想去了,本来都请好假了,后面因为一些事情没去成,这次刚好公司组织,就跟 LD 一起去了厦门,也不洋洋洒洒地写游记了,后面可能会有,今天先来总结下好的地方和比较坑的地方。
    这次主要去了中山路、鼓浪屿、曾厝(cuo)垵、植物园、灵玲马戏团,因为住的离环岛路比较近,还有幸现场看了下厦门马拉松,其中

    中山路

    这里看上去是有点民国时期的建筑风格,部分像那种电视里的租界啥的,不过这次去的时候都在翻修,路一大半拦起来了,听导游说这里往里面走有个局口街,然后上次听前同事说厦门比较有名的就是沙茶面和海蛎煎,不出意料的不太爱吃,沙茶面比较普通,可能是没吃到正宗的,海蛎煎吃不惯,倒是有个大叔的沙茶里脊还不错,在局口街那,还有小哥在那拍,应该也算是个网红打卡点了,然后吃了个油条麻糍也还不错,总体如果是看建筑的话可能最近不是个好时间,个人也没这方面爱好,吃的话最好多打听打听沙茶面跟海蛎煎哪里正宗。如果不知道哪家好吃,也不爱看这类建筑的可以排个坑。

    鼓浪屿

    鼓浪屿也是完全没啥概念,需要乘船过去,但是只要二十分钟,岛上没有机动车,基本都靠走,有几个比较有名的地方,菽庄花园,里面有钢琴博物馆,对这个感兴趣的可以去看看,旁边是沙滩还可以逛逛,然后有各种博物馆,风琴啥的,岛上最大的特色是巷子多,道听途说有三百多条小巷,还有几个网红打卡点,周杰伦晴天墙,还有个最美转角,都是挤满了人排队打卡拍照,不过如果不着急,慢慢悠悠逛逛还是不错的,比较推荐,推荐值☆☆

    曾厝垵

    一直读不对这个字,都是叫:那个曾什么垵,愧对语文老师,这里到算是意外之喜,鼓浪屿回来已经挺累了,不过由于比较饿(什么原因后面说),并且离住的地方不远,就过去逛了逛,东西还蛮好吃的,芒果挺便宜,一大杯才十块,无骨鸡爪很贵,不是特别爱,臭豆腐不错的,也不算很贵,这里想起来,那边八婆婆的豆乳烧仙草还不错的,去中山路那会喝了,来曾厝垵也买了,奶茶爱好者可以试试,含糖量应该很高,不爱甜食或者减肥的同学慎重考虑好了再尝试,晚上那边从牌坊出来,沿着环岛路挺多夜宵店什么的,非常推荐,推荐值☆☆☆☆

    植物园

    植物园还是挺名副其实的,有热带植物,沙漠多肉,因为赶时间逛得不多,热带雨林植物那太多人了,都是在那拍照,而且我指的拍照都是拍人照,本身就很小的路,各种十八线网红,或者普通游客在那摆 pose 拍照,挺无语的;沙漠多肉比较惊喜,好多比人高的仙人掌,一大片的仙人球,很可恶的是好多大仙人掌上都有人刻字,越来越体会到,我们社会人多了,什么样的都有,而且不少;还看了下百花厅,但没什么特别的,可能赶时间比较着急,没仔细看,比较推荐,推荐值☆☆☆

    灵玲马戏团

    对这个其实比较排斥,主要是比较晚了,跑的有点远(我太懒了),一开始真的挺拉低体验感受的,上来个什么书法家,现场画马,卖画;不过后面的还算值回票价,主题是花木兰,空中动作应该很考验基本功,然后那些老外的飞轮还跳绳(不知道学名叫啥),动物那块不太忍心看,应该是吃了不少苦头,不过人都这样就往后点再心疼动物吧。

    总结

    厦门是个非常适合干饭人的地方,吃饭的地方大部分是差不多一桌菜十个左右就完了,而且上来就一大碗饭,一瓶雪碧一瓶可乐,对于经常是家里跟亲戚吃饭都得十几二十个菜的乡下人来说,不太吃得惯这样的🤦‍♂️,当然很有可能是我们预算不足,点的差。但是有一点是我回杭州深有感触的,感觉杭州司机的素质真的是跟厦门的司机差了比较多,杭州除非公交车停了,否则人行道很难看到主动让人的,当然这里拿厦门这个旅游城市来对比也不是很公平,不过这也是体现城市现代化文明水平的一个维度吧。

    之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
    看下代码目录主要是这几个

    • FixedThreadPool:创建一个复用固定个数线程的线程池。
      简单看下代码
      public Executor getExecutor(URL url) {
      +Nicksxs's Blog

      Nicksxs's Blog

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

      0%

      这几天去了趟厦门,原来几年前就想去了,本来都请好假了,后面因为一些事情没去成,这次刚好公司组织,就跟 LD 一起去了厦门,也不洋洋洒洒地写游记了,后面可能会有,今天先来总结下好的地方和比较坑的地方。
      这次主要去了中山路、鼓浪屿、曾厝(cuo)垵、植物园、灵玲马戏团,因为住的离环岛路比较近,还有幸现场看了下厦门马拉松,其中

      中山路

      这里看上去是有点民国时期的建筑风格,部分像那种电视里的租界啥的,不过这次去的时候都在翻修,路一大半拦起来了,听导游说这里往里面走有个局口街,然后上次听前同事说厦门比较有名的就是沙茶面和海蛎煎,不出意料的不太爱吃,沙茶面比较普通,可能是没吃到正宗的,海蛎煎吃不惯,倒是有个大叔的沙茶里脊还不错,在局口街那,还有小哥在那拍,应该也算是个网红打卡点了,然后吃了个油条麻糍也还不错,总体如果是看建筑的话可能最近不是个好时间,个人也没这方面爱好,吃的话最好多打听打听沙茶面跟海蛎煎哪里正宗。如果不知道哪家好吃,也不爱看这类建筑的可以排个坑。

      鼓浪屿

      鼓浪屿也是完全没啥概念,需要乘船过去,但是只要二十分钟,岛上没有机动车,基本都靠走,有几个比较有名的地方,菽庄花园,里面有钢琴博物馆,对这个感兴趣的可以去看看,旁边是沙滩还可以逛逛,然后有各种博物馆,风琴啥的,岛上最大的特色是巷子多,道听途说有三百多条小巷,还有几个网红打卡点,周杰伦晴天墙,还有个最美转角,都是挤满了人排队打卡拍照,不过如果不着急,慢慢悠悠逛逛还是不错的,比较推荐,推荐值☆☆

      曾厝垵

      一直读不对这个字,都是叫:那个曾什么垵,愧对语文老师,这里到算是意外之喜,鼓浪屿回来已经挺累了,不过由于比较饿(什么原因后面说),并且离住的地方不远,就过去逛了逛,东西还蛮好吃的,芒果挺便宜,一大杯才十块,无骨鸡爪很贵,不是特别爱,臭豆腐不错的,也不算很贵,这里想起来,那边八婆婆的豆乳烧仙草还不错的,去中山路那会喝了,来曾厝垵也买了,奶茶爱好者可以试试,含糖量应该很高,不爱甜食或者减肥的同学慎重考虑好了再尝试,晚上那边从牌坊出来,沿着环岛路挺多夜宵店什么的,非常推荐,推荐值☆☆☆☆

      植物园

      植物园还是挺名副其实的,有热带植物,沙漠多肉,因为赶时间逛得不多,热带雨林植物那太多人了,都是在那拍照,而且我指的拍照都是拍人照,本身就很小的路,各种十八线网红,或者普通游客在那摆 pose 拍照,挺无语的;沙漠多肉比较惊喜,好多比人高的仙人掌,一大片的仙人球,很可恶的是好多大仙人掌上都有人刻字,越来越体会到,我们社会人多了,什么样的都有,而且不少;还看了下百花厅,但没什么特别的,可能赶时间比较着急,没仔细看,比较推荐,推荐值☆☆☆

      灵玲马戏团

      对这个其实比较排斥,主要是比较晚了,跑的有点远(我太懒了),一开始真的挺拉低体验感受的,上来个什么书法家,现场画马,卖画;不过后面的还算值回票价,主题是花木兰,空中动作应该很考验基本功,然后那些老外的飞轮还跳绳(不知道学名叫啥),动物那块不太忍心看,应该是吃了不少苦头,不过人都这样就往后点再心疼动物吧。

      总结

      厦门是个非常适合干饭人的地方,吃饭的地方大部分是差不多一桌菜十个左右就完了,而且上来就一大碗饭,一瓶雪碧一瓶可乐,对于经常是家里跟亲戚吃饭都得十几二十个菜的乡下人来说,不太吃得惯这样的🤦‍♂️,当然很有可能是我们预算不足,点的差。但是有一点是我回杭州深有感触的,感觉杭州司机的素质真的是跟厦门的司机差了比较多,杭州除非公交车停了,否则人行道很难看到主动让人的,当然这里拿厦门这个旅游城市来对比也不是很公平,不过这也是体现城市现代化文明水平的一个维度吧。

      之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
      看下代码目录主要是这几个

      • FixedThreadPool:创建一个复用固定个数线程的线程池。
        简单看下代码
        public Executor getExecutor(URL url) {
                 String name = url.getParameter("threadname", "Dubbo");
                 int threads = url.getParameter("threads", 200);
                 int queues = url.getParameter("queues", 0);
        @@ -26,9 +26,9 @@
                 int queues = url.getParameter("queues", 0);
                 int alive = url.getParameter("alive", 60000);
                 return new ThreadPoolExecutor(cores, threads, (long)alive, TimeUnit.MILLISECONDS, (BlockingQueue)(queues == 0 ? new SynchronousQueue() : (queues < 0 ? new LinkedBlockingQueue() : new LinkedBlockingQueue(queues))), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
        -    }
        这里可以看到线程池的配置,核心是 0,最大线程数是 2147483647,保活时间是一分钟
        只是非常简略的介绍下,有兴趣可以自行阅读代码。

      拖更原因

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

      生活-健身跑步

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

      技术成长

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

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

      博客

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

      top 命令在日常的 Linux 使用中,特别是做一些服务器的简单状态查看,排查故障都起了比较大的作用,但是由于这个命令看到的东西比较多,一般只会看部分,或者说像我这样就会比较片面地看一些信息,比如默认是进程维度的,可以在启动命令的时候加-H进入线程模式

      -H  :Threads-mode operation
      +    }
      这里可以看到线程池的配置,核心是 0,最大线程数是 2147483647,保活时间是一分钟
      只是非常简略的介绍下,有兴趣可以自行阅读代码。

    拖更原因

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

    生活-健身跑步

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

    技术成长

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

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

    博客

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

    top 命令在日常的 Linux 使用中,特别是做一些服务器的简单状态查看,排查故障都起了比较大的作用,但是由于这个命令看到的东西比较多,一般只会看部分,或者说像我这样就会比较片面地看一些信息,比如默认是进程维度的,可以在启动命令的时候加-H进入线程模式

    -H  :Threads-mode operation
                 Instructs top to display individual threads.  Without this command-line option a summation of all threads in each process  is  shown.   Later
    -            this can be changed with the `H' interactive command.

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    SORTING of task window
    +            this can be changed with the `H' interactive command.

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    SORTING of task window
     
               For  compatibility,  this top supports most of the former top sort keys.  Since this is primarily a service to former top users, these commands
               do not appear on any help screen.
    diff --git a/page/15/index.html b/page/15/index.html
    index 4f0492473f..3af7aec0f9 100644
    --- a/page/15/index.html
    +++ b/page/15/index.html
    @@ -19,7 +19,7 @@
             }
         }
         return maxSofar;
    -}

    总结下

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

    最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    新年开工开车来杭州,因为没有车位加限行今天来就没开车来了,从东站做公交回住的地方,这班神奇的车我之前也吐槽过了,有神奇的乘客和神奇的司机,因为基本上这班车是从我毕业就开始乘了,所以也算是比较熟悉了,以前总体感觉不太好的是乘坐时间太长了,不过这个也不能怪车,是我自己住得远(离东站),后来住到了现在的地方,也算是直达,并且 LD 比较喜欢直达的,不爱更快却要换乘的地铁,所以坐的频率比较高,也说过前面那些比较气人的乘客,自己不好好戴口罩,反而联合一起上车的乘客诽谤司机,说他要吃人了要打人了,也正是这个司机比较有意思,上车就让戴好口罩,还给大家讲,哪里哪里又有疫情了,我觉得其实这个司机还是不错的,特殊时期,对于这种公共交通,这样的确是比较负责任的做法,只是说话方式,语气这个因人而异,他也不是来伺候人的,而且这么一大车人,说了一遍不行,再说一遍,三遍以上了,嗓门大一点也属于正常的人的行为。
    还是说回今天要说的,今天这位司机我看着跟前面说的那位有点像,因为上车的时候比较暗没看清脸,主要原因是这位司机开车比较猛,比较急,然后车上因为这个时间点,比较多大学开学来的学生,拎着个行李箱,一开始是前面已经都站满了人,后面还有很多空位,因为后面没地方放行李箱,就因为这样前面站着的有几个就在说司机开慢点,结果司机貌似也没听进去,还是我行我素,过了会又有人说司机开稳一点,就在这个人说完没一会,停在红绿灯路口的车里,就有人问有没有垃圾桶,接着又让司机开门,说晕车太严重了,要下车,司机开了门,我望出去两个妹子下了车,好像在路边草丛吐了,前面开门下车的时候就有人说她们第一次来杭州,可能有点责怪司机开的不稳,也影响了杭州交通给新来杭州的人的感受,说完了事情经过,其实我有蛮多感触,对于杭州公交司机,我大概是大一来了没多久,陪室友去文三路买电脑就晕车,下车的时候在公交车站吐了,可能是从大学开始缺乏锻炼,又饮食不规律,更加容易晕车,大部分晕车我觉得都是我自己的原因,有时候是上车前吃太多了,或者早上起太早,没睡好,没吃东西,反正自己也是挺多原因的,说到司机的原因的话,我觉得可能这班车还算好的,最让我难受的还是上下班高峰的时候,因为经过的那条路是比较重要的主干道,路比较老比较窄,并且还有很多人行道,所以经常一脚油门连带着一脚刹车,真的很难受,这种算是我觉得真的是公交体验比较差的一点,但是这一点呢也不能完全怪公交司机,杭州的路政规划是很垃圾,没看错,是垃圾,所以总体结论是公交还行,主要是路政规划就是垃圾,包括这条主干道这么多人行道,并且两边都是老小区,老年人在上班高峰可能要买菜送娃或者其他事情,在通畅的情况下可能只需要六分钟的路程,有时候因为各种原因,半小时都开不完,扯开去一点,杭州的路,核心的高速说封就封,本来是高架可以直接通到城西,结果没造,到了路本已经很拥挤的时候开始来造隧道,各种破坏,隧道接高架的地方,无尽的加塞,对于我这样的小白司机来说真的是太恶心了,所以我一直想说的就是杭州这个地方房价领先基础设施十年,地铁,高架,高速通通不行,地面道路就更不行了。
    总结下,其实杭州的真正的公交体验差,应该还是路造成的,对于前面的那两位妹子来说,有可能是她们来自于公交司机都是开的特别稳,并且路况也很好的地方,也或者是我被虐习惯了🤦‍♂️

    Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

    import java.util.concurrent.locks.Condition;
    +}

    总结下

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

    最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    新年开工开车来杭州,因为没有车位加限行今天来就没开车来了,从东站做公交回住的地方,这班神奇的车我之前也吐槽过了,有神奇的乘客和神奇的司机,因为基本上这班车是从我毕业就开始乘了,所以也算是比较熟悉了,以前总体感觉不太好的是乘坐时间太长了,不过这个也不能怪车,是我自己住得远(离东站),后来住到了现在的地方,也算是直达,并且 LD 比较喜欢直达的,不爱更快却要换乘的地铁,所以坐的频率比较高,也说过前面那些比较气人的乘客,自己不好好戴口罩,反而联合一起上车的乘客诽谤司机,说他要吃人了要打人了,也正是这个司机比较有意思,上车就让戴好口罩,还给大家讲,哪里哪里又有疫情了,我觉得其实这个司机还是不错的,特殊时期,对于这种公共交通,这样的确是比较负责任的做法,只是说话方式,语气这个因人而异,他也不是来伺候人的,而且这么一大车人,说了一遍不行,再说一遍,三遍以上了,嗓门大一点也属于正常的人的行为。
    还是说回今天要说的,今天这位司机我看着跟前面说的那位有点像,因为上车的时候比较暗没看清脸,主要原因是这位司机开车比较猛,比较急,然后车上因为这个时间点,比较多大学开学来的学生,拎着个行李箱,一开始是前面已经都站满了人,后面还有很多空位,因为后面没地方放行李箱,就因为这样前面站着的有几个就在说司机开慢点,结果司机貌似也没听进去,还是我行我素,过了会又有人说司机开稳一点,就在这个人说完没一会,停在红绿灯路口的车里,就有人问有没有垃圾桶,接着又让司机开门,说晕车太严重了,要下车,司机开了门,我望出去两个妹子下了车,好像在路边草丛吐了,前面开门下车的时候就有人说她们第一次来杭州,可能有点责怪司机开的不稳,也影响了杭州交通给新来杭州的人的感受,说完了事情经过,其实我有蛮多感触,对于杭州公交司机,我大概是大一来了没多久,陪室友去文三路买电脑就晕车,下车的时候在公交车站吐了,可能是从大学开始缺乏锻炼,又饮食不规律,更加容易晕车,大部分晕车我觉得都是我自己的原因,有时候是上车前吃太多了,或者早上起太早,没睡好,没吃东西,反正自己也是挺多原因的,说到司机的原因的话,我觉得可能这班车还算好的,最让我难受的还是上下班高峰的时候,因为经过的那条路是比较重要的主干道,路比较老比较窄,并且还有很多人行道,所以经常一脚油门连带着一脚刹车,真的很难受,这种算是我觉得真的是公交体验比较差的一点,但是这一点呢也不能完全怪公交司机,杭州的路政规划是很垃圾,没看错,是垃圾,所以总体结论是公交还行,主要是路政规划就是垃圾,包括这条主干道这么多人行道,并且两边都是老小区,老年人在上班高峰可能要买菜送娃或者其他事情,在通畅的情况下可能只需要六分钟的路程,有时候因为各种原因,半小时都开不完,扯开去一点,杭州的路,核心的高速说封就封,本来是高架可以直接通到城西,结果没造,到了路本已经很拥挤的时候开始来造隧道,各种破坏,隧道接高架的地方,无尽的加塞,对于我这样的小白司机来说真的是太恶心了,所以我一直想说的就是杭州这个地方房价领先基础设施十年,地铁,高架,高速通通不行,地面道路就更不行了。
    总结下,其实杭州的真正的公交体验差,应该还是路造成的,对于前面的那两位妹子来说,有可能是她们来自于公交司机都是开的特别稳,并且路况也很好的地方,也或者是我被虐习惯了🤦‍♂️

    Condition也是 AQS 中很重要的一块内容,可以先看段示例代码,这段代码应该来自于Doug Lea大大,可以在 javadoc 中的 condition 部分找到,其实大大原来写过基于 synchronized 实现的,后面我也贴下代码

    import java.util.concurrent.locks.Condition;
     import java.util.concurrent.locks.Lock;
     import java.util.concurrent.locks.ReentrantLock;
     
    @@ -69,7 +69,7 @@
             /** First node of condition queue. */
             private transient Node firstWaiter;
             /** Last node of condition queue. */
    -        private transient Node lastWaiter;

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    /**
    +        private transient Node lastWaiter;

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    /**
      * Implements interruptible condition wait.
      * <ol>
      * <li> If current thread is interrupted, throw InterruptedException.
    diff --git a/page/16/index.html b/page/16/index.html
    index f3b218f04a..ff6d90e172 100644
    --- a/page/16/index.html
    +++ b/page/16/index.html
    @@ -1,4 +1,4 @@
    -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    最近群里大佬发起了一个读书打卡活动,需要每天读一会书,在群里打卡分享感悟,争取一个月能读完一本书,说实话一天十分钟的读书时间倒是问题不大,不过每天都要打卡,而且一个月要读完一本书,其实难度还是有点大的,不过也想试试看。
    之前某某老大给自己立了个 flag,说要读一百本书,这对我来说挺难实现的,一则我也不喜欢书只读一小半,二则感觉对于喜欢看的内容范围还是比较有限制,可能也算是比较矫情,不爱追热门的各类东西,因为往往会有一些跟大众不一致的观点看法,显得格格不入。所以还是这个打卡活动可能会比较适合我,书是人类进步的阶梯。
    到现在是打卡了三天了,读的主要是白岩松的《幸福了吗》,对于白岩松,我们这一代人是比较熟悉,并且整体印象比较不错的一个央视主持人,从《焦点访谈》开始,到疫情期间的各类一线节目,可能对我来说是个三观比较正,敢于说一些真话的主持人,这中间其实是有个空档期,没怎么看电视,也不太关注了,只是在疫情期间的节目,还是一如既往地给人一种可靠的感觉,正好有一次偶然微信读书推荐了白岩松的这本书,就看了一部分,正好这次继续往下看,因为相对来讲不会很晦涩,并且从这位知名央视主持人的角度分享他的过往和想法看法,还是比较有意思的。
    从对汶川地震,08 年奥运等往事的回忆和一些细节的呈现,也让我了解比较多当时所不知道的,特别是汶川地震,那时的我还在读高中,真的是看着电视,作为“猛男”都忍不住泪目了,共和国之殇,多难兴邦,但是这对于当事人来说,都是一场醒不过来的噩梦。
    然后是对于足球的热爱,其实光这个就能掰扯很多,因为我不爱足球,只爱篮球,其中原因有的没的也挺多可以说的,但是看了他的书,才能比较深入的了解一个足球迷,对足球,对中国足球,对世界杯,对阿根廷的感情。
    接下去还是想能继续坚持下去,加油!

    前面写过一系列的 redis 源码分析的,但是实际上很多的问题还是需要结合实际的使用,然后其实就避不开缓存使用的三个著名问题,穿透,击穿和雪崩,这三个概念也是有着千丝万缕的关系,

    缓存穿透

    缓存穿透是指当数据库中本身就不存在这个数据的时候,使用一般的缓存策略时访问不到缓存后就访问数据库,但是因为数据库也没数据,所以如果不做任何策略优化的话,这类数据就每次都会访问一次数据库,对数据库压力也会比较大。

    缓存击穿

    缓存击穿跟穿透比较类似的,都是访问缓存不在,然后去访问数据库,与穿透不一样的是击穿是在数据库中存在数据,但是可能由于第一次访问,或者缓存过期了,需要访问到数据库,这对于访问量小的情况其实算是个正常情况,但是随着请求量变高就会引发一些性能隐患。

    缓存雪崩

    缓存雪崩就是击穿的大规模集群效应,当大量的缓存过期失效的时候,这些请求都是直接访问到数据库了,会对数据库造成很大的压力。

    对于以上三种场景也有一些比较常见的解决方案,但也不能说是万无一失的,需要随着业务去寻找合适的方案

    解决缓存穿透

    对于数据库中就没这个数据的时候,一种是可以对这个 key 设置下空值,即以一个特定的表示是数据库不存在的,这种情况需要合理地调整过期时间,当这个 key 在数据库中有数据了的话,也需要有策略去更新这个值,并且如果这类 key 非常多,这个方法就会不太合适,就可以使用第二种方法,就是布隆过滤器,bloom filter,前置一个布隆过滤器,当这个 key 在数据库不存在的话,先用布隆过滤器挡一道,如果不在的话就直接返回了,当然布隆过滤器不是绝对的准确的

    解决缓存击穿

    当一个 key 的缓存过期了,如果大量请求过来访问这个 key,请求都会落在数据库里,这个时候就可以使用一些类似于互斥锁的方式去让一个线程去访问数据库,更新缓存,但是这里其实也有个问题,就是如果是热点 key 其实这种方式也比较危险,万一更新失败,或者更新操作的时候耗时比较久,就会有一大堆请求卡在那,这种情况可能需要有一些异步提前刷新缓存,可以结合具体场景选择方式

    解决缓存雪崩

    雪崩的情况是指大批量的 key 都一起过期了,击穿的放大版,大批量的请求都打到数据库上了,一方面有可能直接缓存不可用了,就需要用集群化高可用的缓存服务,然后对于实际使用中也可以使用本地缓存结合 redis 缓存,去提高可用性,再配合一些限流措施,然后就是缓存使用过程总的过期时间最好能加一些随机值,防止在同一时间过期而导致雪崩,结合互斥锁防止大量请求打到数据库。

    题目介绍

    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;
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    最近群里大佬发起了一个读书打卡活动,需要每天读一会书,在群里打卡分享感悟,争取一个月能读完一本书,说实话一天十分钟的读书时间倒是问题不大,不过每天都要打卡,而且一个月要读完一本书,其实难度还是有点大的,不过也想试试看。
    之前某某老大给自己立了个 flag,说要读一百本书,这对我来说挺难实现的,一则我也不喜欢书只读一小半,二则感觉对于喜欢看的内容范围还是比较有限制,可能也算是比较矫情,不爱追热门的各类东西,因为往往会有一些跟大众不一致的观点看法,显得格格不入。所以还是这个打卡活动可能会比较适合我,书是人类进步的阶梯。
    到现在是打卡了三天了,读的主要是白岩松的《幸福了吗》,对于白岩松,我们这一代人是比较熟悉,并且整体印象比较不错的一个央视主持人,从《焦点访谈》开始,到疫情期间的各类一线节目,可能对我来说是个三观比较正,敢于说一些真话的主持人,这中间其实是有个空档期,没怎么看电视,也不太关注了,只是在疫情期间的节目,还是一如既往地给人一种可靠的感觉,正好有一次偶然微信读书推荐了白岩松的这本书,就看了一部分,正好这次继续往下看,因为相对来讲不会很晦涩,并且从这位知名央视主持人的角度分享他的过往和想法看法,还是比较有意思的。
    从对汶川地震,08 年奥运等往事的回忆和一些细节的呈现,也让我了解比较多当时所不知道的,特别是汶川地震,那时的我还在读高中,真的是看着电视,作为“猛男”都忍不住泪目了,共和国之殇,多难兴邦,但是这对于当事人来说,都是一场醒不过来的噩梦。
    然后是对于足球的热爱,其实光这个就能掰扯很多,因为我不爱足球,只爱篮球,其中原因有的没的也挺多可以说的,但是看了他的书,才能比较深入的了解一个足球迷,对足球,对中国足球,对世界杯,对阿根廷的感情。
    接下去还是想能继续坚持下去,加油!

    前面写过一系列的 redis 源码分析的,但是实际上很多的问题还是需要结合实际的使用,然后其实就避不开缓存使用的三个著名问题,穿透,击穿和雪崩,这三个概念也是有着千丝万缕的关系,

    缓存穿透

    缓存穿透是指当数据库中本身就不存在这个数据的时候,使用一般的缓存策略时访问不到缓存后就访问数据库,但是因为数据库也没数据,所以如果不做任何策略优化的话,这类数据就每次都会访问一次数据库,对数据库压力也会比较大。

    缓存击穿

    缓存击穿跟穿透比较类似的,都是访问缓存不在,然后去访问数据库,与穿透不一样的是击穿是在数据库中存在数据,但是可能由于第一次访问,或者缓存过期了,需要访问到数据库,这对于访问量小的情况其实算是个正常情况,但是随着请求量变高就会引发一些性能隐患。

    缓存雪崩

    缓存雪崩就是击穿的大规模集群效应,当大量的缓存过期失效的时候,这些请求都是直接访问到数据库了,会对数据库造成很大的压力。

    对于以上三种场景也有一些比较常见的解决方案,但也不能说是万无一失的,需要随着业务去寻找合适的方案

    解决缓存穿透

    对于数据库中就没这个数据的时候,一种是可以对这个 key 设置下空值,即以一个特定的表示是数据库不存在的,这种情况需要合理地调整过期时间,当这个 key 在数据库中有数据了的话,也需要有策略去更新这个值,并且如果这类 key 非常多,这个方法就会不太合适,就可以使用第二种方法,就是布隆过滤器,bloom filter,前置一个布隆过滤器,当这个 key 在数据库不存在的话,先用布隆过滤器挡一道,如果不在的话就直接返回了,当然布隆过滤器不是绝对的准确的

    解决缓存击穿

    当一个 key 的缓存过期了,如果大量请求过来访问这个 key,请求都会落在数据库里,这个时候就可以使用一些类似于互斥锁的方式去让一个线程去访问数据库,更新缓存,但是这里其实也有个问题,就是如果是热点 key 其实这种方式也比较危险,万一更新失败,或者更新操作的时候耗时比较久,就会有一大堆请求卡在那,这种情况可能需要有一些异步提前刷新缓存,可以结合具体场景选择方式

    解决缓存雪崩

    雪崩的情况是指大批量的 key 都一起过期了,击穿的放大版,大批量的请求都打到数据库上了,一方面有可能直接缓存不可用了,就需要用集群化高可用的缓存服务,然后对于实际使用中也可以使用本地缓存结合 redis 缓存,去提高可用性,再配合一些限流措施,然后就是缓存使用过程总的过期时间最好能加一些随机值,防止在同一时间过期而导致雪崩,结合互斥锁防止大量请求打到数据库。

    题目介绍

    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,7 +21,7 @@
         int res = Math.max(left + right + root.val, currentSum);
         ans = Math.max(res, ans);
         return currentSum;
    -}

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

    结果图

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

    今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    题目介绍

    写一个程序找出两个单向链表的交叉起始点,可能是我英语不好,图里画的其实还有一点是交叉以后所有节点都是相同的
    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
    +}

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

    结果图

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

    今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    题目介绍

    写一个程序找出两个单向链表的交叉起始点,可能是我英语不好,图里画的其实还有一点是交叉以后所有节点都是相同的
    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) {
    diff --git a/page/17/index.html b/page/17/index.html
    index bbf7b6d509..e97ba51563 100644
    --- a/page/17/index.html
    +++ b/page/17/index.html
    @@ -53,7 +53,7 @@
     end while;
     end 
     $
    -call nullIndex1();

    然后看下我们的 is null 查询

    EXPLAIN select * from null_index_t WHERE null_key is null;


    再来看看另一个

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    从这里能看出来啥呢,可以思考下

    从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
    我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

    然后再来看看执行结果

    EXPLAIN select * from null_index_t WHERE null_key is null;

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
    其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

    几年前看了柴静的《穹顶之下》觉得这个记者调查得很深入,挺有水平,然后再看到了她跟丁仲礼的采访,其实没看完整,也没试着去理解,就觉得环境问题挺严重的,为啥柴静这个对面的这位好像对这个很不屑的样子,最近因为丁仲礼上了美国制裁名单,B 站又有人把这个视频发了出来,就完整看了下,就觉得自己挺惭愧的,就抱着对柴静的好感而没来由的否定了丁老的看法和说法,所以人也需要不断地学习,改正之前错误的观点,当然不是说我现在说的就是百分百正确,只是个人的一些浅显的见解

    先聊聊这个事情,整体看下来我的一些理解,IPCC给中国的方案其实是个很大的陷阱,它里面有几个隐藏的点是容易被我们外行忽略的,第一点是基数,首先发达国家目前(指2010年采访或者IPCC方案时间)的人均碳排放量已经是远高于发展中国家的了,这也就导致了所谓的发达国家承诺减排80%是个非常有诚意的承诺其实就是忽悠;第二点是碳排放是个累计过程,从1900年开始到2050年,或者说到2010年,发达国家已经排的量是远超过发展中国家的,这是非常不公平的;第三点其实是通过前两点推导出来的,也就是即使发达国家这么有诚意地说减排80%,扒开这层虚伪的外衣,其实是给他们11亿人分走了48%的碳排放量,留给发展中国家55亿人口的只剩下了52%;第四点,人是否因为国家的发达与否而应受到不平等待遇,如果按国家维度,丁老说的,摩纳哥要跟中国分同样的排放量么,中国人还算不算人;第五点,这点算是我自己想的,也可能是柴静屁股决定脑袋想不到的点,她作为一个物质生活条件已经足够好了,那么对于那些生活在物质条件平均线以下的,他们是否能像城里人那样有空调地暖,洗澡有热水器浴霸,上下班能开车,这些其实都直接或者间接地导致了碳排放;他们有没有改善物质生活条件地权利呢,并且再说回来,其实丁老也给了我们觉得合理地方案,我们保证不管发达国家不管减排多少,我们都不会超过他们的80%,我觉得这是真正的诚意,而不是接着减排80%的噱头来忽悠人,也是像丁老这样的专家才能看破这个陷阱,碳排放权其实就是发展权,就是人权,中国人就不是人了么,或者说站在贫困线以下的人民是否有改善物质条件的权利,而不是说像柴静这样,只是管她自己,可能觉得小孩因为空气污染导致身体不好,所以做了穹顶之下这个纪录片,想去改善这个事情,空气污染不是说对的,只是每个国家都有这个过程,如果不发展,哪里有资源去让人活得好,活得好了是前提,然后再去各方面都改善。

    对于这个问题其实更想说的是人的认知偏差,之前总觉得美帝是更自由民主,公平啥的,或者说觉得美帝啥都好,有种无脑愤青的感觉,外国的月亮比较圆,但是经历了像川普当选美国总统以来的各种魔幻操作,还有对于疫情的种种不可思议的美国民众的反应,其实更让人明白第一是外国的月亮没比较圆,第二是事情总是没那么简单粗暴非黑即白,美国不像原先设想地那么领先优秀,但是的确有很多方面是全球领先的,天朝也有体制所带来的优势,不可妄自菲薄,也不能忙不自大,还是要多学习知识,提升认知水平。

    题目介绍

    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]
    +call nullIndex1();

    然后看下我们的 is null 查询

    EXPLAIN select * from null_index_t WHERE null_key is null;


    再来看看另一个

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    从这里能看出来啥呢,可以思考下

    从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
    我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

    然后再来看看执行结果

    EXPLAIN select * from null_index_t WHERE null_key is null;

    EXPLAIN select * from null_index_t WHERE null_key is not null;


    是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
    其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

    几年前看了柴静的《穹顶之下》觉得这个记者调查得很深入,挺有水平,然后再看到了她跟丁仲礼的采访,其实没看完整,也没试着去理解,就觉得环境问题挺严重的,为啥柴静这个对面的这位好像对这个很不屑的样子,最近因为丁仲礼上了美国制裁名单,B 站又有人把这个视频发了出来,就完整看了下,就觉得自己挺惭愧的,就抱着对柴静的好感而没来由的否定了丁老的看法和说法,所以人也需要不断地学习,改正之前错误的观点,当然不是说我现在说的就是百分百正确,只是个人的一些浅显的见解

    先聊聊这个事情,整体看下来我的一些理解,IPCC给中国的方案其实是个很大的陷阱,它里面有几个隐藏的点是容易被我们外行忽略的,第一点是基数,首先发达国家目前(指2010年采访或者IPCC方案时间)的人均碳排放量已经是远高于发展中国家的了,这也就导致了所谓的发达国家承诺减排80%是个非常有诚意的承诺其实就是忽悠;第二点是碳排放是个累计过程,从1900年开始到2050年,或者说到2010年,发达国家已经排的量是远超过发展中国家的,这是非常不公平的;第三点其实是通过前两点推导出来的,也就是即使发达国家这么有诚意地说减排80%,扒开这层虚伪的外衣,其实是给他们11亿人分走了48%的碳排放量,留给发展中国家55亿人口的只剩下了52%;第四点,人是否因为国家的发达与否而应受到不平等待遇,如果按国家维度,丁老说的,摩纳哥要跟中国分同样的排放量么,中国人还算不算人;第五点,这点算是我自己想的,也可能是柴静屁股决定脑袋想不到的点,她作为一个物质生活条件已经足够好了,那么对于那些生活在物质条件平均线以下的,他们是否能像城里人那样有空调地暖,洗澡有热水器浴霸,上下班能开车,这些其实都直接或者间接地导致了碳排放;他们有没有改善物质生活条件地权利呢,并且再说回来,其实丁老也给了我们觉得合理地方案,我们保证不管发达国家不管减排多少,我们都不会超过他们的80%,我觉得这是真正的诚意,而不是接着减排80%的噱头来忽悠人,也是像丁老这样的专家才能看破这个陷阱,碳排放权其实就是发展权,就是人权,中国人就不是人了么,或者说站在贫困线以下的人民是否有改善物质条件的权利,而不是说像柴静这样,只是管她自己,可能觉得小孩因为空气污染导致身体不好,所以做了穹顶之下这个纪录片,想去改善这个事情,空气污染不是说对的,只是每个国家都有这个过程,如果不发展,哪里有资源去让人活得好,活得好了是前提,然后再去各方面都改善。

    对于这个问题其实更想说的是人的认知偏差,之前总觉得美帝是更自由民主,公平啥的,或者说觉得美帝啥都好,有种无脑愤青的感觉,外国的月亮比较圆,但是经历了像川普当选美国总统以来的各种魔幻操作,还有对于疫情的种种不可思议的美国民众的反应,其实更让人明白第一是外国的月亮没比较圆,第二是事情总是没那么简单粗暴非黑即白,美国不像原先设想地那么领先优秀,但是的确有很多方面是全球领先的,天朝也有体制所带来的优势,不可妄自菲薄,也不能忙不自大,还是要多学习知识,提升认知水平。

    题目介绍

    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
    diff --git a/page/18/index.html b/page/18/index.html
    index 4780c6f81e..49407f4a4d 100644
    --- a/page/18/index.html
    +++ b/page/18/index.html
    @@ -1,4 +1,4 @@
    -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
    • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
    • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
    • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
    • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

    集群容错模式

    Failover Cluster

    失败自动切换,当出现失败,重试其它服务器 1。通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。

    重试次数配置如下:

    <dubbo:service retries=”2” />
    这里重点看下 Failover Cluster集群模式的实现

    public class FailoverCluster implements Cluster {
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
    • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
    • ClusterDirectory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
    • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
    • LoadBalance 负责从多个 Invoker 中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

    集群容错模式

    Failover Cluster

    失败自动切换,当出现失败,重试其它服务器 1。通常用于读操作,但重试会带来更长延迟。可通过 retries=”2” 来设置重试次数(不含第一次)。

    重试次数配置如下:

    <dubbo:service retries=”2” />
    这里重点看下 Failover Cluster集群模式的实现

    public class FailoverCluster implements Cluster {
     
         public final static String NAME = "failover";
     
    diff --git a/page/20/index.html b/page/20/index.html
    index 03826088f3..8f2a0c440c 100644
    --- a/page/20/index.html
    +++ b/page/20/index.html
    @@ -38,7 +38,7 @@ Output: 0

    注释应该写的比较清楚了。

    小工记三

    前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
    差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
    下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

    这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
    +}

    注释应该写的比较清楚了。

    小工记三

    前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
    差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
    下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

    这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
     public SqlSource parseScriptNode() {
         MixedSqlNode rootSqlNode = parseDynamicTags(context);
         SqlSource sqlSource;
    @@ -109,7 +109,7 @@ public class DynamicSqlSource implements SqlSource {
       }
     
     }
    -

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    因为活实在比较多,也不太好叫大工(活比较杂散),相比上一次我跟 LD 俩人晚起了一点,我真的是只要有事,早上就醒的很早,准备八点出发的,六点就醒了,然后想继续睡就一直做梦🤦‍♂️,差不多八点半多到的丈人家,他们应该已经干了有一会了,我们到了以后就分配给我撬地板的活,上次说的那个敲掉柜子的房间里,还铺着质地还不错的木地板,但是也不想要了,得撬掉重新铺。
    拿着撬棍和榔头就上楼去干了,浙江这几天的天气,最高温度一般 38、9,楼上那个房间也没风扇,有了也不能用,都是灰尘,撬了两下,我感觉我体内的水就像真气爆发一样变成汗炸了出来,眼睛全被汗糊住了,可能大部分人不太了解地板是怎么铺的,一般是在地面先铺一层混凝土,混凝土中间嵌进去规则的长条木条,然后真正的地板一块块的都是钉在那个木条上,用那种气枪钉和普通的钉子,并且块跟块之前还有一个木头的槽结构相互耦合,然后边缘的一圈在用较薄的木板将整个木地板封边(这些词都是我现造的),边缘的用的钉子会更多,所以那几下真的很用力,而且撬地板,得蹲下起来,如此反复,对于我这个体重快超过身高的中年人来说的确是非常大的挑战,接下来继续撬了几个,已经有种要虚脱晕倒的感觉了,及时去喝水擦了汗,又歇了一会,为啥一上来就这么拼呢,主要是因为那个房间丈人在干活的时候是直接看得到的🤦‍♂️,后来被 LD 一顿教育,本来就是去帮忙的,又不是专业做这个的,急啥。
    喝了水之后,又稍稍歇了一会,就开始继续撬了,本来觉得这个地板撬着好像还行,房间不大,没多久就撬完了,撬完之后喝了点饮料(补充点糖分,早餐吃得少,有点低血糖),然后看到 LD 在撬下面的木条了,这个动作开始了那天最大的经验值收集行动,前面说了这个木条一般是跟混凝土一块铺上去的,但是谁也没想到,这个混凝土铺上去的时候竟然处理的这么随意,根本没考虑跟下面的贴合,所以撬木条的时候直接把木条跟木条中间大块大块的混凝土一块撬起来了,想想那重量,于是我这靠蛮力干活的,就用力把木条带着混凝土一块撬了起来,还沾沾自喜,但是发现结果是撬起来一块之后,体力值瞬间归零,上一篇我也提到了,其实干这类活也是很有技巧性的,但是上次的是我没学会,可能需要花时间学的,但是这次是LD 用她的纤细胳膊教会我的,我在撬的时候,屏住一口气,双手用力,起,大概是吃好几口奶的力气都用出来了,但是 LD 在我休息的时候,慢慢悠悠的,先把撬棍挤到木条或者混凝土跟下层的缝里,然后往下垫一小块混凝土碎石,然后轻轻松松的扳两下,就撬开了,亏我高中的时候引以为傲的物理成绩,作为物理课代表,这么浅显易懂的杠杆原理都完全不会用到生活里,后面在用这个技巧撬的过程中,真的觉得自己蠢到家了,当然在明白了用点杠杆原理之后,撬地板的活就变得慢慢悠悠,悠哉悠哉的了(其实还是很热的,披着毛巾擦眼睛)。
    上午的活差不多完了,后面就是把撬出来的混凝土和地板条丢下去,地上铺着不用了的被子,然后就是午饭和午休环节了,午饭换了一家快餐,味道非常可以,下午的活就比较单调了,帮忙清理了上去扔下来的混凝土碎块跟木条,然后稍微打扫了下,老丈人就让我们回家了,接着上次说的,还是觉得比跑步啥的消耗大太多了,那汗流的,一口就能喝完一瓶 500 毫升左右的矿泉水。

    本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    
    +

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    因为活实在比较多,也不太好叫大工(活比较杂散),相比上一次我跟 LD 俩人晚起了一点,我真的是只要有事,早上就醒的很早,准备八点出发的,六点就醒了,然后想继续睡就一直做梦🤦‍♂️,差不多八点半多到的丈人家,他们应该已经干了有一会了,我们到了以后就分配给我撬地板的活,上次说的那个敲掉柜子的房间里,还铺着质地还不错的木地板,但是也不想要了,得撬掉重新铺。
    拿着撬棍和榔头就上楼去干了,浙江这几天的天气,最高温度一般 38、9,楼上那个房间也没风扇,有了也不能用,都是灰尘,撬了两下,我感觉我体内的水就像真气爆发一样变成汗炸了出来,眼睛全被汗糊住了,可能大部分人不太了解地板是怎么铺的,一般是在地面先铺一层混凝土,混凝土中间嵌进去规则的长条木条,然后真正的地板一块块的都是钉在那个木条上,用那种气枪钉和普通的钉子,并且块跟块之前还有一个木头的槽结构相互耦合,然后边缘的一圈在用较薄的木板将整个木地板封边(这些词都是我现造的),边缘的用的钉子会更多,所以那几下真的很用力,而且撬地板,得蹲下起来,如此反复,对于我这个体重快超过身高的中年人来说的确是非常大的挑战,接下来继续撬了几个,已经有种要虚脱晕倒的感觉了,及时去喝水擦了汗,又歇了一会,为啥一上来就这么拼呢,主要是因为那个房间丈人在干活的时候是直接看得到的🤦‍♂️,后来被 LD 一顿教育,本来就是去帮忙的,又不是专业做这个的,急啥。
    喝了水之后,又稍稍歇了一会,就开始继续撬了,本来觉得这个地板撬着好像还行,房间不大,没多久就撬完了,撬完之后喝了点饮料(补充点糖分,早餐吃得少,有点低血糖),然后看到 LD 在撬下面的木条了,这个动作开始了那天最大的经验值收集行动,前面说了这个木条一般是跟混凝土一块铺上去的,但是谁也没想到,这个混凝土铺上去的时候竟然处理的这么随意,根本没考虑跟下面的贴合,所以撬木条的时候直接把木条跟木条中间大块大块的混凝土一块撬起来了,想想那重量,于是我这靠蛮力干活的,就用力把木条带着混凝土一块撬了起来,还沾沾自喜,但是发现结果是撬起来一块之后,体力值瞬间归零,上一篇我也提到了,其实干这类活也是很有技巧性的,但是上次的是我没学会,可能需要花时间学的,但是这次是LD 用她的纤细胳膊教会我的,我在撬的时候,屏住一口气,双手用力,起,大概是吃好几口奶的力气都用出来了,但是 LD 在我休息的时候,慢慢悠悠的,先把撬棍挤到木条或者混凝土跟下层的缝里,然后往下垫一小块混凝土碎石,然后轻轻松松的扳两下,就撬开了,亏我高中的时候引以为傲的物理成绩,作为物理课代表,这么浅显易懂的杠杆原理都完全不会用到生活里,后面在用这个技巧撬的过程中,真的觉得自己蠢到家了,当然在明白了用点杠杆原理之后,撬地板的活就变得慢慢悠悠,悠哉悠哉的了(其实还是很热的,披着毛巾擦眼睛)。
    上午的活差不多完了,后面就是把撬出来的混凝土和地板条丢下去,地上铺着不用了的被子,然后就是午饭和午休环节了,午饭换了一家快餐,味道非常可以,下午的活就比较单调了,帮忙清理了上去扔下来的混凝土碎块跟木条,然后稍微打扫了下,老丈人就让我们回家了,接着上次说的,还是觉得比跑步啥的消耗大太多了,那汗流的,一口就能喝完一瓶 500 毫升左右的矿泉水。

    本来是想取个像现在那些公众号转了又转的文章标题,”面试官再问你xxxxx,就把这篇文章甩给他看”这种标题,但是觉得实在太 low 了,还是用一部我比较喜欢的电影里的一句台词,《人在囧途》里王宝强对着那张老板给他的欠条,看不懂字时候说的那句,这些都是些啥(第四声)
    当我刚开始面 Java 的时候,其实我真的没注意这方面的东西,实话说就是不知道这些是啥,开发中用过 Interceptor和 Aop,了解 aop 的实现原理,但是不知道 Java web 中的 Filter 是怎么回事,知道 dubbo 的 filter,就这样,所以被问到了的确是回答不出来,可能就觉得这个渣渣,这么简单的都不会,所以还是花点时间来看看这个是个啥,为了避免我口吐芬芳,还是耐下性子来简单说下这几个东西
    首先是 servlet,怎么去解释这个呢,因为之前是 PHPer,所以比较喜欢用它来举例子,在普通的 PHP 的 web 应用中一般有几部分组成,接受 HTTP 请求的是前置的 nginx 或者 apache,但是这俩玩意都是只能处理静态的请求,远古时代 PHP 和 HTML 混编是通过 apache 的 php module,跟后来 nginx 使用 php-fpm 其实道理类似,就是把请求中需要 PHP 处理的转发给 PHP,在 Java 中呢,是有个比较牛叉的叫 Tomcat 的,它可以把请求转成 servlet,而 servlet 其实就是一种实现了特定接口的 Java 代码,

    
     package javax.servlet;
     
     import java.io.IOException;
    @@ -271,7 +271,7 @@ public class DynamicSqlSource implements SqlSource {
          */
         public void destroy();
     }
    -

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    Filter

    // ---------------------------------------------------- FilterChain Methods
    +

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    Filter

    // ---------------------------------------------------- FilterChain Methods
     
         /**
          * Invoke the next filter in this chain, passing the specified request
    @@ -539,4 +539,4 @@ public class DynamicSqlSource implements SqlSource {
         public String hello() {
             return "hello world";
         }
    -}

    好了,请求一下,看看 stdout,

    搞定完事儿~

    \ No newline at end of file +}

    好了,请求一下,看看 stdout,

    搞定完事儿~

    \ No newline at end of file diff --git a/page/21/index.html b/page/21/index.html index 500d801554..4dc173961e 100644 --- a/page/21/index.html +++ b/page/21/index.html @@ -1,8 +1,8 @@ -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
    这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
    下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
    最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

    用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

    譬如

    
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
    这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
    下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
    最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

    用了比较久的 grep 命令,其实都只是用了最最基本的功能来查日志,

    譬如

    
     grep 'xxx' xxxx.log
     

    然后有挺多情况比如想要找日志里带一些符号什么的,就需要用到一些特殊的

    比如这样\"userId\":\"123456\",因为比如用户 ID 有时候会跟其他的 id 一样,只用具体的值 123456 来查的话干扰信息太多了,如果直接这样

    
     grep '\"userId\":\"123456\"' xxxx.log
    -

    好像不行,盲猜就是符号的问题,特别是\"这两个,

    之前一直是想试一下,但是没成功,昨天在排查一个问题的时候发现了,只要把这些都转义了就行了

    grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log

    原谅我的标题党,其实这些工具的确很厉害,之前其实介绍过一点相关的,是从我一次问题排查的过程中用到的,但是最近又有碰到一次排查问题,发现其实用 idea 直接 dump thread 是不现实的,毕竟服务器环境的没法这么操作,那就得用 Java 的那些工具了

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    public static void main(String[] args) throws InterruptedException {
    +

    好像不行,盲猜就是符号的问题,特别是\"这两个,

    之前一直是想试一下,但是没成功,昨天在排查一个问题的时候发现了,只要把这些都转义了就行了

    grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log

    原谅我的标题党,其实这些工具的确很厉害,之前其实介绍过一点相关的,是从我一次问题排查的过程中用到的,但是最近又有碰到一次排查问题,发现其实用 idea 直接 dump thread 是不现实的,毕竟服务器环境的没法这么操作,那就得用 Java 的那些工具了

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    public static void main(String[] args) throws InterruptedException {
             SpringApplication.run(ThreadDumpDemoApplication.class, args);
             ReentrantLock lock1 = new ReentrantLock();
             ReentrantLock lock2 = new ReentrantLock();
    @@ -499,7 +499,7 @@ JNI global references: java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
             at com.nicksxs.thread_dump_demo.ThreadDumpDemoApplication$1.run(ThreadDumpDemoApplication.java:22)
     
    -Found 1 deadlock.

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    小tips

    对于一些应用内存已经占满了,jstack 和 jmap 可能会连不上的情况,可以使用-F参数强制打印线程或者 dump 文件,但是要注意这两者使用的用户必须与 java 进程启动用户一致,并且使用的 jdk 也要一致

    这周因为没有准备技术方面的内容加之之前有想分享下我和跑步的一些事情,我从小学开始就是个体育渣,因为体重大非常胖,小学的时候要做仰卧起坐,基本是一个都起不来,然后那时候跑步也是要我命那种,跟另外一个比较胖的同学在跑步队尾苟延残喘,只有立定跳远还行。

    时光飞逝,我在初中高中的时候因为爱打篮球,以为自己体质已经有了质的变化,所以在体育课跑步的时候妄图跟一位体育非常好的同学一起跑,结果跟的快断气了,最终还是确认了自己是个体育渣,特别是到了大学的第一次体测跑一千米,跑完直接吐了,一则是大学太宅不运动,二则的确是底子不好。那么怎么会去跑步了呢,其实也没什么特殊的原因,就是工作以后因为运动得更少,体质差,而且越来越胖,所以就想运动下,加之跑步也是我觉得成本最低的运动了,刚好那时候17 年租的地方附近小区周围的路车不太多,一圈刚好一公里多,就觉得开始跑跑看,其实想想以前觉得一千米是非常远的,学校塑胶跑道才 400 米,一千米要两圈半,太难了,但是后来在这个小区周围跑的时候好像跑了一圈以后还能再跑一点,最后跑了两圈,可把自己牛坏了,我都能跑两千米了,哈哈,这是个什么概念呢,大学里最让我绝望的两项体育相关的内容就是一千米和十二分钟跑,一千米把我跑吐了,十二分钟跑及格五圈半也能让我跑完花一周时间恢复以及提前一周心理压力爆炸,虽然我那时候跑的不快,但是已经能跑两千米了,瞬间让自己自信心爆炸,并且跑完步出完汗的感觉是非常棒的,毕竟吃奶茶鸡排都能心安理得了,谁叫我跑步了呢😄,其实现在回去看,那时候跑得还算快的,因为还比较瘦,现在要跑得那么快心跳就太快了,关于心跳什么的后面说,开始建立起自信心之后,对跑步这件事就开始不那么排斥跟害怕了,毕竟我能跑两千米了,然后试试三公里,哇,也可以了呢,三公里是什么概念呢,我大学里跑过最多的一次是十二分钟跑五圈半还是六圈,也就是两公里多,不到三公里,几乎是生涯最长了,一时间产生了一些我可能是个被埋没的运动天才的错觉,其实细想下也能明白,只是速度足够慢了就能跑多一点,毕竟提测一千米是要跑进四分钟才及格,自己跑的时候一千米跑六分多钟已经算不慢了(对我自己来说),但是即使是这样还是对把跑步坚持下去这件事有了很大的正面激励作用,并且由于那时候上下班骑车,整个体重控制的比较理想,导致一时间误会跑步就能非常快的减肥(其实这是我跑步历程中比较大的误区之一),因为会在跑步前后称下体重,如果跑个五公里(后面可以跑五公里了),可能体重就能降 0.5 千克,但实际上这只是这五公里跑步身体流失的水分,喝杯水就回来了,那时候能控制体重主要是骑车跟跑步一起的作用,并且工作压力相对来讲比较小,没有过劳肥。

    后面其实跑步慢慢变得一个比较习惯的运动了,从三公里,到五公里,到七公里,再到十公里,十公里差不多对我来说是个坎,一直还不能比较轻松的跑十公里,可能近一两年好了一些(原谅我只是跟自己比较,跟那些大神比差得不知道多远),其实对我来说每次都是个突破,因为其实与他人比较没有特别大意义,比较顶尖的差得太远,比较普通的也不行,都会打击自信心,比较比我差的就更没意义了,所以其实能挑战自己,能把自己的上限提高才是最有意义的,这也是我看着朋友圈里的一些大神的速度除了佩服赞叹之外没什么其他的惭愧或者说嫌弃自己的感觉(阿 Q 精神😄)。

    一直感性地觉得,跑步能解压,跑完浑身汗,有种把身体的负能量都排出去的感觉,也把吃太多的罪恶感排解掉了🤦‍♂️,之前朋友有看一本书,书名差不多叫越跑越接近自己,这个也是我觉得挺准确的一句话,当跑到接近极限了,还想再继续再跑一点,再跑一点就能突破自己上一次的最远记录了,再跑一点就能又一次突破自己了,成人以后,毕业以后,进入社会以后,世事总是难以件件顺遂,磕磕绊绊的往前走,总觉得要崩溃了,但是还是得坚持,再熬一下,再拼一下,可能还是失败,但人生呢,运气好的人和事总是小概率的,唯有面对挫折,还是日拱一卒,来日方长,我们再坚持下,没准下一次,没准再跑一会,就能突破自己,达到新的境界。

    另外个人后期对跑步的一些知识和理解也变得深入一些,比如伤膝盖,其实跑步的确伤膝盖,需要做一些准备和防护,最好的是适合自己的跑鞋和比较好的路(最好的是塑胶跑道了),也要注意热身跟跑后的拉伸(虽然我做的很差),还有就是注意心率,每个人有自己的适宜心率,我这就不冒充科普达人了,可以自行搜索关键字,先说到这吧~

    broker 的启动形式有点类似于 NameServer,都是服务类型的,跟 Consumer 差别比较大,

    首先是org.apache.rocketmq.broker.BrokerStartup中的 main 函数,org.apache.rocketmq.broker.BrokerStartup#createBrokerController基本就是读取参数,这里差点把最核心的初始化给漏了,

    final BrokerController controller = new BrokerController(
    +Found 1 deadlock.

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    小tips

    对于一些应用内存已经占满了,jstack 和 jmap 可能会连不上的情况,可以使用-F参数强制打印线程或者 dump 文件,但是要注意这两者使用的用户必须与 java 进程启动用户一致,并且使用的 jdk 也要一致

    这周因为没有准备技术方面的内容加之之前有想分享下我和跑步的一些事情,我从小学开始就是个体育渣,因为体重大非常胖,小学的时候要做仰卧起坐,基本是一个都起不来,然后那时候跑步也是要我命那种,跟另外一个比较胖的同学在跑步队尾苟延残喘,只有立定跳远还行。

    时光飞逝,我在初中高中的时候因为爱打篮球,以为自己体质已经有了质的变化,所以在体育课跑步的时候妄图跟一位体育非常好的同学一起跑,结果跟的快断气了,最终还是确认了自己是个体育渣,特别是到了大学的第一次体测跑一千米,跑完直接吐了,一则是大学太宅不运动,二则的确是底子不好。那么怎么会去跑步了呢,其实也没什么特殊的原因,就是工作以后因为运动得更少,体质差,而且越来越胖,所以就想运动下,加之跑步也是我觉得成本最低的运动了,刚好那时候17 年租的地方附近小区周围的路车不太多,一圈刚好一公里多,就觉得开始跑跑看,其实想想以前觉得一千米是非常远的,学校塑胶跑道才 400 米,一千米要两圈半,太难了,但是后来在这个小区周围跑的时候好像跑了一圈以后还能再跑一点,最后跑了两圈,可把自己牛坏了,我都能跑两千米了,哈哈,这是个什么概念呢,大学里最让我绝望的两项体育相关的内容就是一千米和十二分钟跑,一千米把我跑吐了,十二分钟跑及格五圈半也能让我跑完花一周时间恢复以及提前一周心理压力爆炸,虽然我那时候跑的不快,但是已经能跑两千米了,瞬间让自己自信心爆炸,并且跑完步出完汗的感觉是非常棒的,毕竟吃奶茶鸡排都能心安理得了,谁叫我跑步了呢😄,其实现在回去看,那时候跑得还算快的,因为还比较瘦,现在要跑得那么快心跳就太快了,关于心跳什么的后面说,开始建立起自信心之后,对跑步这件事就开始不那么排斥跟害怕了,毕竟我能跑两千米了,然后试试三公里,哇,也可以了呢,三公里是什么概念呢,我大学里跑过最多的一次是十二分钟跑五圈半还是六圈,也就是两公里多,不到三公里,几乎是生涯最长了,一时间产生了一些我可能是个被埋没的运动天才的错觉,其实细想下也能明白,只是速度足够慢了就能跑多一点,毕竟提测一千米是要跑进四分钟才及格,自己跑的时候一千米跑六分多钟已经算不慢了(对我自己来说),但是即使是这样还是对把跑步坚持下去这件事有了很大的正面激励作用,并且由于那时候上下班骑车,整个体重控制的比较理想,导致一时间误会跑步就能非常快的减肥(其实这是我跑步历程中比较大的误区之一),因为会在跑步前后称下体重,如果跑个五公里(后面可以跑五公里了),可能体重就能降 0.5 千克,但实际上这只是这五公里跑步身体流失的水分,喝杯水就回来了,那时候能控制体重主要是骑车跟跑步一起的作用,并且工作压力相对来讲比较小,没有过劳肥。

    后面其实跑步慢慢变得一个比较习惯的运动了,从三公里,到五公里,到七公里,再到十公里,十公里差不多对我来说是个坎,一直还不能比较轻松的跑十公里,可能近一两年好了一些(原谅我只是跟自己比较,跟那些大神比差得不知道多远),其实对我来说每次都是个突破,因为其实与他人比较没有特别大意义,比较顶尖的差得太远,比较普通的也不行,都会打击自信心,比较比我差的就更没意义了,所以其实能挑战自己,能把自己的上限提高才是最有意义的,这也是我看着朋友圈里的一些大神的速度除了佩服赞叹之外没什么其他的惭愧或者说嫌弃自己的感觉(阿 Q 精神😄)。

    一直感性地觉得,跑步能解压,跑完浑身汗,有种把身体的负能量都排出去的感觉,也把吃太多的罪恶感排解掉了🤦‍♂️,之前朋友有看一本书,书名差不多叫越跑越接近自己,这个也是我觉得挺准确的一句话,当跑到接近极限了,还想再继续再跑一点,再跑一点就能突破自己上一次的最远记录了,再跑一点就能又一次突破自己了,成人以后,毕业以后,进入社会以后,世事总是难以件件顺遂,磕磕绊绊的往前走,总觉得要崩溃了,但是还是得坚持,再熬一下,再拼一下,可能还是失败,但人生呢,运气好的人和事总是小概率的,唯有面对挫折,还是日拱一卒,来日方长,我们再坚持下,没准下一次,没准再跑一会,就能突破自己,达到新的境界。

    另外个人后期对跑步的一些知识和理解也变得深入一些,比如伤膝盖,其实跑步的确伤膝盖,需要做一些准备和防护,最好的是适合自己的跑鞋和比较好的路(最好的是塑胶跑道了),也要注意热身跟跑后的拉伸(虽然我做的很差),还有就是注意心率,每个人有自己的适宜心率,我这就不冒充科普达人了,可以自行搜索关键字,先说到这吧~

    broker 的启动形式有点类似于 NameServer,都是服务类型的,跟 Consumer 差别比较大,

    首先是org.apache.rocketmq.broker.BrokerStartup中的 main 函数,org.apache.rocketmq.broker.BrokerStartup#createBrokerController基本就是读取参数,这里差点把最核心的初始化给漏了,

    final BrokerController controller = new BrokerController(
                     brokerConfig,
                     nettyServerConfig,
                     nettyClientConfig,
    diff --git a/page/4/index.html b/page/4/index.html
    index acf2d0c842..e88abce305 100644
    --- a/page/4/index.html
    +++ b/page/4/index.html
    @@ -1,4 +1,4 @@
    -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    这里开始慢慢深入的讲一下 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
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    这里开始慢慢深入的讲一下 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;
     }
    @@ -90,4 +90,4 @@
                 Thread.sleep(1000);
             }
         }
    -}

    运行下可以看到运行结果

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

    scp 是个在服务器之间拷贝文件的一个常用命令,有时候有个场景是比如我们需要拷贝一些带有共同前缀的文件,但是有一个问题是比如我们有使用 zsh 的话,会出现一个报错,

    zsh: no matches found: root@100.100.100.100://root/prefix*

    这里就比较奇怪了,这个前缀的文件肯定是有的,这里其实是由于 zsh 会对 * 进行展开,这个可以在例如 ls 命令在使用中就可以发现 zsh 有这个特性
    需要使用双引号或单引号将路径包起来或者在*之前加反斜杠\来阻止对*展开和转义

    scp root@100.100.100.100://root/prefix* .

    通过使用双引号"进行转义

    scp root@100.100.100.100:"//root/prefix*" .

    或者可以将 shell 从 zsh 切换成 bash

    \ No newline at end of file +}

    运行下可以看到运行结果

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

    scp 是个在服务器之间拷贝文件的一个常用命令,有时候有个场景是比如我们需要拷贝一些带有共同前缀的文件,但是有一个问题是比如我们有使用 zsh 的话,会出现一个报错,

    zsh: no matches found: root@100.100.100.100://root/prefix*

    这里就比较奇怪了,这个前缀的文件肯定是有的,这里其实是由于 zsh 会对 * 进行展开,这个可以在例如 ls 命令在使用中就可以发现 zsh 有这个特性
    需要使用双引号或单引号将路径包起来或者在*之前加反斜杠\来阻止对*展开和转义

    scp root@100.100.100.100://root/prefix* .

    通过使用双引号"进行转义

    scp root@100.100.100.100:"//root/prefix*" .

    或者可以将 shell 从 zsh 切换成 bash

    \ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html index 5bd75e6e9e..a847496de5 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -1,4 +1,4 @@ -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前阵子一个同事因为发现某个分支上的代码好像有缺失导致无法正常运行,然后就对比了下把缺失的代码从另一个分支上拷了过来,可能有所欠考虑,不过主要是说下操作过程和最后的处理方法,这位同学的操作是改一些代码commit 一下,这样的 commit 了大概五六次,并且已经 push 到了远端,然后就在想要怎么去处理,在本地可以 reset,已经到远端了,一个很不优雅的操作就是本地 reset 了用 force push,这个当然是不可取的,然后就是 revert 了,但是又已经 commit 了好几次了,网上看了下,好像处理方法还挺成熟的,git revert 命令本质上就是一个逆向的 git cherry-pick 操作。 它将你提交中的变更的以完全相反的方式的应用到一个新创建的提交中,本质上就是撤销或者倒转。可以理解为就是提交一个反向的操作,这里其实我们可以用range revert来进行 git revert, 用法就是

    git revert OLDER_COMMIT^..NEWER_COMMIT

    这样就可以解决上面的问题了,但是还有个问题是这样会根据前面的 commit 数量提交对应数量个 revert commit 会显得比较乱,如果要比较干净的 commit 历史,
    可以看下 git revert 命令说明

    然后就可以用 -n 参数,表示不自动提交

    git revert -n OLDER_COMMIT^..NEWER_COMMIT
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前阵子一个同事因为发现某个分支上的代码好像有缺失导致无法正常运行,然后就对比了下把缺失的代码从另一个分支上拷了过来,可能有所欠考虑,不过主要是说下操作过程和最后的处理方法,这位同学的操作是改一些代码commit 一下,这样的 commit 了大概五六次,并且已经 push 到了远端,然后就在想要怎么去处理,在本地可以 reset,已经到远端了,一个很不优雅的操作就是本地 reset 了用 force push,这个当然是不可取的,然后就是 revert 了,但是又已经 commit 了好几次了,网上看了下,好像处理方法还挺成熟的,git revert 命令本质上就是一个逆向的 git cherry-pick 操作。 它将你提交中的变更的以完全相反的方式的应用到一个新创建的提交中,本质上就是撤销或者倒转。可以理解为就是提交一个反向的操作,这里其实我们可以用range revert来进行 git revert, 用法就是

    git revert OLDER_COMMIT^..NEWER_COMMIT

    这样就可以解决上面的问题了,但是还有个问题是这样会根据前面的 commit 数量提交对应数量个 revert commit 会显得比较乱,如果要比较干净的 commit 历史,
    可以看下 git revert 命令说明

    然后就可以用 -n 参数,表示不自动提交

    git revert -n OLDER_COMMIT^..NEWER_COMMIT
     git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"

    spring框架中如果想使用一些一部操作,除了依赖第三方中间件的消息队列,还可以用spring自己的event,简单介绍下使用方法
    首先我们可以建一个event,继承ApplicationEvent

    
     public class CustomSpringEvent extends ApplicationEvent {
     
    @@ -66,7 +66,7 @@
     @ResponseBody
     public void event() {
         customSpringEventPublisher.publishCustomEvent("hello sprint event");
    -}


    就能看到接收到消息了。

    又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

    工作篇

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

    Mysql 字符编码和排序规则

    这个一直是属于一知半解的状态,知道 utf8 跟 utf8mb4 的区别主要是能不能支持 emoji,但是具体后面配置的排序规则是用来干嘛,或者有什么区别,应该使用哪个,所以在 stackoverflow 上找了下,有两个比较不错的解答,就搬过来并且配合机翻做了点修改

    原文

    For those people still arriving at this question in 2020 or later, there are newer options that may be better than both of these. For example, utf8mb4_0900_ai_ci.

    All these collations are for the UTF-8 character encoding. The differences are in how text is sorted and compared.

    _unicode_ci and _general_ci are two different sets of rules for sorting and comparing text according to the way we expect. Newer versions of MySQL introduce new sets of rules, too, such as _0900_ai_ci for equivalent rules based on Unicode 9.0 - and with no equivalent _general_ci variant. People reading this now should probably use one of these newer collations instead of either _unicode_ci or _general_ci. The description of those older collations below is provided for interest only.

    MySQL is currently transitioning away from an older, flawed UTF-8 implementation. For now, you need to use utf8mb4 instead of utf8 for the character encoding part, to ensure you are getting the fixed version. The flawed version remains for backward compatibility, though it is being deprecated.

    Key differences

    utf8mb4_unicode_ci is based on the official Unicode rules for universal sorting and comparison, which sorts accurately in a wide range of languages.

    utf8mb4_general_ci is a simplified set of sorting rules which aims to do as well as it can while taking many short-cuts designed to improve speed. It does not follow the Unicode rules and will result in undesirable sorting or comparison in some situations, such as when using particular languages or characters.

    On modern servers, this performance boost will be all but negligible. It was devised in a time when servers had a tiny fraction of the CPU performance of today’s computers.

    Benefits of utf8mb4_unicode_ci over utf8mb4_general_ci

    utf8mb4_unicode_ci, which uses the Unicode rules for sorting and comparison, employs a fairly complex algorithm for correct sorting in a wide range of languages and when using a wide range of special characters. These rules need to take into account language-specific conventions; not everybody sorts their characters in what we would call ‘alphabetical order’.

    As far as Latin (ie “European”) languages go, there is not much difference between the Unicode sorting and the simplified utf8mb4_general_cisorting in MySQL, but there are still a few differences:

    For examples, the Unicode collation sorts “ß” like “ss”, and “Œ” like “OE” as people using those characters would normally want, whereas utf8mb4_general_cisorts them as single characters (presumably like “s” and “e” respectively).

    Some Unicode characters are defined as ignorable, which means they shouldn’t count toward the sort order and the comparison should move on to the next character instead. utf8mb4_unicode_cihandles these properly.

    In non-latin languages, such as Asian languages or languages with different alphabets, there may be a lot more differences between Unicode sorting and the simplified utf8mb4_general_cisorting. The suitability of utf8mb4_general_ciwill depend heavily on the language used. For some languages, it’ll be quite inadequate.

    What should you use?

    There is almost certainly no reason to use utf8mb4_general_cianymore, as we have left behind the point where CPU speed is low enough that the performance difference would be important. Your database will almost certainly be limited by other bottlenecks than this.

    In the past, some people recommended to use utf8mb4_general_ciexcept when accurate sorting was going to be important enough to justify the performance cost. Today, that performance cost has all but disappeared, and developers are treating internationalization more seriously.

    There’s an argument to be made that if speed is more important to you than accuracy, you may as well not do any sorting at all. It’s trivial to make an algorithm faster if you do not need it to be accurate. So, utf8mb4_general_ciis a compromise that’s probably not needed for speed reasons and probably also not suitable for accuracy reasons.

    One other thing I’ll add is that even if you know your application only supports the English language, it may still need to deal with people’s names, which can often contain characters used in other languages in which it is just as important to sort correctly. Using the Unicode rules for everything helps add peace of mind that the very smart Unicode people have worked very hard to make sorting work properly.

    What the parts mean

    Firstly, ci is for case-insensitive sorting and comparison. This means it’s suitable for textual data, and case is not important. The other types of collation are cs (case-sensitive) for textual data where case is important, and bin, for where the encoding needs to match, bit for bit, which is suitable for fields which are really encoded binary data (including, for example, Base64). Case-sensitive sorting leads to some weird results and case-sensitive comparison can result in duplicate values differing only in letter case, so case-sensitive collations are falling out of favor for textual data - if case is significant to you, then otherwise ignorable punctuation and so on is probably also significant, and a binary collation might be more appropriate.

    Next, unicode or general refers to the specific sorting and comparison rules - in particular, the way text is normalized or compared. There are many different sets of rules for the utf8mb4 character encoding, with unicode and general being two that attempt to work well in all possible languages rather than one specific one. The differences between these two sets of rules are the subject of this answer. Note that unicode uses rules from Unicode 4.0. Recent versions of MySQL add the rulesets unicode_520 using rules from Unicode 5.2, and 0900 (dropping the “unicode_” part) using rules from Unicode 9.0.

    And lastly, utf8mb4 is of course the character encoding used internally. In this answer I’m talking only about Unicode based encodings.

    翻译

    对于那些在 2020 年或之后仍会遇到这个问题的人,有可能比这两个更好的新选项。例如,utf8mb4_0900_ai_ci

    所有这些排序规则都用于 UTF-8 字符编码。不同之处在于文本的排序和比较方式。

    _unicode_ci_general_ci是两组不同的规则,用于按照我们期望的方式对文本进行排序和比较。较新版本的 MySQL 也引入了新的规则集,例如 _0900_ai_ci用于基于 Unicode 9.0 的等效规则 - 并且没有等效的 _general_ci变体。现在阅读本文的人可能应该使用这些较新的排序规则之一,而不是 _unicode_ci_general_ci。下面对那些较旧的排序规则的描述仅供参考。

    MySQL 目前正在从旧的、有缺陷的 UTF-8 实现过渡。现在,您需要使用 utf8mb4 而不是 utf8作为字符编码部分,以确保您获得的是固定版本。有缺陷的版本仍然是为了向后兼容,尽管它已被弃用。

    主要区别

    utf8mb4_unicode_ci基于官方 Unicode 规则进行通用排序和比较,可在多种语言中准确排序。

    utf8mb4_general_ci是一组简化的排序规则,旨在尽其所能,同时采用许多旨在提高速度的捷径。它不遵循 Unicode 规则,并且在某些情况下会导致不希望的排序或比较,例如在使用特定语言或字符时。

    在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的 CPU 性能只有当今计算机的一小部分时设计的。

    utf8mb4_unicode_ci 相对于 utf8mb4_general_ci的优势

    utf8mb4_unicode_ci使用 Unicode 规则进行排序和比较,采用相当复杂的算法在多种语言中以及在使用多种特殊字符时进行正确排序。这些规则需要考虑特定语言的约定;不是每个人都按照我们所说的“字母顺序”对他们的字符进行排序。

    就拉丁语(即“欧洲”)语言而言,Unicode 排序和 MySQL 中简化的 utf8mb4_general_ci排序没有太大区别,但仍有一些区别:

    例如,Unicode 排序规则将“ß”排序为“ss”,将“Œ”排序为“OE”,因为使用这些字符的人通常需要这些字符,而 utf8mb4_general_ci将它们排序为单个字符(大概分别像“s”和“e” )。

    一些 Unicode 字符被定义为可忽略,这意味着它们不应该计入排序顺序,并且比较应该转到下一个字符。 utf8mb4_unicode_ci正确处理这些。

    在非拉丁语言中,例如亚洲语言或具有不同字母的语言,Unicode 排序和简化的 utf8mb4_general_ci排序之间可能存在更多差异。 utf8mb4_general_ci的适用性在很大程度上取决于所使用的语言。对于某些语言,这将是非常不充分的。

    你应该用什么?

    几乎可以肯定没有理由再使用 utf8mb4_general_ci,因为我们已经将 CPU 速度低到会严重影响性能表现的时代远抛在脑后了。您的数据库几乎肯定会受到除此之外的其他瓶颈的限制。

    过去,有些人建议使用 utf8mb4_general_ci,除非准确排序足够重要以证明性能成本是合理的。如今,这种性能成本几乎消失了,开发人员正在更加认真地对待国际化。

    有一个论点是,如果速度对您来说比准确性更重要,那么您可能根本不进行任何排序。如果您不需要准确的算法,那么使算法更快是微不足道的。因此,utf8mb4_general_ci是一种折衷方案,出于速度原因可能不需要,也可能出于准确性原因也不适合。

    我要补充的另一件事是,即使您知道您的应用程序仅支持英语,它可能仍需要处理人名,这些人名通常包含其他语言中使用的字符,在这些语言中正确排序同样重要.对所有事情都使用 Unicode 规则有助于让您更加安心,因为非常聪明的 Unicode 人员已经非常努力地工作以使排序正常工作。

    其余各个部分是什么意思

    首先, ci 用于不区分大小写的排序和比较。这意味着它适用于文本数据,大小写并不重要。其他类型的排序规则是 cs(区分大小写),用于区分大小写的文本数据,以及 bin,用于编码需要匹配的地方,逐位匹配,适用于真正编码二进制数据的字段(包括,用于例如,Base64)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。

    接下来,unicode 或general 指的是具体的排序和比较规则——特别是文本被规范化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicode 和 general 是两种,它们试图在所有可能的语言中都很好地工作,而不是在一种特定的语言中。这两组规则之间的差异是此答案的主题。请注意,unicode 使用 Unicode 4.0 中的规则。 MySQL 的最新版本使用 Unicode 5.2 的规则添加规则集 unicode_520,使用 Unicode 9.0 的规则添加 0900(删除“unicode_”部分)。

    最后,utf8mb4 当然是内部使用的字符编码。在这个答案中,我只谈论基于 Unicode 的编码。

    utf8 和 utf8mb4 编码有什么区别

    原文

    UTF-8is a variable-length encoding. In the case of UTF-8, this means that storing one code point requires one to four bytes. However, MySQL’s encoding called “utf8” (alias of “utf8mb3”) only stores a maximum of three bytes per code point.

    So the character set “utf8”/“utf8mb3” cannot store all Unicode code points: it only supports the range 0x000 to 0xFFFF, which is called the “Basic Multilingual Plane“. See also Comparison of Unicode encodings.

    This is what (a previous version of the same page at)the MySQL documentationhas to say about it:

    The character set named utf8[/utf8mb3] uses a maximum of three bytes per character and contains only BMP characters. As of MySQL 5.5.3, the utf8mb4 character set uses a maximum of four bytes per character supports supplemental characters:

    • For a BMP character, utf8[/utf8mb3] and utf8mb4 have identical storage characteristics: same code values, same encoding, same length.
    • For a supplementary character, utf8[/utf8mb3] cannot store the character at all, while utf8mb4 requires four bytes to store it. Since utf8[/utf8mb3] cannot store the character at all, you do not have any supplementary characters in utf8[/utf8mb3] columns and you need not worry about converting characters or losing data when upgrading utf8[/utf8mb3] data from older versions of MySQL.

    So if you want your column to support storing characters lying outside the BMP (and you usually want to), such as emoji, use “utf8mb4”. See also What are the most common non-BMP Unicode characters in actual use?.

    译文

    UTF-8 是一种可变长度编码。对于 UTF-8,这意味着存储一个代码点需要一到四个字节。但是,MySQL 的编码称为“utf8”(“utf8mb3”的别名)每个代码点最多只能存储三个字节。

    所以字符集“utf8”/“utf8mb3”不能存储所有的Unicode码位:它只支持0x000到0xFFFF的范围,被称为“基本多语言平面”。另请参阅 Unicode 编码比较

    这就是(同一页面的先前版本)MySQL 文档 不得不说的:

    名为 utf8[/utf8mb3] 的字符集每个字符最多使用三个字节,并且仅包含 BMP 字符。从 MySQL 5.5.3 开始,utf8mb4 字符集每个字符最多使用四个字节,支持补充字符:

    • 对于 BMP 字符,utf8[/utf8mb3] 和 utf8mb4 具有相同的存储特性:相同的代码值、相同的编码、相同的长度。
    • 对于补充字符,utf8[/utf8mb3] 根本无法存储该字符,而 utf8mb4 需要四个字节来存储它。由于 utf8[/utf8mb3] 根本无法存储字符,因此您在 utf8[/utf8mb3] 列中没有任何补充字符,您不必担心从旧版本升级 utf8[/utf8mb3] 数据时转换字符或丢失数据mysql。

    因此,如果您希望您的列支持存储位于 BMP 之外的字符(并且您通常希望这样做),例如 emoji,请使用“utf8mb4”。另请参阅

    实际使用中最常见的非 BMP Unicode 字符是什么?

    前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    CREATE TABLE `student_time_0` (
    +}


    就能看到接收到消息了。

    又是一年年终总结,本着极度讨厌实时需求的理念,我还是 T+N 发布这个年终总结

    工作篇

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

    Mysql 字符编码和排序规则

    这个一直是属于一知半解的状态,知道 utf8 跟 utf8mb4 的区别主要是能不能支持 emoji,但是具体后面配置的排序规则是用来干嘛,或者有什么区别,应该使用哪个,所以在 stackoverflow 上找了下,有两个比较不错的解答,就搬过来并且配合机翻做了点修改

    原文

    For those people still arriving at this question in 2020 or later, there are newer options that may be better than both of these. For example, utf8mb4_0900_ai_ci.

    All these collations are for the UTF-8 character encoding. The differences are in how text is sorted and compared.

    _unicode_ci and _general_ci are two different sets of rules for sorting and comparing text according to the way we expect. Newer versions of MySQL introduce new sets of rules, too, such as _0900_ai_ci for equivalent rules based on Unicode 9.0 - and with no equivalent _general_ci variant. People reading this now should probably use one of these newer collations instead of either _unicode_ci or _general_ci. The description of those older collations below is provided for interest only.

    MySQL is currently transitioning away from an older, flawed UTF-8 implementation. For now, you need to use utf8mb4 instead of utf8 for the character encoding part, to ensure you are getting the fixed version. The flawed version remains for backward compatibility, though it is being deprecated.

    Key differences

    utf8mb4_unicode_ci is based on the official Unicode rules for universal sorting and comparison, which sorts accurately in a wide range of languages.

    utf8mb4_general_ci is a simplified set of sorting rules which aims to do as well as it can while taking many short-cuts designed to improve speed. It does not follow the Unicode rules and will result in undesirable sorting or comparison in some situations, such as when using particular languages or characters.

    On modern servers, this performance boost will be all but negligible. It was devised in a time when servers had a tiny fraction of the CPU performance of today’s computers.

    Benefits of utf8mb4_unicode_ci over utf8mb4_general_ci

    utf8mb4_unicode_ci, which uses the Unicode rules for sorting and comparison, employs a fairly complex algorithm for correct sorting in a wide range of languages and when using a wide range of special characters. These rules need to take into account language-specific conventions; not everybody sorts their characters in what we would call ‘alphabetical order’.

    As far as Latin (ie “European”) languages go, there is not much difference between the Unicode sorting and the simplified utf8mb4_general_cisorting in MySQL, but there are still a few differences:

    For examples, the Unicode collation sorts “ß” like “ss”, and “Œ” like “OE” as people using those characters would normally want, whereas utf8mb4_general_cisorts them as single characters (presumably like “s” and “e” respectively).

    Some Unicode characters are defined as ignorable, which means they shouldn’t count toward the sort order and the comparison should move on to the next character instead. utf8mb4_unicode_cihandles these properly.

    In non-latin languages, such as Asian languages or languages with different alphabets, there may be a lot more differences between Unicode sorting and the simplified utf8mb4_general_cisorting. The suitability of utf8mb4_general_ciwill depend heavily on the language used. For some languages, it’ll be quite inadequate.

    What should you use?

    There is almost certainly no reason to use utf8mb4_general_cianymore, as we have left behind the point where CPU speed is low enough that the performance difference would be important. Your database will almost certainly be limited by other bottlenecks than this.

    In the past, some people recommended to use utf8mb4_general_ciexcept when accurate sorting was going to be important enough to justify the performance cost. Today, that performance cost has all but disappeared, and developers are treating internationalization more seriously.

    There’s an argument to be made that if speed is more important to you than accuracy, you may as well not do any sorting at all. It’s trivial to make an algorithm faster if you do not need it to be accurate. So, utf8mb4_general_ciis a compromise that’s probably not needed for speed reasons and probably also not suitable for accuracy reasons.

    One other thing I’ll add is that even if you know your application only supports the English language, it may still need to deal with people’s names, which can often contain characters used in other languages in which it is just as important to sort correctly. Using the Unicode rules for everything helps add peace of mind that the very smart Unicode people have worked very hard to make sorting work properly.

    What the parts mean

    Firstly, ci is for case-insensitive sorting and comparison. This means it’s suitable for textual data, and case is not important. The other types of collation are cs (case-sensitive) for textual data where case is important, and bin, for where the encoding needs to match, bit for bit, which is suitable for fields which are really encoded binary data (including, for example, Base64). Case-sensitive sorting leads to some weird results and case-sensitive comparison can result in duplicate values differing only in letter case, so case-sensitive collations are falling out of favor for textual data - if case is significant to you, then otherwise ignorable punctuation and so on is probably also significant, and a binary collation might be more appropriate.

    Next, unicode or general refers to the specific sorting and comparison rules - in particular, the way text is normalized or compared. There are many different sets of rules for the utf8mb4 character encoding, with unicode and general being two that attempt to work well in all possible languages rather than one specific one. The differences between these two sets of rules are the subject of this answer. Note that unicode uses rules from Unicode 4.0. Recent versions of MySQL add the rulesets unicode_520 using rules from Unicode 5.2, and 0900 (dropping the “unicode_” part) using rules from Unicode 9.0.

    And lastly, utf8mb4 is of course the character encoding used internally. In this answer I’m talking only about Unicode based encodings.

    翻译

    对于那些在 2020 年或之后仍会遇到这个问题的人,有可能比这两个更好的新选项。例如,utf8mb4_0900_ai_ci

    所有这些排序规则都用于 UTF-8 字符编码。不同之处在于文本的排序和比较方式。

    _unicode_ci_general_ci是两组不同的规则,用于按照我们期望的方式对文本进行排序和比较。较新版本的 MySQL 也引入了新的规则集,例如 _0900_ai_ci用于基于 Unicode 9.0 的等效规则 - 并且没有等效的 _general_ci变体。现在阅读本文的人可能应该使用这些较新的排序规则之一,而不是 _unicode_ci_general_ci。下面对那些较旧的排序规则的描述仅供参考。

    MySQL 目前正在从旧的、有缺陷的 UTF-8 实现过渡。现在,您需要使用 utf8mb4 而不是 utf8作为字符编码部分,以确保您获得的是固定版本。有缺陷的版本仍然是为了向后兼容,尽管它已被弃用。

    主要区别

    utf8mb4_unicode_ci基于官方 Unicode 规则进行通用排序和比较,可在多种语言中准确排序。

    utf8mb4_general_ci是一组简化的排序规则,旨在尽其所能,同时采用许多旨在提高速度的捷径。它不遵循 Unicode 规则,并且在某些情况下会导致不希望的排序或比较,例如在使用特定语言或字符时。

    在现代服务器上,这种性能提升几乎可以忽略不计。它是在服务器的 CPU 性能只有当今计算机的一小部分时设计的。

    utf8mb4_unicode_ci 相对于 utf8mb4_general_ci的优势

    utf8mb4_unicode_ci使用 Unicode 规则进行排序和比较,采用相当复杂的算法在多种语言中以及在使用多种特殊字符时进行正确排序。这些规则需要考虑特定语言的约定;不是每个人都按照我们所说的“字母顺序”对他们的字符进行排序。

    就拉丁语(即“欧洲”)语言而言,Unicode 排序和 MySQL 中简化的 utf8mb4_general_ci排序没有太大区别,但仍有一些区别:

    例如,Unicode 排序规则将“ß”排序为“ss”,将“Œ”排序为“OE”,因为使用这些字符的人通常需要这些字符,而 utf8mb4_general_ci将它们排序为单个字符(大概分别像“s”和“e” )。

    一些 Unicode 字符被定义为可忽略,这意味着它们不应该计入排序顺序,并且比较应该转到下一个字符。 utf8mb4_unicode_ci正确处理这些。

    在非拉丁语言中,例如亚洲语言或具有不同字母的语言,Unicode 排序和简化的 utf8mb4_general_ci排序之间可能存在更多差异。 utf8mb4_general_ci的适用性在很大程度上取决于所使用的语言。对于某些语言,这将是非常不充分的。

    你应该用什么?

    几乎可以肯定没有理由再使用 utf8mb4_general_ci,因为我们已经将 CPU 速度低到会严重影响性能表现的时代远抛在脑后了。您的数据库几乎肯定会受到除此之外的其他瓶颈的限制。

    过去,有些人建议使用 utf8mb4_general_ci,除非准确排序足够重要以证明性能成本是合理的。如今,这种性能成本几乎消失了,开发人员正在更加认真地对待国际化。

    有一个论点是,如果速度对您来说比准确性更重要,那么您可能根本不进行任何排序。如果您不需要准确的算法,那么使算法更快是微不足道的。因此,utf8mb4_general_ci是一种折衷方案,出于速度原因可能不需要,也可能出于准确性原因也不适合。

    我要补充的另一件事是,即使您知道您的应用程序仅支持英语,它可能仍需要处理人名,这些人名通常包含其他语言中使用的字符,在这些语言中正确排序同样重要.对所有事情都使用 Unicode 规则有助于让您更加安心,因为非常聪明的 Unicode 人员已经非常努力地工作以使排序正常工作。

    其余各个部分是什么意思

    首先, ci 用于不区分大小写的排序和比较。这意味着它适用于文本数据,大小写并不重要。其他类型的排序规则是 cs(区分大小写),用于区分大小写的文本数据,以及 bin,用于编码需要匹配的地方,逐位匹配,适用于真正编码二进制数据的字段(包括,用于例如,Base64)。区分大小写的排序会导致一些奇怪的结果,区分大小写的比较可能会导致重复值仅在字母大小写上有所不同,因此区分大小写的排序规则对文本数据不受欢迎 - 如果大小写对您很重要,那么标点符号就可以忽略等等可能也很重要,二进制排序规则可能更合适。

    接下来,unicode 或general 指的是具体的排序和比较规则——特别是文本被规范化或比较的方式。 utf8mb4 字符编码有许多不同的规则集,其中 unicode 和 general 是两种,它们试图在所有可能的语言中都很好地工作,而不是在一种特定的语言中。这两组规则之间的差异是此答案的主题。请注意,unicode 使用 Unicode 4.0 中的规则。 MySQL 的最新版本使用 Unicode 5.2 的规则添加规则集 unicode_520,使用 Unicode 9.0 的规则添加 0900(删除“unicode_”部分)。

    最后,utf8mb4 当然是内部使用的字符编码。在这个答案中,我只谈论基于 Unicode 的编码。

    utf8 和 utf8mb4 编码有什么区别

    原文

    UTF-8is a variable-length encoding. In the case of UTF-8, this means that storing one code point requires one to four bytes. However, MySQL’s encoding called “utf8” (alias of “utf8mb3”) only stores a maximum of three bytes per code point.

    So the character set “utf8”/“utf8mb3” cannot store all Unicode code points: it only supports the range 0x000 to 0xFFFF, which is called the “Basic Multilingual Plane“. See also Comparison of Unicode encodings.

    This is what (a previous version of the same page at)the MySQL documentationhas to say about it:

    The character set named utf8[/utf8mb3] uses a maximum of three bytes per character and contains only BMP characters. As of MySQL 5.5.3, the utf8mb4 character set uses a maximum of four bytes per character supports supplemental characters:

    • For a BMP character, utf8[/utf8mb3] and utf8mb4 have identical storage characteristics: same code values, same encoding, same length.
    • For a supplementary character, utf8[/utf8mb3] cannot store the character at all, while utf8mb4 requires four bytes to store it. Since utf8[/utf8mb3] cannot store the character at all, you do not have any supplementary characters in utf8[/utf8mb3] columns and you need not worry about converting characters or losing data when upgrading utf8[/utf8mb3] data from older versions of MySQL.

    So if you want your column to support storing characters lying outside the BMP (and you usually want to), such as emoji, use “utf8mb4”. See also What are the most common non-BMP Unicode characters in actual use?.

    译文

    UTF-8 是一种可变长度编码。对于 UTF-8,这意味着存储一个代码点需要一到四个字节。但是,MySQL 的编码称为“utf8”(“utf8mb3”的别名)每个代码点最多只能存储三个字节。

    所以字符集“utf8”/“utf8mb3”不能存储所有的Unicode码位:它只支持0x000到0xFFFF的范围,被称为“基本多语言平面”。另请参阅 Unicode 编码比较

    这就是(同一页面的先前版本)MySQL 文档 不得不说的:

    名为 utf8[/utf8mb3] 的字符集每个字符最多使用三个字节,并且仅包含 BMP 字符。从 MySQL 5.5.3 开始,utf8mb4 字符集每个字符最多使用四个字节,支持补充字符:

    • 对于 BMP 字符,utf8[/utf8mb3] 和 utf8mb4 具有相同的存储特性:相同的代码值、相同的编码、相同的长度。
    • 对于补充字符,utf8[/utf8mb3] 根本无法存储该字符,而 utf8mb4 需要四个字节来存储它。由于 utf8[/utf8mb3] 根本无法存储字符,因此您在 utf8[/utf8mb3] 列中没有任何补充字符,您不必担心从旧版本升级 utf8[/utf8mb3] 数据时转换字符或丢失数据mysql。

    因此,如果您希望您的列支持存储位于 BMP 之外的字符(并且您通常希望这样做),例如 emoji,请使用“utf8mb4”。另请参阅

    实际使用中最常见的非 BMP Unicode 字符是什么?

    前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    CREATE TABLE `student_time_0` (
       `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
       `user_id` int(11) NOT NULL,
       `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
    @@ -90,7 +90,7 @@
             i++;
         } while (i <= 2000);

    三个表的数据分别是 673,678,650,说明符合预期了,各个表数据不一样,接下来比如我们想要做一个这样的分页查询

    select * from student_time ORDER BY create_time ASC limit 1000, 5;

    student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表,首先从一无所知去想这个查询如果我们自己来处理应该是怎么做,
    首先是不是可以每个表都从 333 开始取 5 条数据,类似于下面的查询,然后进行 15 条的合并重排序获取前面的 5 条

    select * from student_time_0 ORDER BY create_time ASC limit 333, 5;
     select * from student_time_1 ORDER BY create_time ASC limit 333, 5;
    -select * from student_time_2 ORDER BY create_time ASC limit 333, 5;

    忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
    因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

    黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
    那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
    第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
    第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
    这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

    t0
    +select * from student_time_2 ORDER BY create_time ASC limit 333, 5;

    忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
    因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

    黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
    那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
    第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
    第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
    这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

    t0
     334 10158 nick95  18  1641548941767
     335 10098 nick11  18  1641548941879
     336 10167 nick51  18  1641548942089
    diff --git a/page/6/index.html b/page/6/index.html
    index 8223075806..0d69c8ad06 100644
    --- a/page/6/index.html
    +++ b/page/6/index.html
    @@ -1,7 +1,7 @@
    -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
    就是我在执行 query 的时候犯过一个比较难发现的错误,

    ResultSet resultSet = ps.executeQuery(sql);

    实际上应该是

    ResultSet resultSet = ps.executeQuery();

    而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

    这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
    那么在我上面的写错的代码里

    @Override
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
    就是我在执行 query 的时候犯过一个比较难发现的错误,

    ResultSet resultSet = ps.executeQuery(sql);

    实际上应该是

    ResultSet resultSet = ps.executeQuery();

    而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

    这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
    那么在我上面的写错的代码里

    @Override
     public final ResultSet executeQuery(final String sql) throws SQLException {
         throw new SQLFeatureNotSupportedException("executeQuery with SQL for PreparedStatement");
    -}

    这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

    看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

    在前一篇的 demo 中的

    Connection conn = dataSource.getConnection();

    其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
    然后获得java.sql.PreparedStatement

    PreparedStatement ps = conn.prepareStatement(sql)

    就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
    然后就是执行

    ResultSet resultSet = ps.executeQuery();

    然后获得结果
    org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

    其实像 mybatis 也是基于这样去实现的

    其实最近看的不止这一部,前面看了《继承者们》,《少年包青天》这些,就一起聊下,其中看《继承者们》算是个人比较喜欢,以前就有这种看剧的习惯,这个跟《一生一世》里任嘉伦说自己看《寻秦记》看了几十遍一样,我看喜欢的剧也基本上会看不止五遍,继承者们是有帅哥美女看,而且印象中剧情也挺甜的,一般情况下最好是已经有点遗忘剧情了,因为我个人觉得看剧分两种,无聊了又心情不太好,可以看些这类轻松又看过的剧,可以不完全专心地看剧,另外有心情专心看的时候,可以看一些需要思考,一些探案类的或者烧脑类。
    最近看了《神探狄仁杰》,因为跟前面看的《少年包青天》都是这类古装探案剧,正好有些感想,《少年包青天》算是儿时阴影,本来是不太会去看的,正好有一次有机会跟 LD 一起看了会就也觉得比较有意思就看了下去,不得不说,以前的这些剧还是很不错的,包括剧情和演员,第一部一共是 40 集,看的过程中也发现了大概是五个案子,平均八集一个案子,整体节奏还是比较慢的,但是基本每个案子其实都是构思得很巧妙,很久以前看过但是现在基本不太记得剧情了,每个案子在前面几集的时候基本都猜不到犯案逻辑,但是在看了狄仁杰之后,发现两部剧也有比较大的差别,少年包青天相对来说逻辑性会更强一些,个人主观觉得推理的严谨性更高,可能剧本打磨上更将就一下,而狄仁杰因为要提现他的个人强项,不比少年包青天中有公孙策一时瑜亮的情节,狄仁杰中分工明确,李元芳是个武力担当,曾泰是捧哏的,相对来说是狄仁杰在案子里从始至终地推进案情,有些甚至有些玄乎,会有一些跳脱跟不合理,有些像是狄仁杰的奇遇,不过这些想法是私人的观点,并不是想要评孰优孰劣;第二个感受是不知道是不是年代关系,特别是少年包青天,每个案件的大 boss 基本都不是个完全的坏人,甚至都是比较情有可原的可怜人,因为一些特殊原因,而好几个都是包拯身边的人,这一点其实跟狄仁杰里第一个使团惊魂案件比较类似,虎敬晖也是个人物形象比较丰满的角色,不是个标签化的淡薄形象,跟金木兰的感情和反叛行动在最后都说明的缘由,而且也有随着跟狄仁杰一起办案被其影响感化,最终为了救狄仁杰而被金木兰所杀,只是这样金木兰这个角色就会有些偏执和符号化,当然剧本肯定不是能面面俱到,这样的剧本已经比现在很多流量剧的好很多了。还想到了前阵子看的《指环王》中的白袍萨鲁曼在剧中也是个比较单薄的角色,这样的电影彪炳影史也没办法把个个人物都设计得完整有血有肉,或者说这本来也是应该有侧重点,当然其实我也不觉得指环王就是绝对的最好的,因为相对来说故事情节的复杂性等真的不如西游记,只是在 86 版之后的各种乱七八糟的翻牌和乱拍已经让这个真正的王者神话故事有点力不从心,这里边有部西游记后传是个人还比较喜欢的,虽然武打动作比较鬼畜,但是剧情基本是无敌的,在西游的架构上衍生出来这么完整丰富的故事,人物角色也都有各自的出彩点。
    说回狄仁杰,在这之前也看过徐克拍的几部狄仁杰的电影版,第一部刘德华拍得相对完成度更高,故事性也可圈可点,后面几部就是剧情拉胯,靠特效拉回来一点分,虽说这个也是所谓的狄仁杰宇宙的构思在里面但是现在来看基本是跟西游那些差不多,完全没有整体性可言,打一枪换一个地方,演员也没有延续性,剧情也是前后跳脱,没什么关联跟承上启下,导致质量层次不一,更不用谈什么狄仁杰宇宙了,不过这个事情也是难说,原因很多,现在资本都是更加趋利的,一些需要更长久时间才能有回报的投资是很难获得资本青睐,所以只能将重心投给选择一些流量明星,而本来应该将资源投给剧本打磨的基本就没了,再深入说也没意义了,社会现状就是这样。
    还有一点感想是,以前的剧里的拍摄环境还是比较惨的,看着一些房子,甚至皇宫都是比较破旧的,地面还是石板这种,想想以前的演员的环境再想想现在的,比如成龙说的,以前他拍剧就是啪摔了,问这条有没有过,过了就直接送医院,而不是现在可能手蹭破点皮就大叫,甚至还有饭圈这些破事。

    我们在日常工作中还是使用比较多的分库分表组件的,其中比较优秀的就有 Sharding-Jdbc,一开始由当当开源,后来捐献给了 Apache,说一下简单使用,因为原来经常的使用都是基于 xml 跟 properties 组合起来使用,这里主要试下用 Java Config 来配置
    首先是通过 Spring Initializr 创建个带 jdbc 的 Spring Boot 项目,然后引入主要的依赖

    <dependency>
    +}

    这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

    看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

    在前一篇的 demo 中的

    Connection conn = dataSource.getConnection();

    其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
    然后获得java.sql.PreparedStatement

    PreparedStatement ps = conn.prepareStatement(sql)

    就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
    然后就是执行

    ResultSet resultSet = ps.executeQuery();

    然后获得结果
    org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

    其实像 mybatis 也是基于这样去实现的

    其实最近看的不止这一部,前面看了《继承者们》,《少年包青天》这些,就一起聊下,其中看《继承者们》算是个人比较喜欢,以前就有这种看剧的习惯,这个跟《一生一世》里任嘉伦说自己看《寻秦记》看了几十遍一样,我看喜欢的剧也基本上会看不止五遍,继承者们是有帅哥美女看,而且印象中剧情也挺甜的,一般情况下最好是已经有点遗忘剧情了,因为我个人觉得看剧分两种,无聊了又心情不太好,可以看些这类轻松又看过的剧,可以不完全专心地看剧,另外有心情专心看的时候,可以看一些需要思考,一些探案类的或者烧脑类。
    最近看了《神探狄仁杰》,因为跟前面看的《少年包青天》都是这类古装探案剧,正好有些感想,《少年包青天》算是儿时阴影,本来是不太会去看的,正好有一次有机会跟 LD 一起看了会就也觉得比较有意思就看了下去,不得不说,以前的这些剧还是很不错的,包括剧情和演员,第一部一共是 40 集,看的过程中也发现了大概是五个案子,平均八集一个案子,整体节奏还是比较慢的,但是基本每个案子其实都是构思得很巧妙,很久以前看过但是现在基本不太记得剧情了,每个案子在前面几集的时候基本都猜不到犯案逻辑,但是在看了狄仁杰之后,发现两部剧也有比较大的差别,少年包青天相对来说逻辑性会更强一些,个人主观觉得推理的严谨性更高,可能剧本打磨上更将就一下,而狄仁杰因为要提现他的个人强项,不比少年包青天中有公孙策一时瑜亮的情节,狄仁杰中分工明确,李元芳是个武力担当,曾泰是捧哏的,相对来说是狄仁杰在案子里从始至终地推进案情,有些甚至有些玄乎,会有一些跳脱跟不合理,有些像是狄仁杰的奇遇,不过这些想法是私人的观点,并不是想要评孰优孰劣;第二个感受是不知道是不是年代关系,特别是少年包青天,每个案件的大 boss 基本都不是个完全的坏人,甚至都是比较情有可原的可怜人,因为一些特殊原因,而好几个都是包拯身边的人,这一点其实跟狄仁杰里第一个使团惊魂案件比较类似,虎敬晖也是个人物形象比较丰满的角色,不是个标签化的淡薄形象,跟金木兰的感情和反叛行动在最后都说明的缘由,而且也有随着跟狄仁杰一起办案被其影响感化,最终为了救狄仁杰而被金木兰所杀,只是这样金木兰这个角色就会有些偏执和符号化,当然剧本肯定不是能面面俱到,这样的剧本已经比现在很多流量剧的好很多了。还想到了前阵子看的《指环王》中的白袍萨鲁曼在剧中也是个比较单薄的角色,这样的电影彪炳影史也没办法把个个人物都设计得完整有血有肉,或者说这本来也是应该有侧重点,当然其实我也不觉得指环王就是绝对的最好的,因为相对来说故事情节的复杂性等真的不如西游记,只是在 86 版之后的各种乱七八糟的翻牌和乱拍已经让这个真正的王者神话故事有点力不从心,这里边有部西游记后传是个人还比较喜欢的,虽然武打动作比较鬼畜,但是剧情基本是无敌的,在西游的架构上衍生出来这么完整丰富的故事,人物角色也都有各自的出彩点。
    说回狄仁杰,在这之前也看过徐克拍的几部狄仁杰的电影版,第一部刘德华拍得相对完成度更高,故事性也可圈可点,后面几部就是剧情拉胯,靠特效拉回来一点分,虽说这个也是所谓的狄仁杰宇宙的构思在里面但是现在来看基本是跟西游那些差不多,完全没有整体性可言,打一枪换一个地方,演员也没有延续性,剧情也是前后跳脱,没什么关联跟承上启下,导致质量层次不一,更不用谈什么狄仁杰宇宙了,不过这个事情也是难说,原因很多,现在资本都是更加趋利的,一些需要更长久时间才能有回报的投资是很难获得资本青睐,所以只能将重心投给选择一些流量明星,而本来应该将资源投给剧本打磨的基本就没了,再深入说也没意义了,社会现状就是这样。
    还有一点感想是,以前的剧里的拍摄环境还是比较惨的,看着一些房子,甚至皇宫都是比较破旧的,地面还是石板这种,想想以前的演员的环境再想想现在的,比如成龙说的,以前他拍剧就是啪摔了,问这条有没有过,过了就直接送医院,而不是现在可能手蹭破点皮就大叫,甚至还有饭圈这些破事。

    我们在日常工作中还是使用比较多的分库分表组件的,其中比较优秀的就有 Sharding-Jdbc,一开始由当当开源,后来捐献给了 Apache,说一下简单使用,因为原来经常的使用都是基于 xml 跟 properties 组合起来使用,这里主要试下用 Java Config 来配置
    首先是通过 Spring Initializr 创建个带 jdbc 的 Spring Boot 项目,然后引入主要的依赖

    <dependency>
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>shardingsphere-jdbc-core</artifactId>
         <version>5.0.0-beta</version>
    @@ -50,7 +50,7 @@
             return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(shardingRuleConfig), new Properties());
     
         }
    -}

    然后我们就可以在使用这个 DataSource 了,先看下这两个表的数据

    @Override
    +}

    然后我们就可以在使用这个 DataSource 了,先看下这两个表的数据

    @Override
         public void run(String... args) {
             LOGGER.info("run here");
             String sql = "SELECT * FROM student WHERE user_id=? ";
    @@ -85,4 +85,4 @@
             } catch (SQLException e) {
                 e.printStackTrace();
             }
    -    }

    看下查询结果

    今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    \ No newline at end of file + }

    看下查询结果

    今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    \ No newline at end of file diff --git a/page/7/index.html b/page/7/index.html index d508994acb..4db7fb9659 100644 --- a/page/7/index.html +++ b/page/7/index.html @@ -1,4 +1,4 @@ -Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前阵子搭了个 WordPress,但是没怎么用,前两天发现忘了登录密码了,最近不知道是什么情况,chrome 的记住密码跟历史记录感觉有点问题,历史记录丢了不少东西,可能是时间太久了,但是理论上应该有 LRU 这种策略的,有些还比较常用,还有记住密码,因为个人域名都是用子域名分配给各个服务,有些记住了,有些又没记住密码,略蛋疼,所以就找了下这个方案。
    当然这个方案不是最优的,有很多限制,首先就是要能够登陆 WordPress 的数据库,不然这个方法是没用的。
    首先不管用什么方式(别违法)先登陆数据库,选择 WordPress 的数据库,可以看到里面有几个表,我们的目标就是 wp_users 表,用 select 查询看下可以看到有用户的数据,如果是像我这样搭着玩的没有创建其他用户的话应该就只有一个用户,那我们的表里的用户数据就只会有一条,当然多条的话可以通过用户名来找

    然后可能我这个版本是这样,没有装额外的插件,密码只是经过了 MD5 的单向哈希,所以我们可以设定一个新密码,然后用 MD5 编码后直接更新进去

    UPDATE wp_users SET user_pass = MD5('123456') WHERE ID = 1;

    然后就能用自己的账户跟刚才更新的密码登录了。

    题目介绍

    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) {
    +Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    前阵子搭了个 WordPress,但是没怎么用,前两天发现忘了登录密码了,最近不知道是什么情况,chrome 的记住密码跟历史记录感觉有点问题,历史记录丢了不少东西,可能是时间太久了,但是理论上应该有 LRU 这种策略的,有些还比较常用,还有记住密码,因为个人域名都是用子域名分配给各个服务,有些记住了,有些又没记住密码,略蛋疼,所以就找了下这个方案。
    当然这个方案不是最优的,有很多限制,首先就是要能够登陆 WordPress 的数据库,不然这个方法是没用的。
    首先不管用什么方式(别违法)先登陆数据库,选择 WordPress 的数据库,可以看到里面有几个表,我们的目标就是 wp_users 表,用 select 查询看下可以看到有用户的数据,如果是像我这样搭着玩的没有创建其他用户的话应该就只有一个用户,那我们的表里的用户数据就只会有一条,当然多条的话可以通过用户名来找

    然后可能我这个版本是这样,没有装额外的插件,密码只是经过了 MD5 的单向哈希,所以我们可以设定一个新密码,然后用 MD5 编码后直接更新进去

    UPDATE wp_users SET user_pass = MD5('123456') WHERE ID = 1;

    然后就能用自己的账户跟刚才更新的密码登录了。

    题目介绍

    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++) {
    @@ -8,7 +8,7 @@
                 max = Math.max(max, sum);
             }
             return max;
    -    }

    最近因为也没什么好看的新剧和综艺所以就看看一些以前看过的老剧,我是个非常念旧的人吧,很多剧都会反反复复地看,一方面之前看过觉得好看的的确是一直记着,还有就是平时工作完了回来就想能放松下,剧情太纠结的,太烧脑的都不喜欢,也就是我常挂在口头的不喜欢看费脑子的剧,跟我不喜欢狼人杀的原因也类似。

    前面其实是看的太阳的后裔,跟 LD 一起看的,之前其实算是看过一点,但是没有看的很完整,并且很多剧情也忘了,只是这个我我可能看得更少一点,因为最开始的时候觉得男主应该是男二,可能对长得这样的男主并且是这样的人设有点失望,感觉不是特别像个特种兵,但是由于本来也比较火,而且 LD 比较喜欢就从这个开始看了,有两个点是比较想说的

    韩剧虽然被吐槽的很多,但是很多剧的质量,情节把控还是优于目前非常多国内剧的,相对来说剧情发展的前后承接不是那么硬凹出来的,而且人设都立得住,这个是非常重要的,很多国内剧怎么说呢,就是当爹的看起来就比儿子没大几岁,三四十岁的人去演一个十岁出头的小姑娘,除非容貌异常,比如刘晓庆这种,不然就会觉得导演在把我们观众当傻子。瞬间就没有想看下去的欲望了。

    再一点就是情节是大众都能接受度比较高的,现在有很多普遍会找一些新奇的视角,比如卖腐,想某某令,两部都叫某某令,这其实是一个点,延伸出去就是跟前面说的一点有点类似,xx 老祖,人看着就二三十,叫 xx 老祖,(喜欢的人轻喷哈)然后名字有一堆,同一个人物一会叫这个名字,一会又叫另一个名字,然后一堆死表情。

    因为今天有个特殊的事情发生,所以简短的写(shui)一篇了

    vscode 扩展转发

    在日常使用云服务器的时候,如果要访问上面自建的 mysql,一般要不直接开对应的端口,然后需要对本地 ip 进行授权,但是这个方案会有比较多的限制,比如本地 ip 变了,比如是非固定出口 ip 的家用宽带,或者要在家里跟公司都要访问,如果对所有 ip 都授权的话会不安全,这个时候其实是用 ssh 端口转发是个比较安全方便的方式。
    原来在这个之前其实对这块内容不太了解,后面是听朋友说的,vscode 的 Remote - SSH 扩展可以很方便的使用端口转发,在使用该扩展的时候,会在控制台位置里都出现一个”端口” tab

    如图中所示,我就是将一个服务器上的 mysql 的 3306 端口转发到本地的 3307 端口,至于为什么不用 3306 是因为本地我也有个 mysql 已经使用了 3306 端口,这个方法是使用的 vscode 的这个扩展,

    ssh 命令转发

    还有个方式是直接使用 ssh 命令
    命令可以如此

    ssh -CfNg -L 3307:127.0.0.1:3306 user1@199.199.199.199

    简单介绍下这个命令
    -C 表示的是压缩数据包
    -f 表示后台执行命令
    -N 是表示不执行具体命令只用于端口转发
    -g 表示允许远程主机连接本地转发端口
    -L 则是具体端口转发的映射配置
    上面的命令就是将远程主机的 127.0.0.1:3306 对应转发到本地 3307
    而后面的用户则就是登录主机的用户名user1和ip地址199.199.199.199,当然这个配置也不是唯一的

    ssh config 配置转发

    还可以在ssh 的 config 配置中加对应的配置

    Host host1
    +    }

    最近因为也没什么好看的新剧和综艺所以就看看一些以前看过的老剧,我是个非常念旧的人吧,很多剧都会反反复复地看,一方面之前看过觉得好看的的确是一直记着,还有就是平时工作完了回来就想能放松下,剧情太纠结的,太烧脑的都不喜欢,也就是我常挂在口头的不喜欢看费脑子的剧,跟我不喜欢狼人杀的原因也类似。

    前面其实是看的太阳的后裔,跟 LD 一起看的,之前其实算是看过一点,但是没有看的很完整,并且很多剧情也忘了,只是这个我我可能看得更少一点,因为最开始的时候觉得男主应该是男二,可能对长得这样的男主并且是这样的人设有点失望,感觉不是特别像个特种兵,但是由于本来也比较火,而且 LD 比较喜欢就从这个开始看了,有两个点是比较想说的

    韩剧虽然被吐槽的很多,但是很多剧的质量,情节把控还是优于目前非常多国内剧的,相对来说剧情发展的前后承接不是那么硬凹出来的,而且人设都立得住,这个是非常重要的,很多国内剧怎么说呢,就是当爹的看起来就比儿子没大几岁,三四十岁的人去演一个十岁出头的小姑娘,除非容貌异常,比如刘晓庆这种,不然就会觉得导演在把我们观众当傻子。瞬间就没有想看下去的欲望了。

    再一点就是情节是大众都能接受度比较高的,现在有很多普遍会找一些新奇的视角,比如卖腐,想某某令,两部都叫某某令,这其实是一个点,延伸出去就是跟前面说的一点有点类似,xx 老祖,人看着就二三十,叫 xx 老祖,(喜欢的人轻喷哈)然后名字有一堆,同一个人物一会叫这个名字,一会又叫另一个名字,然后一堆死表情。

    因为今天有个特殊的事情发生,所以简短的写(shui)一篇了

    vscode 扩展转发

    在日常使用云服务器的时候,如果要访问上面自建的 mysql,一般要不直接开对应的端口,然后需要对本地 ip 进行授权,但是这个方案会有比较多的限制,比如本地 ip 变了,比如是非固定出口 ip 的家用宽带,或者要在家里跟公司都要访问,如果对所有 ip 都授权的话会不安全,这个时候其实是用 ssh 端口转发是个比较安全方便的方式。
    原来在这个之前其实对这块内容不太了解,后面是听朋友说的,vscode 的 Remote - SSH 扩展可以很方便的使用端口转发,在使用该扩展的时候,会在控制台位置里都出现一个”端口” tab

    如图中所示,我就是将一个服务器上的 mysql 的 3306 端口转发到本地的 3307 端口,至于为什么不用 3306 是因为本地我也有个 mysql 已经使用了 3306 端口,这个方法是使用的 vscode 的这个扩展,

    ssh 命令转发

    还有个方式是直接使用 ssh 命令
    命令可以如此

    ssh -CfNg -L 3307:127.0.0.1:3306 user1@199.199.199.199

    简单介绍下这个命令
    -C 表示的是压缩数据包
    -f 表示后台执行命令
    -N 是表示不执行具体命令只用于端口转发
    -g 表示允许远程主机连接本地转发端口
    -L 则是具体端口转发的映射配置
    上面的命令就是将远程主机的 127.0.0.1:3306 对应转发到本地 3307
    而后面的用户则就是登录主机的用户名user1和ip地址199.199.199.199,当然这个配置也不是唯一的

    ssh config 配置转发

    还可以在ssh 的 config 配置中加对应的配置

    Host host1
       HostName 199.199.199.199
       User user1
       IdentityFile  /Users/user1/.ssh/id_rsa
    diff --git a/page/8/index.html b/page/8/index.html
    index a357976f66..17cb8b9542 100644
    --- a/page/8/index.html
    +++ b/page/8/index.html
    @@ -176,7 +176,7 @@ Output: 0

    具体可以看一下这个简略的示意图

    周末把《蛮荒记》看完了,前面是发现微信读书有《搜神记》和《蛮荒记》,但是《搜神记》看了会发现很多都是跳段了,不知道为啥,貌似也没什么少儿不宜的情节,所以就上网找了原版来看,为什么看这个呢,主要还是高中的时候看过,觉得写得很不错,属于那时候的玄幻小说里的独一档,基于山海经创造了一个半架空的大荒宇宙,五族帝尊,人物名都是听说过的,而且又能契合部分历史,整个故事布局非常宏大,并且情节矛盾埋得很深,这里就不对具体情节作介绍了,只是聊聊对书中的一些人物和情节的看法感受。

    乌丝兰玛是个贯穿两部,甚至在蛮荒的最后还要再搞事情,极其坚定的自以为是的大 boss,其实除了最后被我们的主人公打败,前面几乎就是无所不能,下了一盘无比巨大的棋,主人公都只是其中一个棋子和意外,但是正如很多反派,一直以来都是背着一个信念,并且这个所谓的信念是比较正义的,只是为了这个正义的信念和目标却做了各种丧尽天良的事情,说起来跟灭霸有点像,为了环保哈哈,相对来说感觉姬远玄也只是个最大牌的工具人,或者说是中间人,深爱的妹妹冰夷也意外被蚩尤怒拿一血。

    但是中间那个赤霞仙子一定要给烈烟石的心上锁,导致最后认不出来蚩尤,也间接导致了蚩尤被杀,如果不考虑最后情节或者推动故事的需求,这个还是我很讨厌的,有点类似于《驴得水》里那个校长,看着貌似是个正常的,做的事情也是正派,但是其实是害人不浅,即使南阳仙子因此被抛进了火山,那也是有贱人在那挑食,并且赤松子是赤飚怒的儿子,烈烟石跟蚩尤又没这层关系,就很像倚天屠龙记里的灭绝师太和极品家丁里的那个玉德仙坊的院主,后者还好一些,前者几乎就是导致周芷若一生悲剧的始作俑者,自己偏执的善恶观,还要给徒弟灌输如此恶毒的理念和让她立下像紧箍咒似的誓言,在人一生中本来就有很多不能如愿的,又被最亲最尊敬的人下了这样的紧箍咒,人生的不幸也加倍了。

    似乎习惯了总要有个总结的,想说的应该是我觉得这些剧也好,书也好,我觉得最坏的人可能是大部分人眼中的一些次要人物,或者至少大 boss 才是最坏的人,当然这个坏也不是严格的二分法,只是我觉得最让我觉得负面的人物,这些人可能看起来情景出现的不多,只是说了很少的话,做了很少的事,但是在我看来却做了最大的恶。

    题目介绍

    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) {
    +    }

    具体可以看一下这个简略的示意图

    周末把《蛮荒记》看完了,前面是发现微信读书有《搜神记》和《蛮荒记》,但是《搜神记》看了会发现很多都是跳段了,不知道为啥,貌似也没什么少儿不宜的情节,所以就上网找了原版来看,为什么看这个呢,主要还是高中的时候看过,觉得写得很不错,属于那时候的玄幻小说里的独一档,基于山海经创造了一个半架空的大荒宇宙,五族帝尊,人物名都是听说过的,而且又能契合部分历史,整个故事布局非常宏大,并且情节矛盾埋得很深,这里就不对具体情节作介绍了,只是聊聊对书中的一些人物和情节的看法感受。

    乌丝兰玛是个贯穿两部,甚至在蛮荒的最后还要再搞事情,极其坚定的自以为是的大 boss,其实除了最后被我们的主人公打败,前面几乎就是无所不能,下了一盘无比巨大的棋,主人公都只是其中一个棋子和意外,但是正如很多反派,一直以来都是背着一个信念,并且这个所谓的信念是比较正义的,只是为了这个正义的信念和目标却做了各种丧尽天良的事情,说起来跟灭霸有点像,为了环保哈哈,相对来说感觉姬远玄也只是个最大牌的工具人,或者说是中间人,深爱的妹妹冰夷也意外被蚩尤怒拿一血。

    但是中间那个赤霞仙子一定要给烈烟石的心上锁,导致最后认不出来蚩尤,也间接导致了蚩尤被杀,如果不考虑最后情节或者推动故事的需求,这个还是我很讨厌的,有点类似于《驴得水》里那个校长,看着貌似是个正常的,做的事情也是正派,但是其实是害人不浅,即使南阳仙子因此被抛进了火山,那也是有贱人在那挑食,并且赤松子是赤飚怒的儿子,烈烟石跟蚩尤又没这层关系,就很像倚天屠龙记里的灭绝师太和极品家丁里的那个玉德仙坊的院主,后者还好一些,前者几乎就是导致周芷若一生悲剧的始作俑者,自己偏执的善恶观,还要给徒弟灌输如此恶毒的理念和让她立下像紧箍咒似的誓言,在人一生中本来就有很多不能如愿的,又被最亲最尊敬的人下了这样的紧箍咒,人生的不幸也加倍了。

    似乎习惯了总要有个总结的,想说的应该是我觉得这些剧也好,书也好,我觉得最坏的人可能是大部分人眼中的一些次要人物,或者至少大 boss 才是最坏的人,当然这个坏也不是严格的二分法,只是我觉得最让我觉得负面的人物,这些人可能看起来情景出现的不多,只是说了很少的话,做了很少的事,但是在我看来却做了最大的恶。

    题目介绍

    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;
    @@ -218,4 +218,4 @@ Output: 0

    结果

    \ No newline at end of file + }

    结果

    \ No newline at end of file diff --git a/page/9/index.html b/page/9/index.html index e29ced5493..805e9c1b82 100644 --- a/page/9/index.html +++ b/page/9/index.html @@ -1,7 +1,7 @@ Nicksxs's Blog

    Nicksxs's Blog

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

    0%

    ConsumeQueue 其实是定位到一个 topic 下的消息在 CommitLog 下的偏移量,它也是固定大小的

    // ConsumeQueue file size,default is 30W
     private int mapedFileSizeConsumeQueue = 300000 * ConsumeQueue.CQ_STORE_UNIT_SIZE;
     
    -public static final int CQ_STORE_UNIT_SIZE = 20;

    所以文件大小是5.7M 左右

    5udpag

    ConsumeQueue 的构建是通过org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService运行后的 doReput 方法,而启动是的 reputFromOffset 则是通过org.apache.rocketmq.store.DefaultMessageStore#start中下面代码设置并启动

    log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
    +public static final int CQ_STORE_UNIT_SIZE = 20;

    所以文件大小是5.7M 左右

    5udpag

    ConsumeQueue 的构建是通过org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService运行后的 doReput 方法,而启动是的 reputFromOffset 则是通过org.apache.rocketmq.store.DefaultMessageStore#start中下面代码设置并启动

    log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
                     maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset());
                 this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);
                 this.reputMessageService.start();

    看一下 doReput 的逻辑

    private void doReput() {
    @@ -111,7 +111,7 @@
         this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);
         this.byteBufferIndex.putLong(offset);
         this.byteBufferIndex.putInt(size);
    -    this.byteBufferIndex.putLong(tagsCode);

    这里也可以看到 ConsumeQueue 的存储格式,

    AA6Tve

    偏移量,消息大小,跟 tag 的 hashCode

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便。

    首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置

    第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 这个类,他的determineCurrentLookupKey方法会被调用来获得用来决定选择那个数据源的对象,也就是 lookupKey,也可以通过这个类看到就是通过这个 lookupKey 来路由找到数据源。

    public class DynamicDataSource extends AbstractRoutingDataSource {
    +    this.byteBufferIndex.putLong(tagsCode);

    这里也可以看到 ConsumeQueue 的存储格式,

    AA6Tve

    偏移量,消息大小,跟 tag 的 hashCode

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便。

    首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置

    第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 这个类,他的determineCurrentLookupKey方法会被调用来获得用来决定选择那个数据源的对象,也就是 lookupKey,也可以通过这个类看到就是通过这个 lookupKey 来路由找到数据源。

    public class DynamicDataSource extends AbstractRoutingDataSource {
     
         @Override
         protected Object determineCurrentLookupKey() {
    @@ -120,7 +120,7 @@
             }
             return DatabaseType.MASTER1.getName();
         }
    -}

    而如何使用这个 lookupKey 呢,就涉及到我们的 DataSource 配置了,原来就是我们可以直接通过spring 的 jdbc 配置数据源,像这样

    现在我们要使用 Druid 作为数据源了,然后配置 DynamicDataSource的参数,通过 key 来选择对应的 DataSource,也就是下面配的 master1 和 master2

    <bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
    +}

    而如何使用这个 lookupKey 呢,就涉及到我们的 DataSource 配置了,原来就是我们可以直接通过spring 的 jdbc 配置数据源,像这样

    现在我们要使用 Druid 作为数据源了,然后配置 DynamicDataSource的参数,通过 key 来选择对应的 DataSource,也就是下面配的 master1 和 master2

    <bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
               destroy-method="close"
               p:driverClassName="com.mysql.cj.jdbc.Driver"
               p:url="${master1.demo.datasource.url}"
    @@ -264,13 +264,13 @@
         public void after() {
     				DatabaseContextHolder.clearDatabaseType();
         }
    -}

    通过接口判断是否带有注解跟是注解的值,DatabaseType 的配置不太好,不过先忽略了,然后在切点后进行清理

    这是我 master1 的数据,

    master2 的数据

    然后跑一下简单的 demo,

    @Override
    +}

    通过接口判断是否带有注解跟是注解的值,DatabaseType 的配置不太好,不过先忽略了,然后在切点后进行清理

    这是我 master1 的数据,

    master2 的数据

    然后跑一下简单的 demo,

    @Override
     public void run(String...args) {
     	LOGGER.info("run here");
     	System.out.println(studentService.queryOne());
     	System.out.println(studentService.queryAnother());
     
    -}

    看一下运行结果

    其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

    这个话题是由一次组内同学分享引出来的,首先在 springboot 2.x 开始默认使用了 cglib 作为 aop 的实现,这里也稍微讲一下,在一个 1.x 的老项目里,可以看到AopAutoConfiguration 是这样的

    @Configuration
    +}

    看一下运行结果

    其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

    这个话题是由一次组内同学分享引出来的,首先在 springboot 2.x 开始默认使用了 cglib 作为 aop 的实现,这里也稍微讲一下,在一个 1.x 的老项目里,可以看到AopAutoConfiguration 是这样的

    @Configuration
     @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
     @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
     public class AopAutoConfiguration {
    @@ -351,7 +351,7 @@
     //        Integer t = Integer.valueOf("aaa");
             return studentManager.createStudent();
         }
    -}

    第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久

    EdR7oB

    报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢

    这个问题着实查了很久,不废话来看代码

    @Override
    +}

    第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久

    EdR7oB

    报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢

    这个问题着实查了很久,不废话来看代码

    @Override
     		protected Object invokeJoinpoint() throws Throwable {
     			if (this.methodProxy != null) {
             // 这里的 target 就是被代理的 bean
    @@ -411,7 +411,7 @@
     		this.advisedBeans.put(cacheKey, Boolean.FALSE);
     		return bean;
     	}

    然后在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy 中创建了代理类

    CommitLog 结构

    CommitLog 是 rocketmq 的服务端,也就是 broker 存储消息的的文件,跟 kafka 一样,也是顺序写入,当然消息是变长的,生成的规则是每个文件的默认1G =1024 * 1024 * 1024,commitlog的文件名fileName,名字长度为20位,左边补零,剩余为起始偏移量;比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1 073 741 824Byte;当这个文件满了,第二个文件名字为00000000001073741824,起始偏移量为1073741824, 消息存储的时候会顺序写入文件,当文件满了则写入下一个文件,代码中的定义

    // CommitLog file size,default is 1G
    -private int mapedFileSizeCommitLog = 1024 * 1024 * 1024;

    kLahwW

    本地跑个 demo 验证下,也是这样,这里奇妙有几个比较巧妙的点(个人观点),首先文件就刚好是 1G,并且按照大小偏移量去生成下一个文件,这样获取消息的时候按大小算一下就知道在哪个文件里了,

    代码中写入 CommitLog 的逻辑可以从这开始看

    public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
    +private int mapedFileSizeCommitLog = 1024 * 1024 * 1024;

    kLahwW

    本地跑个 demo 验证下,也是这样,这里奇妙有几个比较巧妙的点(个人观点),首先文件就刚好是 1G,并且按照大小偏移量去生成下一个文件,这样获取消息的时候按大小算一下就知道在哪个文件里了,

    代码中写入 CommitLog 的逻辑可以从这开始看

    public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
             // Set the storage time
             msg.setStoreTimestamp(System.currentTimeMillis());
             // Set the message body BODY CRC (consider the most appropriate setting
    @@ -754,8 +754,8 @@
             }
             return true;
         }

    这是个很大的话题了,可能会分成两部分说,第一部分就是所谓的零拷贝 ( zero-copy ),这一块其实也不新鲜,我对零拷贝的概念主要来自这篇文章,个人感觉写得非常好,在 rocketmq 中,最大的一块存储就是消息存储,也就是 CommitLog ,当然还有 ConsumeQueue 和 IndexFile,以及其他一些文件,CommitLog 的存储是以一个 1G 大小的文件作为存储单位,写完了就再建一个,那么如何提高这 1G 文件的读写效率呢,就是 mmap,传统意义的读写文件,read,write 都需要由系统调用,来回地在用户态跟内核态进行拷贝切换,

    read(file, tmp_buf, len);
    -write(socket, tmp_buf, len);

    vms95Z

    如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,

    1. 第一步是调用 read,需要在用户态切换成内核态,DMA模块从磁盘中读取文件,并存储在内核缓冲区,相当于是第一次复制
    2. 数据从内核缓冲区被拷贝到用户缓冲区,read 调用返回,伴随着内核态又切换成用户态,完成了第二次复制
    3. 然后是write 写入,这里也会伴随着用户态跟内核态的切换,数据从用户缓冲区被复制到内核空间缓冲区,完成了第三次复制,这次有点不一样的是数据不是在内核缓冲区了,会复制到 socket buffer 中。
    4. write 系统调用返回,又切换回了用户态,然后数据由 DMA 拷贝到协议引擎。

    如此就能看出其实默认的读写操作代价是非常大的,而在 rocketmq 等高性能中间件中都有使用的零拷贝技术,其中 rocketmq 使用的是 mmap

    mmap

    mmap基于 OS 的 mmap 的内存映射技术,通过MMU 映射文件,将文件直接映射到用户态的内存地址,使得对文件的操作不再是 write/read,而转化为直接对内存地址的操作,使随机读写文件和读写内存相似的速度。

    mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,这样的文件读写文件方式少了数据从内核缓存到用户空间的拷贝,效率很高。

    tmp_buf = mmap(file, len);
    -write(socket, tmp_buf, len);

    I68mFx

    第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。

    第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。

    第三步:第三次拷贝发生在DMA引擎将数据从内核套接字缓冲区传递给协议引擎时。

    通过使用mmap而不是read,我们将内核需要拷贝的数据量减少了一半。当大量的数据被传输时,这将有很好的效果。然而,这种改进并不是没有代价的;在使用mmap+write方法时,有一些隐藏的陷阱。例如当你对一个文件进行内存映射,然后在另一个进程截断同一文件时调用写。你的写系统调用将被总线错误信号SIGBUS打断,因为你执行了一个错误的内存访问。该信号的默认行为是杀死进程并dumpcore–这对网络服务器来说不是最理想的操作。

    有两种方法可以解决这个问题。

    第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用返回。通过这样做,写系统调用会返回它在被打断之前所写的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,一个治标不治本的解决方案。因为SIGBUS预示着进程出了严重的问题,所以不鼓励使用这种解决方案。

    第二个解决方案涉及内核的文件租赁(在Windows中称为 “机会锁”)。这是解决这个问题的正确方法。通过在文件描述符上使用租赁,你与内核在一个特定的文件上达成租约。然后你可以向内核请求一个读/写租约。当另一个进程试图截断你正在传输的文件时,内核会向你发送一个实时信号,即RT_SIGNAL_LEASE信号。它告诉你内核即将终止你对该文件的写或读租约。在你的程序访问一个无效的地址和被SIGBUS信号杀死之前,你的写调用会被打断了。写入调用的返回值是中断前写入的字节数,errno将被设置为成功。下面是一些示例代码,显示了如何从内核中获得租约。

    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    +write(socket, tmp_buf, len);

    vms95Z

    如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,

    1. 第一步是调用 read,需要在用户态切换成内核态,DMA模块从磁盘中读取文件,并存储在内核缓冲区,相当于是第一次复制
    2. 数据从内核缓冲区被拷贝到用户缓冲区,read 调用返回,伴随着内核态又切换成用户态,完成了第二次复制
    3. 然后是write 写入,这里也会伴随着用户态跟内核态的切换,数据从用户缓冲区被复制到内核空间缓冲区,完成了第三次复制,这次有点不一样的是数据不是在内核缓冲区了,会复制到 socket buffer 中。
    4. write 系统调用返回,又切换回了用户态,然后数据由 DMA 拷贝到协议引擎。

    如此就能看出其实默认的读写操作代价是非常大的,而在 rocketmq 等高性能中间件中都有使用的零拷贝技术,其中 rocketmq 使用的是 mmap

    mmap

    mmap基于 OS 的 mmap 的内存映射技术,通过MMU 映射文件,将文件直接映射到用户态的内存地址,使得对文件的操作不再是 write/read,而转化为直接对内存地址的操作,使随机读写文件和读写内存相似的速度。

    mmap 把文件映射到用户空间里的虚拟内存,省去了从内核缓冲区复制到用户空间的过程,文件中的位置在虚拟内存中有了对应的地址,可以像操作内存一样操作这个文件,这样的文件读写文件方式少了数据从内核缓存到用户空间的拷贝,效率很高。

    tmp_buf = mmap(file, len);
    +write(socket, tmp_buf, len);

    I68mFx

    第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。

    第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。

    第三步:第三次拷贝发生在DMA引擎将数据从内核套接字缓冲区传递给协议引擎时。

    通过使用mmap而不是read,我们将内核需要拷贝的数据量减少了一半。当大量的数据被传输时,这将有很好的效果。然而,这种改进并不是没有代价的;在使用mmap+write方法时,有一些隐藏的陷阱。例如当你对一个文件进行内存映射,然后在另一个进程截断同一文件时调用写。你的写系统调用将被总线错误信号SIGBUS打断,因为你执行了一个错误的内存访问。该信号的默认行为是杀死进程并dumpcore–这对网络服务器来说不是最理想的操作。

    有两种方法可以解决这个问题。

    第一种方法是为SIGBUS信号安装一个信号处理程序,然后在处理程序中简单地调用返回。通过这样做,写系统调用会返回它在被打断之前所写的字节数,并将errno设置为成功。让我指出,这将是一个糟糕的解决方案,一个治标不治本的解决方案。因为SIGBUS预示着进程出了严重的问题,所以不鼓励使用这种解决方案。

    第二个解决方案涉及内核的文件租赁(在Windows中称为 “机会锁”)。这是解决这个问题的正确方法。通过在文件描述符上使用租赁,你与内核在一个特定的文件上达成租约。然后你可以向内核请求一个读/写租约。当另一个进程试图截断你正在传输的文件时,内核会向你发送一个实时信号,即RT_SIGNAL_LEASE信号。它告诉你内核即将终止你对该文件的写或读租约。在你的程序访问一个无效的地址和被SIGBUS信号杀死之前,你的写调用会被打断了。写入调用的返回值是中断前写入的字节数,errno将被设置为成功。下面是一些示例代码,显示了如何从内核中获得租约。

    if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
         perror("kernel lease set signal");
         return -1;
     }
    diff --git a/search.xml b/search.xml
    index 2c4f38abaa..0c5cd43b50 100644
    --- a/search.xml
    +++ b/search.xml
    @@ -1,30 +1,5 @@
     
     
    -  
    -    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 - 拖更 - -
    村上春树《1Q84》读后感 /2019/12/18/1Q84%E8%AF%BB%E5%90%8E%E6%84%9F/ @@ -74,6 +49,31 @@ 2019 + + 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 + 拖更 + +
    2020年中总结 /2020/07/11/2020%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ @@ -93,6 +93,46 @@ 年中总结 + + 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 + 年中总结 + 技术 + 读书 + +
    + + 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/ @@ -197,7 +237,7 @@ public: private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter;
    -

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    +

    主要的就这么点,而且也复用了 AQS 阻塞队列或者大大叫 lock queue中同样的 Node 节点,只不过它没有使用其中的双向队列,也就是prev 和 next,而是在 Node 中的 nextWaiter,所以只是个单向的队列,没使用 next 其实还有个用处,后面会提到,看下结构的示意图

    然后主要是看两个方法,awaitsignal,
    先来看下 await

    /**
      * Implements interruptible condition wait.
      * <ol>
    @@ -637,357 +677,61 @@ public:
           
       
       
    -    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 - 年中总结 - 技术 - 读书 - -
    - - 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 - 拖更 - -
    - - 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;
    -    }
    -};
    + 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;
    +        }
    +    }
    -

    失败的代码

    /**
    - * 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;
    -        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 && cn == 0){
    -                flag = 1;
    -                break;
    -            }
    -            if(!l2 && cn == 0){
    -                flag = 1;
    -                break;
    -            }
    -            if(l1){
    -                l1 = l1->next;
    -            }
    -            if(l2){
    -                l2 = l2->next;
    -            }
    -        }
    -        if(!l1 && cn == 0 && flag == 1){
    -            p->next = l2->next;
    -        }
    -        if(!l2 && cn == 0 && flag == 1){
    -            p->next = l1->next;
    -        }
    -        if(cn != 0){
    -            p->next = new ListNode(cn);
    -            p = p->next;
    -        }
    -        return dummy.next;
    -    }
    -};
    -]]>
    - - leetcode - - - leetcode - c++ - -
    - - Apollo 的 value 注解是怎么自动更新的 - /2020/11/01/Apollo-%E7%9A%84-value-%E6%B3%A8%E8%A7%A3%E6%98%AF%E6%80%8E%E4%B9%88%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0%E7%9A%84/ - 在前司和目前公司,用的配置中心都是使用的 Apollo,经过了业界验证,比较强大的配置管理系统,特别是在0.10 后开始支持对使用 value 注解的配置值进行自动更新,今天刚好有个同学问到我,就顺便写篇文章记录下,其实也是借助于 spring 强大的 bean 生命周期管理,可以实现BeanPostProcessor接口,使用postProcessBeforeInitialization方法,来对bean 内部的属性和方法进行判断,是否有 value 注解,如果有就是将它注册到一个 map 中,可以看到这个方法com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor#processField

    -
    @Override
    -  protected void processField(Object bean, String beanName, Field field) {
    -    // register @Value on field
    -    Value value = field.getAnnotation(Value.class);
    -    if (value == null) {
    -      return;
    -    }
    -    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
    -
    -    if (keys.isEmpty()) {
    -      return;
    -    }
    -
    -    for (String key : keys) {
    -      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
    -      springValueRegistry.register(beanFactory, key, springValue);
    -      logger.debug("Monitoring {}", springValue);
    -    }
    -  }
    -

    然后我们看下这个springValueRegistry是啥玩意

    -
    public class SpringValueRegistry {
    -  private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
    -  private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
    -  private final AtomicBoolean initialized = new AtomicBoolean(false);
    -  private final Object LOCK = new Object();
    -
    -  public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
    -    if (!registry.containsKey(beanFactory)) {
    -      synchronized (LOCK) {
    -        if (!registry.containsKey(beanFactory)) {
    -          registry.put(beanFactory, LinkedListMultimap.<String, SpringValue>create());
    -        }
    -      }
    -    }
    -
    -    registry.get(beanFactory).put(key, springValue);
    -
    -    // lazy initialize
    -    if (initialized.compareAndSet(false, true)) {
    -      initialize();
    -    }
    -  }
    -

    这类其实就是个 map 来存放 springvalue,然后有com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener来监听更新操作,当有变更时

    -
    @Override
    - public void onChange(ConfigChangeEvent changeEvent) {
    -   Set<String> keys = changeEvent.changedKeys();
    -   if (CollectionUtils.isEmpty(keys)) {
    -     return;
    -   }
    -   for (String key : keys) {
    -     // 1. check whether the changed key is relevant
    -     Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
    -     if (targetValues == null || targetValues.isEmpty()) {
    -       continue;
    -     }
    -
    -     // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
    -     // 这里其实有一点比较绕,是因为 Apollo 里的 namespace 划分,会出现 key 相同,但是 namespace 不同的情况,所以会有个优先级存在,所以需要去校验 environment 里面的是否已经更新,如果未更新则表示不需要更新
    -     if (!shouldTriggerAutoUpdate(changeEvent, key)) {
    -       continue;
    -     }
    -
    -     // 3. update the value
    -     for (SpringValue val : targetValues) {
    -       updateSpringValue(val);
    -     }
    -   }
    - }
    -

    其实原理很简单,就是得了解知道下

    -]]>
    - - Java - Apollo - value - - - Java - Apollo - value - 注解 - environment - -
    - - 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 - -
    - - 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();
    -    }
    +

    第二个线程

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

    +
    /**
    +     * 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的逻辑

    /**
    @@ -1161,21 +905,277 @@ public:
           
       
       
    -    Clone Graph Part I
    -    /2014/12/30/Clone-Graph-Part-I/
    -    problem
    Clone a graph. Input is a Node pointer. Return the Node pointer of the cloned graph.
    +    AbstractQueuedSynchronizer
    +    /2019/09/23/AbstractQueuedSynchronizer/
    +    最近看了大神的 AQS 的文章,之前总是断断续续地看一点,每次都知难而退,下次看又从头开始,昨天总算硬着头皮看完了第一部分
    首先 AQS 只要有这些属性

    +
    // 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
    +private transient volatile Node head;
     
    -A graph is defined below:
    -struct Node {
    -vector neighbors;
    -}
    +// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表 +private transient volatile Node tail; - -

    code

    typedef unordered_map<Node *, Node *> Map;
    - 
    -Node *clone(Node *graph) {
    -    if (!graph) return NULL;
    - 
    +// 这个是最重要的,代表当前锁的状态,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;
    +
    +        int cn = 0;
    +        int flag = 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 && cn == 0){
    +                flag = 1;
    +                break;
    +            }
    +            if(!l2 && cn == 0){
    +                flag = 1;
    +                break;
    +            }
    +            if(l1){
    +                l1 = l1->next;
    +            }
    +            if(l2){
    +                l2 = l2->next;
    +            }
    +        }
    +        if(!l1 && cn == 0 && flag == 1){
    +            p->next = l2->next;
    +        }
    +        if(!l2 && cn == 0 && flag == 1){
    +            p->next = l1->next;
    +        }
    +        if(cn != 0){
    +            p->next = new ListNode(cn);
    +            p = p->next;
    +        }
    +        return dummy.next;
    +    }
    +};
    +]]>
    + + leetcode + + + leetcode + c++ + +
    + + Apollo 的 value 注解是怎么自动更新的 + /2020/11/01/Apollo-%E7%9A%84-value-%E6%B3%A8%E8%A7%A3%E6%98%AF%E6%80%8E%E4%B9%88%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0%E7%9A%84/ + 在前司和目前公司,用的配置中心都是使用的 Apollo,经过了业界验证,比较强大的配置管理系统,特别是在0.10 后开始支持对使用 value 注解的配置值进行自动更新,今天刚好有个同学问到我,就顺便写篇文章记录下,其实也是借助于 spring 强大的 bean 生命周期管理,可以实现BeanPostProcessor接口,使用postProcessBeforeInitialization方法,来对bean 内部的属性和方法进行判断,是否有 value 注解,如果有就是将它注册到一个 map 中,可以看到这个方法com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor#processField

    +
    @Override
    +  protected void processField(Object bean, String beanName, Field field) {
    +    // register @Value on field
    +    Value value = field.getAnnotation(Value.class);
    +    if (value == null) {
    +      return;
    +    }
    +    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
    +
    +    if (keys.isEmpty()) {
    +      return;
    +    }
    +
    +    for (String key : keys) {
    +      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
    +      springValueRegistry.register(beanFactory, key, springValue);
    +      logger.debug("Monitoring {}", springValue);
    +    }
    +  }
    +

    然后我们看下这个springValueRegistry是啥玩意

    +
    public class SpringValueRegistry {
    +  private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
    +  private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
    +  private final AtomicBoolean initialized = new AtomicBoolean(false);
    +  private final Object LOCK = new Object();
    +
    +  public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
    +    if (!registry.containsKey(beanFactory)) {
    +      synchronized (LOCK) {
    +        if (!registry.containsKey(beanFactory)) {
    +          registry.put(beanFactory, LinkedListMultimap.<String, SpringValue>create());
    +        }
    +      }
    +    }
    +
    +    registry.get(beanFactory).put(key, springValue);
    +
    +    // lazy initialize
    +    if (initialized.compareAndSet(false, true)) {
    +      initialize();
    +    }
    +  }
    +

    这类其实就是个 map 来存放 springvalue,然后有com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener来监听更新操作,当有变更时

    +
    @Override
    + public void onChange(ConfigChangeEvent changeEvent) {
    +   Set<String> keys = changeEvent.changedKeys();
    +   if (CollectionUtils.isEmpty(keys)) {
    +     return;
    +   }
    +   for (String key : keys) {
    +     // 1. check whether the changed key is relevant
    +     Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
    +     if (targetValues == null || targetValues.isEmpty()) {
    +       continue;
    +     }
    +
    +     // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
    +     // 这里其实有一点比较绕,是因为 Apollo 里的 namespace 划分,会出现 key 相同,但是 namespace 不同的情况,所以会有个优先级存在,所以需要去校验 environment 里面的是否已经更新,如果未更新则表示不需要更新
    +     if (!shouldTriggerAutoUpdate(changeEvent, key)) {
    +       continue;
    +     }
    +
    +     // 3. update the value
    +     for (SpringValue val : targetValues) {
    +       updateSpringValue(val);
    +     }
    +   }
    + }
    +

    其实原理很简单,就是得了解知道下

    +]]>
    + + Java + Apollo + value + + + Java + Apollo + value + 注解 + environment + +
    + + Clone Graph Part I + /2014/12/30/Clone-Graph-Part-I/ + problem
    Clone a graph. Input is a Node pointer. Return the Node pointer of the cloned graph.
    +
    +A graph is defined below:
    +struct Node {
    +vector neighbors;
    +}
    + + +

    code

    typedef unordered_map<Node *, Node *> Map;
    + 
    +Node *clone(Node *graph) {
    +    if (!graph) return NULL;
    + 
         Map map;
         queue<Node *> q;
         q.push(graph);
    @@ -1282,36 +1282,164 @@ Node *clone(Node *graph) {
             // if null, non-null Ts are considered equal
             private final Comparator<T> real;
     
    -        @SuppressWarnings("unchecked")
    -        NullComparator(boolean nullFirst, Comparator<? super T> real) {
    -            this.nullFirst = nullFirst;
    -            this.real = (Comparator<T>) real;
    -        }
    +        @SuppressWarnings("unchecked")
    +        NullComparator(boolean nullFirst, Comparator<? super T> real) {
    +            this.nullFirst = nullFirst;
    +            this.real = (Comparator<T>) real;
    +        }
    +
    +        @Override
    +        public int compare(T a, T b) {
    +            if (a == null) {
    +                return (b == null) ? 0 : (nullFirst ? -1 : 1);
    +            } else if (b == null) {
    +                return nullFirst ? 1: -1;
    +            } else {
    +                return (real == null) ? 0 : real.compare(a, b);
    +            }
    +        }
    + +

    核心代码就是下面这段,其实就是帮我们把前面要做的事情做掉了,是不是挺方便的,小记一下哈

    +]]>
    + + Java + 集合 + + + Java + Stream + Comparator + 排序 + sort + nullsfirst + +
    + + 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;
    +
    +    public void set(long value) {
    +        this.value = value;
    +    }
    +
    +    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;
    +
    +public class LongEventMain
    +{
    +    public static void main(String[] args) throws Exception
    +    {
    +        // 这个需要是 2 的幂次,这样在定位的时候只需要位移操作,也能减少各种计算操作
    +        int bufferSize = 1024; 
    +
    +        Disruptor<LongEvent> disruptor = 
    +                new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
    +
    +        // 类似于注册处理器
    +        disruptor.handleEventsWith(new LongEventHandler());
    +        // 或者直接用 lambda
    +        disruptor.handleEventsWith((event, sequence, endOfBatch) ->
    +                System.out.println("Event: " + event));
    +        // 启动我们的 disruptor
    +        disruptor.start(); 
    +
    +
    +        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);
    +        }
    +    }
    +}
    +

    运行下可以看到运行结果

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

    +]]>
    + + Java + + + Java + Disruptor + +
    + + 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;
    +}
     
    -        @Override
    -        public int compare(T a, T b) {
    -            if (a == null) {
    -                return (b == null) ? 0 : (nullFirst ? -1 : 1);
    -            } else if (b == null) {
    -                return nullFirst ? 1: -1;
    -            } else {
    -                return (real == null) ? 0 : real.compare(a, b);
    -            }
    -        }
    +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 - Stream - Comparator - 排序 - sort - nullsfirst + Disruptor
    @@ -1503,7 +1631,7 @@ Node *clone(Node *graph) { public void destroy(); }
    -

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    +

    重点看 servlet 的 service方法,就是接受请求,处理完了给响应,不说细节,不然光 Tomcat 的能说半年,所以呢再进一步去理解,其实就能知道,就是一个先后的问题,盗个图

    filter 跟后两者最大的不一样其实是一个基于 servlet,在非常外层做的处理,然后是 interceptor 的 prehandle 跟 posthandle,接着才是我们常规的 aop,就这么点事情,做个小试验吧(还是先补段代码吧)

    Filter

    // ---------------------------------------------------- FilterChain Methods
     
         /**
    @@ -1781,7 +1909,7 @@ Node *clone(Node *graph) {
             return "hello world";
         }
     }
    -

    好了,请求一下,看看 stdout,

    搞定完事儿~

    +

    好了,请求一下,看看 stdout,

    搞定完事儿~

    ]]>
    Java @@ -1886,45 +2014,6 @@ Node *clone(Node *graph) { Garbage-First Collector
    - - 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 的问题。

    -]]>
    - - Java - - - Java - Disruptor - -
    JVM源码分析之G1垃圾收集器分析一 /2019/12/07/JVM-G1-Part-1/ @@ -2536,7 +2625,7 @@ Node *clone(Node *graph) { /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

    +

    示例 1

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

    @@ -2591,7 +2680,7 @@ Node *clone(Node *graph) { return merged; }
    -

    结果

    +

    结果

    ]]> Java @@ -2603,95 +2692,6 @@ 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;
    -
    -    public void set(long value) {
    -        this.value = value;
    -    }
    -
    -    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;
    -
    -public class LongEventMain
    -{
    -    public static void main(String[] args) throws Exception
    -    {
    -        // 这个需要是 2 的幂次,这样在定位的时候只需要位移操作,也能减少各种计算操作
    -        int bufferSize = 1024; 
    -
    -        Disruptor<LongEvent> disruptor = 
    -                new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
    -
    -        // 类似于注册处理器
    -        disruptor.handleEventsWith(new LongEventHandler());
    -        // 或者直接用 lambda
    -        disruptor.handleEventsWith((event, sequence, endOfBatch) ->
    -                System.out.println("Event: " + event));
    -        // 启动我们的 disruptor
    -        disruptor.start(); 
    -
    -
    -        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);
    -        }
    -    }
    -}
    -

    运行下可以看到运行结果

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

    -]]>
    - - 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/ @@ -2906,22 +2906,93 @@ inorder = [9,3,15,20,7] + + 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 - Binary Tree - 二叉树 题解 - 递归 - Preorder Traversal - Inorder Traversal - 前序 - 中序 + Print FooBar Alternately
    @@ -2982,7 +3053,7 @@ inorder = [9,3,15,20,7] + + Leetcode 3 Longest Substring Without Repeating Characters 题解分析 + /2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 又做了个题,看记录是以前用 C++写过的,现在捋一捋思路,用 Java 再写了一下,思路还是比较清晰的,但是边界细节处理得比较差

    +

    简要介绍

    Given a string s, find the length of the longest substring without repeating characters.

    +

    样例

    Example 1:

    Input: s = "abcabcbb"
    +Output: 3
    +Explanation: The answer is "abc", with the length of 3.
    + +

    Example 2:

    Input: s = "bbbbb"
    +Output: 1
    +Explanation: The answer is "b", with the length of 1.
    +

    Example 3:

    Input: s = "pwwkew"
    +Output: 3
    +Explanation: The answer is "wke", with the length of 3.
    +Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
    +

    Example 4:

    Input: s = ""
    +Output: 0
    + +

    就是一个最长不重复的字符串长度,因为也是中等难度的题,不太需要特别复杂的思考,最基本的就是O(N*N)两重循环,不过显然不太好,万一超时间,还有一种就是线性复杂度的了,这个就是需要搞定一个思路,比如字符串时 abcdefgaqwrty,比如遍历到第二个a的时候其实不用再从头去遍历了,只要把前面那个a给排除掉,继续往下算就好了

    +
    class Solution {
    +    Map<String, Integer> counter = new HashMap<>();
    +    public int lengthOfLongestSubstring(String s) {
    +        int length = s.length();
    +        // 当前的长度
    +        int subStringLength = 0;
    +        // 最长的长度
    +        int maxSubStringLength = 0;
    +        // 考虑到重复的位置已经被跳过的情况,即已经在当前长度的字符串范围之前的重复字符不需要回溯
    +        int lastDuplicatePos = -1;
    +        for (int i = 0; i < length; i++) {
    +            // 使用 map 存储字符和上一次出现的位置,如果存在并且大于上一次重复位置
    +            if (counter.get(String.valueOf(s.charAt(i))) != null && counter.get(String.valueOf(s.charAt(i))) > lastDuplicatePos) {
    +                // 记录重复位置
    +                lastDuplicatePos = counter.get(String.valueOf(s.charAt(i)));
    +                // 重置不重复子串的长度,减去重复起点
    +                subStringLength = i - counter.get(String.valueOf(s.charAt(i))) - 1;
    +                // 替换当前位置
    +                counter.replace(String.valueOf(s.charAt(i)), i);
    +            } else {
    +                // 如果不存在就直接 put
    +                counter.put(String.valueOf(s.charAt(i)), i);
    +            }
    +            // 长度累加
    +            subStringLength++;
    +            if (subStringLength > maxSubStringLength) {
    +                // 简单替换
    +                maxSubStringLength = subStringLength;
    +            }
    +        }
    +        return maxSubStringLength;
    +    }
    +}
    +

    注释应该写的比较清楚了。

    +]]>
    + + Java + leetcode + java + 字符串 - online + string + + + leetcode + java + 题解 + string + +
    Leetcode 349 两个数组的交集 ( Intersection of Two Arrays *Easy* ) 题解分析 /2022/03/07/Leetcode-349-%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86-Intersection-of-Two-Arrays-Easy-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ @@ -3414,80 +3554,82 @@ Output: [8,9,9,9,0,0,0,1] - Leetcode 3 Longest Substring Without Repeating Characters 题解分析 - /2020/09/20/Leetcode-3-Longest-Substring-Without-Repeating-Characters-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 又做了个题,看记录是以前用 C++写过的,现在捋一捋思路,用 Java 再写了一下,思路还是比较清晰的,但是边界细节处理得比较差

    -

    简要介绍

    Given a string s, find the length of the longest substring without repeating characters.

    -

    样例

    Example 1:

    Input: s = "abcabcbb"
    -Output: 3
    -Explanation: The answer is "abc", with the length of 3.
    + Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析 + /2022/03/27/Leetcode-4-%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0-Median-of-Two-Sorted-Arrays-Hard-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    给定两个大小分别为 mn 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

    +

    算法的时间复杂度应该为 O(log (m+n))

    +

    示例 1:

    +

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    +
    +

    示例 2:

    +

    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    +
    +

    分析与题解

    这个题也是我随机出来的,之前都是随机到 easy 的,而且是序号这么靠前的,然后翻一下,之前应该是用 C++做过的,具体的方法其实可以从他的算法时间复杂度要求看出来,大概率是要二分法这种,后面就结合代码来讲了

    +
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    +        int n1 = nums1.length;
    +        int n2 = nums2.length;
    +        if (n1 > n2) {
    +            return findMedianSortedArrays(nums2, nums1);
    +        }
     
    -

    Example 2:

    Input: s = "bbbbb"
    -Output: 1
    -Explanation: The answer is "b", with the length of 1.
    -

    Example 3:

    Input: s = "pwwkew"
    -Output: 3
    -Explanation: The answer is "wke", with the length of 3.
    -Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
    -

    Example 4:

    Input: s = ""
    -Output: 0
    + // 找到两个数组的中点下标 + int k = (n1 + n2 + 1 ) / 2; + // 使用一个类似于二分法的查找方法 + // 起始值就是 num1 的头跟尾 + int left = 0; + int right = n1; + while (left < right) { + // m1 表示我取的是 nums1 的中点,即二分法的方式 + int m1 = left + (right - left) / 2; + // *** 这里是重点,因为这个问题也可以转换成找成 n1 + n2 那么多个数中的前 (n1 + n2 + 1) / 2 个 + // *** 因为两个数组都是排好序的,那么我从 num1 中取了 m1 个,从 num2 中就是去 k - m1 个 + // *** 但是不知道取出来大小是否正好是整体排序的第 (n1 + n2 + 1) / 2 个,所以需要二分法上下对比 + int m2 = k - m1; + // 如果 nums1[m1] 小,那我在第一个数组 nums1 的二分查找就要把左端点改成前一次的中点 + 1 (不然就进死循环了 + if (nums1[m1] < nums2[m2 - 1]) { + left = m1 + 1; + } else { + right = m1; + } + } -

    就是一个最长不重复的字符串长度,因为也是中等难度的题,不太需要特别复杂的思考,最基本的就是O(N*N)两重循环,不过显然不太好,万一超时间,还有一种就是线性复杂度的了,这个就是需要搞定一个思路,比如字符串时 abcdefgaqwrty,比如遍历到第二个a的时候其实不用再从头去遍历了,只要把前面那个a给排除掉,继续往下算就好了

    -
    class Solution {
    -    Map<String, Integer> counter = new HashMap<>();
    -    public int lengthOfLongestSubstring(String s) {
    -        int length = s.length();
    -        // 当前的长度
    -        int subStringLength = 0;
    -        // 最长的长度
    -        int maxSubStringLength = 0;
    -        // 考虑到重复的位置已经被跳过的情况,即已经在当前长度的字符串范围之前的重复字符不需要回溯
    -        int lastDuplicatePos = -1;
    -        for (int i = 0; i < length; i++) {
    -            // 使用 map 存储字符和上一次出现的位置,如果存在并且大于上一次重复位置
    -            if (counter.get(String.valueOf(s.charAt(i))) != null && counter.get(String.valueOf(s.charAt(i))) > lastDuplicatePos) {
    -                // 记录重复位置
    -                lastDuplicatePos = counter.get(String.valueOf(s.charAt(i)));
    -                // 重置不重复子串的长度,减去重复起点
    -                subStringLength = i - counter.get(String.valueOf(s.charAt(i))) - 1;
    -                // 替换当前位置
    -                counter.replace(String.valueOf(s.charAt(i)), i);
    -            } else {
    -                // 如果不存在就直接 put
    -                counter.put(String.valueOf(s.charAt(i)), i);
    -            }
    -            // 长度累加
    -            subStringLength++;
    -            if (subStringLength > maxSubStringLength) {
    -                // 简单替换
    -                maxSubStringLength = subStringLength;
    -            }
    -        }
    -        return maxSubStringLength;
    -    }
    -}
    -

    注释应该写的比较清楚了。

    + // 因为对比后其实我们只是拿到了一个位置,具体哪个是第 k 个就需要继续判断 + int m1 = left; + int m2 = k - left; + // 如果 m1 或者 m2 有小于等于 0 的,那这个值可以先抛弃 + // m1 如果等于 0,就是 num1[0] 都比 nums2 中所有值都要大 + // m2 等于 0 的话 刚好相反 + // 可以这么推断,当其中一个是 0 的时候那么另一个 mx 值肯定是> 0 的,那么就是取的对应的这个下标的值 + int c1 = Math.max( m1 <= 0 ? Integer.MIN_VALUE : nums1[m1 - 1] , m2 <= 0 ? Integer.MIN_VALUE : nums2[m2 - 1]); + // 如果两个数组的元素数量和是奇数,那就直接可以返回了,因为 m1 + m2 就是 k, 如果是一个数组,那这个元素其实就是 nums[k - 1] + // 如果 m1 或者 m2 是 0,那另一个就是 k,取 mx - 1的下标就等于是 k - 1 + // 如果都不是 0,那就是取的了 nums1[m1 - 1] 与 nums2[m2 - 1]中的较大者,如果取得是后者,那么也就是 m1 + m2 - 1 的下标就是 k - 1 + if ((n1 + n2) % 2 == 1) { + return c1; + } + // 如果是偶数个,那还要取两个数组后面的较小者,然后求平均值 + int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1], m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]); + return (c1 + c2) / 2.0; + }
    +

    前面考虑的方法还是比较繁琐,考虑了两个数组的各种交叉情况,后面这个参考了一些网上的解法,代码比较简洁,但是可能不容易一下子就搞明白,所以配合了比较多的注释。

    ]]>
    Java leetcode - java - 字符串 - online - string leetcode java 题解 - string + Median of Two Sorted Arrays
    Leetcode 42 接雨水 (Trapping Rain Water) 题解分析 /2021/07/04/Leetcode-42-%E6%8E%A5%E9%9B%A8%E6%B0%B4-Trapping-Rain-Water-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ 题目介绍

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

    -

    示例


    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6
    解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

    -

    简单分析

    其实最开始的想法是从左到右扫区间,就是示例中的第一个水槽跟第二个水槽都可以用这个办法解决

    前面这种是属于右侧比左侧高的情况,对于左侧高右侧低的就不行了,(写这篇的时候想起来可以再反着扫一遍可能可以)

    所以这个方案不好,贴一下这个方案的代码

    +

    示例


    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6
    解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

    +

    简单分析

    其实最开始的想法是从左到右扫区间,就是示例中的第一个水槽跟第二个水槽都可以用这个办法解决

    前面这种是属于右侧比左侧高的情况,对于左侧高右侧低的就不行了,(写这篇的时候想起来可以再反着扫一遍可能可以)

    所以这个方案不好,贴一下这个方案的代码

    public int trap(int[] height) {
         int lastLeft = -1;
         int sum = 0;
    @@ -3508,7 +3650,7 @@ Output: 0
    -

    后面结合网上的解法,其实可以反过来,对于每个格子找左右侧的最大值,取小的那个和当前格子的差值就是这一个的储水量了

    理解了这种想法,代码其实就不难了

    +

    后面结合网上的解法,其实可以反过来,对于每个格子找左右侧的最大值,取小的那个和当前格子的差值就是这一个的储水量了

    理解了这种想法,代码其实就不难了

    代码

    int n = height.length;
     if (n <= 2) {
         return 0;
    @@ -3560,7 +3702,7 @@ maxR[n -Leetcode 48 旋转图像(Rotate Image) 题解分析
         /2021/05/01/Leetcode-48-%E6%97%8B%E8%BD%AC%E5%9B%BE%E5%83%8F-Rotate-Image-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/
         题目介绍

    You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

    -

    You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

    如图,这道题以前做过,其实一看有点蒙,好像规则很容易描述,但是代码很难写,因为要类似于贪吃蛇那样,后来想着应该会有一些特殊的技巧,比如翻转等

    +

    You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

    如图,这道题以前做过,其实一看有点蒙,好像规则很容易描述,但是代码很难写,因为要类似于贪吃蛇那样,后来想着应该会有一些特殊的技巧,比如翻转等

    代码

    直接上码

    public void rotate(int[][] matrix) {
             // 这里真的傻了,长宽应该是一致的,所以取一次就够了
    @@ -3602,16 +3744,96 @@ maxR[n -还没到可以直接归纳题目类型的水平,主要是几年前做过,可能有那么点模糊的记忆,当然应该也有直接转的方法

    ]]> - Java + Java + leetcode + Rotate Image + + + leetcode + java + 题解 + Rotate Image + 矩阵 + + + + Leetcode 83 删除排序链表中的重复元素 ( Remove Duplicates from Sorted List *Easy* ) 题解分析 + /2022/03/13/Leetcode-83-%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0-Remove-Duplicates-from-Sorted-List-Easy-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ + 题目介绍

    给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
    PS:注意已排序,还有返回也要已排序

    +

    示例 1:

    +
    +

    输入:head = [1,1,2]
    输出:[1,2]

    +
    +

    示例 2:

    +
    +

    输入:head = [1,1,2,3,3]
    输出:[1,2,3]

    +
    +

    提示:

      +
    • 链表中节点数目在范围 [0, 300]
    • +
    • -100 <= Node.val <= 100
    • +
    • 题目数据保证链表已经按 升序 排列
    • +
    +

    分析与题解

    这题其实是比较正常的 easy 级别的题目,链表已经排好序了,如果还带一个排序就更复杂一点,
    只需要前后项做个对比,如果一致则移除后项,因为可能存在多个重复项,所以只有在前后项不同
    时才会更新被比较项

    +

    code

    public ListNode deleteDuplicates(ListNode head) {
    +    // 链表头是空的或者只有一个头结点,就不用处理了
    +    if (head == null || head.next == null) {
    +        return head;
    +    }
    +    ListNode tail = head;
    +    // 以处理节点还有后续节点作为循环边界条件
    +    while (tail.next != null) {
    +        ListNode temp = tail.next;
    +        // 如果前后相同,那么可以跳过这个节点,将 Tail  ---->   temp  ---> temp.next 
    +        // 更新成  Tail ---->  temp.next
    +        if (temp.val == tail.val) {
    +            tail.next = temp.next;
    +        } else {
    +            // 不相同,则更新 tail
    +            tail = tail.next;
    +        }
    +    }
    +    // 最后返回头结点
    +    return head;
    +}
    +

    链表应该是个需要反复的训练的数据结构,因为涉及到前后指针,然后更新操作,判空等,
    我在这块也是掌握的不太好,需要多练习。

    +]]>
    + + Java + leetcode + + + leetcode + java + 题解 + Remove Duplicates from Sorted List + +
    + + leetcode no.3 + /2015/04/15/Leetcode-No-3/ + **Longest Substring Without Repeating Characters **

    + +

    description

    Given a string, find the length of the longest substring without repeating characters.
    For example, the longest substring without repeating letters for “abcabcbb” is “abc”,
    which the length is 3. For “bbbbb” the longest substring is “b”, with the length of 1.

    +

    分析

    源码这次是参考了这个代码,
    tail 表示的当前子串的起始点位置,tail从-1开始就包括的串的长度是1的边界。其实我
    也是猜的(逃

    +
    int ct[256];
    +    memset(ct, -1, sizeof(ct));
    +	int tail = -1;
    +	int max = 0;
    +	for (int i = 0; i < s.size(); i++){
    +		if (ct[s[i]] > tail)
    +			tail = ct[s[i]];
    +		if (i - tail > max)
    +			max = i - tail;
    +		ct[s[i]] = i;
    +	}
    +	return max;
    +]]>
    + leetcode - Rotate Image leetcode - java - 题解 - Rotate Image - 矩阵 + c++
    @@ -3632,12 +3854,12 @@ maxR[n -好像不行,盲猜就是符号的问题,特别是\"这两个,

    之前一直是想试一下,但是没成功,昨天在排查一个问题的时候发现了,只要把这些都转义了就行了

    grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log

    -

    +

    ]]> Linux - 命令 小技巧 + 命令 grep grep 查日志 @@ -3648,34 +3870,6 @@ maxR[n -转义
    - - leetcode no.3 - /2015/04/15/Leetcode-No-3/ - **Longest Substring Without Repeating Characters **

    - -

    description

    Given a string, find the length of the longest substring without repeating characters.
    For example, the longest substring without repeating letters for “abcabcbb” is “abc”,
    which the length is 3. For “bbbbb” the longest substring is “b”, with the length of 1.

    -

    分析

    源码这次是参考了这个代码,
    tail 表示的当前子串的起始点位置,tail从-1开始就包括的串的长度是1的边界。其实我
    也是猜的(逃

    -
    int ct[256];
    -    memset(ct, -1, sizeof(ct));
    -	int tail = -1;
    -	int max = 0;
    -	for (int i = 0; i < s.size(); i++){
    -		if (ct[s[i]] > tail)
    -			tail = ct[s[i]];
    -		if (i - tail > max)
    -			max = i - tail;
    -		ct[s[i]] = i;
    -	}
    -	return max;
    -]]>
    - - leetcode - - - leetcode - c++ - -
    MFC 模态对话框 /2014/12/24/MFC%20%E6%A8%A1%E6%80%81%E5%AF%B9%E8%AF%9D%E6%A1%86/ @@ -3697,77 +3891,6 @@ maxR[n -mfc - - Leetcode 4 寻找两个正序数组的中位数 ( Median of Two Sorted Arrays *Hard* ) 题解分析 - /2022/03/27/Leetcode-4-%E5%AF%BB%E6%89%BE%E4%B8%A4%E4%B8%AA%E6%AD%A3%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0-Median-of-Two-Sorted-Arrays-Hard-%E9%A2%98%E8%A7%A3%E5%88%86%E6%9E%90/ - 题目介绍

    给定两个大小分别为 mn 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

    -

    算法的时间复杂度应该为 O(log (m+n))

    -

    示例 1:

    -

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    -
    -

    示例 2:

    -

    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    -
    -

    分析与题解

    这个题也是我随机出来的,之前都是随机到 easy 的,而且是序号这么靠前的,然后翻一下,之前应该是用 C++做过的,具体的方法其实可以从他的算法时间复杂度要求看出来,大概率是要二分法这种,后面就结合代码来讲了

    -
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    -        int n1 = nums1.length;
    -        int n2 = nums2.length;
    -        if (n1 > n2) {
    -            return findMedianSortedArrays(nums2, nums1);
    -        }
    -
    -        // 找到两个数组的中点下标
    -        int k = (n1 + n2 + 1 ) / 2;
    -        // 使用一个类似于二分法的查找方法
    -        // 起始值就是 num1 的头跟尾
    -        int left = 0;
    -        int right = n1;
    -        while (left < right) {
    -            // m1 表示我取的是 nums1 的中点,即二分法的方式
    -            int m1 = left + (right - left) / 2;
    -            // *** 这里是重点,因为这个问题也可以转换成找成 n1 + n2 那么多个数中的前 (n1 + n2 + 1) / 2 个
    -            // *** 因为两个数组都是排好序的,那么我从 num1 中取了 m1 个,从 num2 中就是去 k - m1 个
    -            // *** 但是不知道取出来大小是否正好是整体排序的第 (n1 + n2 + 1) / 2 个,所以需要二分法上下对比
    -            int m2 = k - m1;
    -            // 如果 nums1[m1] 小,那我在第一个数组 nums1 的二分查找就要把左端点改成前一次的中点 + 1 (不然就进死循环了
    -            if (nums1[m1] < nums2[m2 - 1]) {
    -                left = m1 + 1;
    -            } else {
    -                right = m1;
    -            }
    -        }
    -
    -        // 因为对比后其实我们只是拿到了一个位置,具体哪个是第 k 个就需要继续判断
    -        int m1 = left;
    -        int m2 = k - left;
    -        // 如果 m1 或者 m2 有小于等于 0 的,那这个值可以先抛弃
    -        // m1 如果等于 0,就是 num1[0] 都比 nums2 中所有值都要大
    -        // m2 等于 0 的话 刚好相反
    -        // 可以这么推断,当其中一个是 0 的时候那么另一个 mx 值肯定是> 0 的,那么就是取的对应的这个下标的值
    -        int c1 = Math.max( m1 <= 0 ? Integer.MIN_VALUE : nums1[m1 - 1] , m2 <= 0 ?  Integer.MIN_VALUE : nums2[m2 - 1]);
    -        // 如果两个数组的元素数量和是奇数,那就直接可以返回了,因为 m1 + m2 就是 k, 如果是一个数组,那这个元素其实就是 nums[k - 1]
    -        // 如果 m1 或者 m2 是 0,那另一个就是 k,取 mx - 1的下标就等于是 k - 1
    -        // 如果都不是 0,那就是取的了 nums1[m1 - 1] 与 nums2[m2 - 1]中的较大者,如果取得是后者,那么也就是 m1 + m2 - 1 的下标就是 k - 1
    -        if ((n1 + n2) % 2 == 1) {
    -            return c1;
    -        }
    -        // 如果是偶数个,那还要取两个数组后面的较小者,然后求平均值
    -        int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1], m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]);
    -        return (c1 + c2) / 2.0;
    -    }
    -

    前面考虑的方法还是比较繁琐,考虑了两个数组的各种交叉情况,后面这个参考了一些网上的解法,代码比较简洁,但是可能不容易一下子就搞明白,所以配合了比较多的注释。

    -]]>
    - - Java - leetcode - - - leetcode - java - 题解 - Median of Two Sorted Arrays - -
    Maven实用小技巧 /2020/02/16/Maven%E5%AE%9E%E7%94%A8%E5%B0%8F%E6%8A%80%E5%B7%A7/ @@ -3967,77 +4090,6 @@ public: 分布式锁 - - 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 - -
    Reverse Bits /2015/03/11/Reverse-Bits/ @@ -4310,64 +4362,12 @@ Run a command 题目介绍

    给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
    PS:注意已排序,还有返回也要已排序

    -

    示例 1:

    -
    -

    输入:head = [1,1,2]
    输出:[1,2]

    -
    -

    示例 2:

    -
    -

    输入:head = [1,1,2,3,3]
    输出:[1,2,3]

    -
    -

    提示:

      -
    • 链表中节点数目在范围 [0, 300]
    • -
    • -100 <= Node.val <= 100
    • -
    • 题目数据保证链表已经按 升序 排列
    • -
    -

    分析与题解

    这题其实是比较正常的 easy 级别的题目,链表已经排好序了,如果还带一个排序就更复杂一点,
    只需要前后项做个对比,如果一致则移除后项,因为可能存在多个重复项,所以只有在前后项不同
    时才会更新被比较项

    -

    code

    public ListNode deleteDuplicates(ListNode head) {
    -    // 链表头是空的或者只有一个头结点,就不用处理了
    -    if (head == null || head.next == null) {
    -        return head;
    -    }
    -    ListNode tail = head;
    -    // 以处理节点还有后续节点作为循环边界条件
    -    while (tail.next != null) {
    -        ListNode temp = tail.next;
    -        // 如果前后相同,那么可以跳过这个节点,将 Tail  ---->   temp  ---> temp.next 
    -        // 更新成  Tail ---->  temp.next
    -        if (temp.val == tail.val) {
    -            tail.next = temp.next;
    -        } else {
    -            // 不相同,则更新 tail
    -            tail = tail.next;
    -        }
    -    }
    -    // 最后返回头结点
    -    return head;
    -}
    -

    链表应该是个需要反复的训练的数据结构,因为涉及到前后指针,然后更新操作,判空等,
    我在这块也是掌握的不太好,需要多练习。

    -]]> - - Java - leetcode - - - leetcode - java - 题解 - Remove Duplicates from Sorted List + 介绍 + + + Docker + namespace + cgroup
    @@ -4723,7 +4723,7 @@ public: mybatis 的 $ 和 # 是有啥区别 /2020/09/06/mybatis-%E7%9A%84-%E5%92%8C-%E6%98%AF%E6%9C%89%E5%95%A5%E5%8C%BA%E5%88%AB/ - 这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    + 这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑,
    对于一条select * from t1 where id = #{id}这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource,
    在这里做了替换,将#{}替换成了?

    前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
     public SqlSource parseScriptNode() {
         MixedSqlNode rootSqlNode = parseDynamicTags(context);
    @@ -4798,7 +4798,7 @@ public class DynamicSqlSource implements SqlSource {
     
     }
     
    -

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    +

    这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}替换成了?占位符,但是 DynamicSqlSource 并没有
    再看这个图,我们发现在这的时候还没有进行替换
    然后往里跟
    好像是这里了

    这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode

    可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个

    理论上还是可以做过滤的,不过好像现在没用起来。
    我们前面可以发现对于#{}是在启动扫描 mapper的 xml 文件就替换成了 ?,然后是在什么时候变成实际的值的呢

    发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套,

    在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯

    ]]>
    Java @@ -5059,6 +5059,51 @@ public class DynamicSqlSource implements SqlSource { 缓存
    + + nginx 日志小记 + /2022/04/17/nginx-%E6%97%A5%E5%BF%97%E5%B0%8F%E8%AE%B0/ + nginx 默认的日志有特定的格式,我们也可以自定义,

    +

    默认的格式是预定义的 combined

    +
    log_format combined '$remote_addr - $remote_user [$time_local] '
    +                    '"$request" $status $body_bytes_sent '
    +                    '"$http_referer" "$http_user_agent"';
    + +

    配置的日志可以使用这个默认的,如果满足需求的话

    +
    Syntax:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    +        access_log off;
    +Default: access_log logs/access.log combined;
    +Context: http, server, location, if in location, limit_except
    + +

    而如果需要额外的一些配置的话可以自己定义 log_format ,比如我想要给日志里加上请求时间,那就可以自己定义一个 log_format 比如添加下

    +
    $request_time
    +request processing time in seconds with a milliseconds resolution;   
    +time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client
    + +
    log_format combined_extend '$remote_addr - $remote_user [$time_local] '
    +                    '"$request" $status $body_bytes_sent '
    +                    '"$http_referer" "$http_user_agent" "$request_time"';
    + +

    然后其他的比如还有 gzip 压缩,可以设置压缩级别,flush 刷盘时间还有根据条件控制

    +

    这里的条件控制简单看了下还比较厉害

    +

    比如我想对2xx 跟 3xx 的访问不记录日志

    +
    map $status $loggable {
    +    ~^[23]  0;
    +    default 1;
    +}
    +
    +access_log /path/to/access.log combined if=$loggable;
    + +

    $loggable 是 0 或者空时表示 if 条件为否,上面的默认就是 1,只有当请求状态 status 是 2xx 或 3xx 时才是 0,代表不用记录,有了这个特性就可以更灵活地配置日志

    +

    文章主要参考了 nginx 的 log 模块的文档

    +]]>
    + + nginx + + + nginx + 日志 + +
    openresty /2019/06/18/openresty/ @@ -5120,8 +5165,8 @@ location ~*nginx - openresty nginx + openresty @@ -6833,51 +6878,6 @@ hz 10
    - -

    配置的日志可以使用这个默认的,如果满足需求的话

    -
    Syntax:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    -        access_log off;
    -Default: access_log logs/access.log combined;
    -Context: http, server, location, if in location, limit_except
    - -

    而如果需要额外的一些配置的话可以自己定义 log_format ,比如我想要给日志里加上请求时间,那就可以自己定义一个 log_format 比如添加下

    -
    $request_time
    -request processing time in seconds with a milliseconds resolution;   
    -time elapsed between the first bytes were read from the client and the log write after the last bytes were sent to the client
    - -
    log_format combined_extend '$remote_addr - $remote_user [$time_local] '
    -                    '"$request" $status $body_bytes_sent '
    -                    '"$http_referer" "$http_user_agent" "$request_time"';
    - -

    然后其他的比如还有 gzip 压缩,可以设置压缩级别,flush 刷盘时间还有根据条件控制

    -

    这里的条件控制简单看了下还比较厉害

    -

    比如我想对2xx 跟 3xx 的访问不记录日志

    -
    map $status $loggable {
    -    ~^[23]  0;
    -    default 1;
    -}
    -
    -access_log /path/to/access.log combined if=$loggable;
    - -

    $loggable 是 0 或者空时表示 if 条件为否,上面的默认就是 1,只有当请求状态 status 是 2xx 或 3xx 时才是 0,代表不用记录,有了这个特性就可以更灵活地配置日志

    -

    文章主要参考了 nginx 的 log 模块的文档

    -]]>
    - - nginx - - - nginx - 日志 - - rust学习笔记-所有权三之切片 /2021/05/16/rust%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E6%89%80%E6%9C%89%E6%9D%83%E4%B8%89%E4%B9%8B%E5%88%87%E7%89%87/ @@ -6945,7 +6945,7 @@ access_log /path/to/access.log combined rust学习笔记-所有权二 /2021/04/18/rust%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E6%89%80%E6%9C%89%E6%9D%83%E4%BA%8C/ - 这里需要说道函数和返回值了
    可以看书上的这个例子

    对于这种情况,当进入函数内部时,会把传入的变量的所有权转移进函数内部,如果最后还是要返回该变量,但是如果此时还要返回别的计算结果,就可能需要笨拙地使用元组

    + 这里需要说道函数和返回值了
    可以看书上的这个例子

    对于这种情况,当进入函数内部时,会把传入的变量的所有权转移进函数内部,如果最后还是要返回该变量,但是如果此时还要返回别的计算结果,就可能需要笨拙地使用元组

    引用

    此时我们就可以用引用来解决这个问题

    fn main() {
         let s1 = String::from("hello");
    @@ -6956,7 +6956,7 @@ access_log /path/to/access.log combined fn calculate_length(s: &String) -> usize {
         s.len()
     }
    -

    这里的&符号就是引用的语义,它们允许你在不获得所有权的前提下使用值

    由于引用不持有值的所有权,所以当引用离开当前作用域时,它指向的值也不会被丢弃

    +

    这里的&符号就是引用的语义,它们允许你在不获得所有权的前提下使用值

    由于引用不持有值的所有权,所以当引用离开当前作用域时,它指向的值也不会被丢弃

    可变引用

    而当我们尝试对引用的字符串进行修改时

    fn main() {
         let s1 = String::from("hello");
    @@ -6965,7 +6965,7 @@ access_log /path/to/access.log combined fn change(s: &String) {
         s.push_str(", world");
     }
    -

    就会有以下报错,

    其实也很容易发现,毕竟没有 mut 指出这是可变引用,同时需要将 s1 改成 mut 可变的

    +

    就会有以下报错,

    其实也很容易发现,毕竟没有 mut 指出这是可变引用,同时需要将 s1 改成 mut 可变的

    fn main() {
         let mut s1 = String::from("hello");
         change(&mut s1);
    @@ -6994,7 +6994,7 @@ access_log /path/to/access.log combined fn change(s: &mut String) {
         s.push_str(", world");
     }
    -


    这里可能就是具体版本在实现上的一个差异,我用的 rustc 是 1.44.0 版本
    其实上面的主要是由 rust 想要避免这类多重可变更导致的异常问题,总结下就是三个点

    +


    这里可能就是具体版本在实现上的一个差异,我用的 rustc 是 1.44.0 版本
    其实上面的主要是由 rust 想要避免这类多重可变更导致的异常问题,总结下就是三个点

    • 两个或两个以上的指针同时同时访问同一空间
    • 其中至少有一个指针会想空间中写入数据
    • @@ -7006,7 +7006,7 @@ access_log /path/to/access.log combined let s = String::from("hello"); &s }
    -这里可以看到其实在 dangle函数返回后,这里的 s 理论上就离开了作用域,但是由于返回了 s 的引用,在 main 函数中就会拿着这个引用,就会出现如下错误

    总结

    最后总结下 +这里可以看到其实在 dangle函数返回后,这里的 s 理论上就离开了作用域,但是由于返回了 s 的引用,在 main 函数中就会拿着这个引用,就会出现如下错误

    总结

    最后总结下
  • 在任何一个段给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
  • 引用总是有效的。
  • @@ -7031,7 +7031,7 @@ access_log /path/to/access.log combined
  • Rust中的每一个值都有一个对应的变量作为它的所有者。
  • 在同一时间内,值有且只有一个所有者。
  • -
  • 当所有者离开自己的作用域时,它持有的值就会被释放掉。

    这里有两个重点:
  • +
  • 当所有者离开自己的作用域时,它持有的值就会被释放掉。

    这里有两个重点:
  • s 在进入作用域后才变得有效
  • 它会保持自己的有效性直到自己离开作用域为止
  • @@ -7043,7 +7043,7 @@ access_log /path/to/access.log combined let s2 = s1; println!("{}, world!", s1);
    -

    有可能认为有两种内存分布可能
    先看下 string 的内存结构

    第一种可能是

    第二种是

    我们来尝试编译下

    发现有这个错误,其实在 rust 中let y = x这个行为的实质是移动,在赋值给 y 之后 x 就无效了

    这样子就不会造成脱离作用域时,对同一块内存区域的二次释放,如果需要复制,可以使用 clone 方法

    +

    有可能认为有两种内存分布可能
    先看下 string 的内存结构

    第一种可能是

    第二种是

    我们来尝试编译下

    发现有这个错误,其实在 rust 中let y = x这个行为的实质是移动,在赋值给 y 之后 x 就无效了

    这样子就不会造成脱离作用域时,对同一块内存区域的二次释放,如果需要复制,可以使用 clone 方法

    let s1 = String::from("hello");
     let s2 = s1.clone();
     
    @@ -7173,7 +7173,7 @@ for _ in data:
         customSpringEventPublisher.publishCustomEvent("hello sprint event");
     }
    -


    就能看到接收到消息了。

    +


    就能看到接收到消息了。

    ]]> Java @@ -7339,10 +7339,32 @@ user3: swoole + + 《垃圾回收算法手册读书》笔记之整理算法 + /2021/03/07/%E3%80%8A%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95%E6%89%8B%E5%86%8C%E8%AF%BB%E4%B9%A6%E3%80%8B%E7%AC%94%E8%AE%B0%E4%B9%8B%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95/ + 最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    +

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    +

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    +

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    +

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    +]]>
    + + Java + gc + jvm + + + java + gc + 标记整理 + 垃圾回收 + jvm + +
    wordpress 忘记密码的一种解决方法 /2021/12/05/wordpress-%E5%BF%98%E8%AE%B0%E5%AF%86%E7%A0%81%E7%9A%84%E4%B8%80%E7%A7%8D%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/ - 前阵子搭了个 WordPress,但是没怎么用,前两天发现忘了登录密码了,最近不知道是什么情况,chrome 的记住密码跟历史记录感觉有点问题,历史记录丢了不少东西,可能是时间太久了,但是理论上应该有 LRU 这种策略的,有些还比较常用,还有记住密码,因为个人域名都是用子域名分配给各个服务,有些记住了,有些又没记住密码,略蛋疼,所以就找了下这个方案。
    当然这个方案不是最优的,有很多限制,首先就是要能够登陆 WordPress 的数据库,不然这个方法是没用的。
    首先不管用什么方式(别违法)先登陆数据库,选择 WordPress 的数据库,可以看到里面有几个表,我们的目标就是 wp_users 表,用 select 查询看下可以看到有用户的数据,如果是像我这样搭着玩的没有创建其他用户的话应该就只有一个用户,那我们的表里的用户数据就只会有一条,当然多条的话可以通过用户名来找

    然后可能我这个版本是这样,没有装额外的插件,密码只是经过了 MD5 的单向哈希,所以我们可以设定一个新密码,然后用 MD5 编码后直接更新进去

    + 前阵子搭了个 WordPress,但是没怎么用,前两天发现忘了登录密码了,最近不知道是什么情况,chrome 的记住密码跟历史记录感觉有点问题,历史记录丢了不少东西,可能是时间太久了,但是理论上应该有 LRU 这种策略的,有些还比较常用,还有记住密码,因为个人域名都是用子域名分配给各个服务,有些记住了,有些又没记住密码,略蛋疼,所以就找了下这个方案。
    当然这个方案不是最优的,有很多限制,首先就是要能够登陆 WordPress 的数据库,不然这个方法是没用的。
    首先不管用什么方式(别违法)先登陆数据库,选择 WordPress 的数据库,可以看到里面有几个表,我们的目标就是 wp_users 表,用 select 查询看下可以看到有用户的数据,如果是像我这样搭着玩的没有创建其他用户的话应该就只有一个用户,那我们的表里的用户数据就只会有一条,当然多条的话可以通过用户名来找

    然后可能我这个版本是这样,没有装额外的插件,密码只是经过了 MD5 的单向哈希,所以我们可以设定一个新密码,然后用 MD5 编码后直接更新进去

    UPDATE wp_users SET user_pass = MD5('123456') WHERE ID = 1;

    然后就能用自己的账户跟刚才更新的密码登录了。

    @@ -7356,25 +7378,16 @@ user3:
    - 《垃圾回收算法手册读书》笔记之整理算法 - /2021/03/07/%E3%80%8A%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95%E6%89%8B%E5%86%8C%E8%AF%BB%E4%B9%A6%E3%80%8B%E7%AC%94%E8%AE%B0%E4%B9%8B%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95/ - 最近看了下这本垃圾回收算法手册,看到了第三章的标记-整理回收算法,做个简单的读书笔记

    -

    双指针整理算法

    对于一块待整理区域,通过两个指针,free 在区域的起始端,scan 指针在区域的末端,free 指针从前往后知道找到空闲区域,scan 从后往前一直找到存活对象,当 free 指针未与 scan 指针交叉时,会给 scan 位置的对象特定位置标记上 free 的地址,即将要转移的地址,不过这里有个限制,这种整理算法一般会用于对象大小统一的情况,否则 free 指针扫描时还需要匹配scan 指针扫描到的存活对象的大小。

    -

    Lisp 2 整理算法

    需要三次完整遍历堆区域
    第一遍是遍历后将计算出所有对象的最终地址(转发地址)
    第二遍是使用转发地址更新赋值器线程根以及被标记对象中的引用,该操作将确保它们指向对象的新位置
    第三次遍历是relocate最终将存活对象移动到其新的目标位置

    -

    引线整理算法

    这个真的长见识了,

    可以看到,原来是 A,B,C 对象引用了 N,这里会在第一次遍历的时候把这种引用反过来,让 N 的对象头部保存下 A 的地址,表示这类引用,然后在遍历到 B 的时候在链起来,到最后就会把所有引用了 N 对象的所有对象通过引线链起来,在第二次遍历的时候就把更新A,B,C 对象引用的 N 地址,并且移动 N 对象

    -

    单次遍历算法

    这个一直提到过位图的实现方式,

    可以看到在第一步会先通过位图标记,标记的方式是位图的每一位对应的堆内存的一个字(这里可能指的是 byte 吧),然后将一个存活对象的内存区域的第一个字跟最后一个字标记,这里如果在通过普通的方式就还需要一个地方在存转发地址,但是因为具体的位置可以通过位图算出来,也就不需要额外记录了

    + 上次的其他 外行聊国足 + /2022/03/06/%E4%B8%8A%E6%AC%A1%E7%9A%84%E5%85%B6%E4%BB%96-%E5%A4%96%E8%A1%8C%E8%81%8A%E5%9B%BD%E8%B6%B3/ + 上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    ]]>
    - Java - gc - jvm + 生活 + 运动 - java - gc - 标记整理 - 垃圾回收 - jvm + 生活
    @@ -7449,7 +7462,7 @@ user3: 介绍下最近比较实用的端口转发 /2021/11/14/%E4%BB%8B%E7%BB%8D%E4%B8%8B%E6%9C%80%E8%BF%91%E6%AF%94%E8%BE%83%E5%AE%9E%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/ - vscode 扩展转发

    在日常使用云服务器的时候,如果要访问上面自建的 mysql,一般要不直接开对应的端口,然后需要对本地 ip 进行授权,但是这个方案会有比较多的限制,比如本地 ip 变了,比如是非固定出口 ip 的家用宽带,或者要在家里跟公司都要访问,如果对所有 ip 都授权的话会不安全,这个时候其实是用 ssh 端口转发是个比较安全方便的方式。
    原来在这个之前其实对这块内容不太了解,后面是听朋友说的,vscode 的 Remote - SSH 扩展可以很方便的使用端口转发,在使用该扩展的时候,会在控制台位置里都出现一个”端口” tab

    如图中所示,我就是将一个服务器上的 mysql 的 3306 端口转发到本地的 3307 端口,至于为什么不用 3306 是因为本地我也有个 mysql 已经使用了 3306 端口,这个方法是使用的 vscode 的这个扩展,

    + vscode 扩展转发

    在日常使用云服务器的时候,如果要访问上面自建的 mysql,一般要不直接开对应的端口,然后需要对本地 ip 进行授权,但是这个方案会有比较多的限制,比如本地 ip 变了,比如是非固定出口 ip 的家用宽带,或者要在家里跟公司都要访问,如果对所有 ip 都授权的话会不安全,这个时候其实是用 ssh 端口转发是个比较安全方便的方式。
    原来在这个之前其实对这块内容不太了解,后面是听朋友说的,vscode 的 Remote - SSH 扩展可以很方便的使用端口转发,在使用该扩展的时候,会在控制台位置里都出现一个”端口” tab

    如图中所示,我就是将一个服务器上的 mysql 的 3306 端口转发到本地的 3307 端口,至于为什么不用 3306 是因为本地我也有个 mysql 已经使用了 3306 端口,这个方法是使用的 vscode 的这个扩展,

    ssh 命令转发

    还有个方式是直接使用 ssh 命令
    命令可以如此

    ssh -CfNg -L 3307:127.0.0.1:3306 user1@199.199.199.199

    简单介绍下这个命令
    -C 表示的是压缩数据包
    -f 表示后台执行命令
    -N 是表示不执行具体命令只用于端口转发
    -g 表示允许远程主机连接本地转发端口
    -L 则是具体端口转发的映射配置
    上面的命令就是将远程主机的 127.0.0.1:3306 对应转发到本地 3307
    而后面的用户则就是登录主机的用户名user1和ip地址199.199.199.199,当然这个配置也不是唯一的

    @@ -7491,23 +7504,10 @@ user3: 美国
    - - 上次的其他 外行聊国足 - /2022/03/06/%E4%B8%8A%E6%AC%A1%E7%9A%84%E5%85%B6%E4%BB%96-%E5%A4%96%E8%A1%8C%E8%81%8A%E5%9B%BD%E8%B6%B3/ - 上次本来想在换车牌后面聊下这个话题,为啥要聊这个话题呢,也很简单,在地铁上看到一对猜测是情侣或者比较关系好的男女同学在聊,因为是这位男同学是大学学的工科,然后自己爱好设计绘画相关的,可能还以此赚了点钱,在地铁上讨论男的要不要好好努力把大学课程完成好,大致的观点是没必要,本来就不适合,这一段我就不说了,恋爱人的嘴,信你个鬼。
    后面男的说在家里又跟他爹吵了关于男足的,估计是那次输了越南,实话说我不是个足球迷,对各方面技术相关也不熟,只是对包括这个人的解释和网上一些观点的看法,纯主观,这次地铁上这位说的大概意思是足球这个训练什么的很难的,要想赢越南也很难的,不是我们能嘴炮的;在网上看到一个赞同数很多的一个回答,说什么中国是个体育弱国,但是由于有一些乒乓球,跳水等小众项目比较厉害,让民众给误解了,首先我先来反驳下这个偷换概念的观点,第一所谓的体育弱国,跟我们觉得足球不应该这么差没半毛钱关系,因为体育弱国,我们的足球本来就不是顶尖的,也并不是去跟顶尖的球队去争,以足球为例,跟巴西,阿根廷,英国,德国,西班牙,意大利,法国这些足球强国,去比较,我相信没有一个足球迷会这么去做对比,因为我们足球历史最高排名是 1998 年的 37 名,最差是 100 名,把能数出来的强队都数完,估计都还不会到 37,所以根本没有跟强队去做对比,第二体育弱国,我们的体育投入是在逐年降低吗,我们是因战乱没法好好训练踢球?还是这帮傻逼就不争气,前面也说了我们足球世界排名最高 37,最低 100,那么前阵子我们输的越南是第几,目前我们的排名 77 名,越南 92 名,看明白了么,轮排名我们都不至于输越南,然后就是这个排名,这也是我想回应那位地铁上的兄弟,我觉得除了造核弹这种高精尖技术,绝大部分包含足球这类运动,遵循类二八原则,比如满分是 100 分,从 80 提到 90 分或者 90 分提到 100 分非常难,30 分提到 40 分,50 分提到 60 分我觉得都是可以凭后天努力达成的,基本不受天赋限制,这里可以以篮球来类比下,相对足球的确篮球没有那么火,或者行业市值没法比,但是也算是相对大众了,中国在篮球方面相对比较好一点,在 08 年奥运会冲进过八强,那也不是唯一的巅峰,但是我说这个其实是想说明两方面的事情,第一,像篮球一样,状态是有起起伏伏,排名也会变动,但是我觉得至少能维持一个相对稳定的总体排名和持平或者上升的趋势,这恰恰是我们这种所谓的“体育弱国”应该走的路线,第二就是去支持我的类二八原则的,可以看到我们的篮球这两年也很垃圾,排名跌到 29 了,那问题我觉得跟足球是一样的,就是不能脚踏实地,如斯科拉说的,中国篮球太缺少竞争,打得好不好都是这些人打,打输了还是照样拿钱,相对足球,篮球的技术我还是懂一些的,对比 08 年的中国男篮,的确像姚明跟王治郅这样的天赋型+努力型球员少了以后竞争力下降在所难免,但是去对比下基本功,传球,投篮,罚球稳定性,也完全不是一个水平的,这些就是我说的,可以通过努力训练拿 80 分的,只要拿到 80 分,甚至只要拿到 60 分,我觉得应该就还算对得起球迷了,就像 NBA 里球队也会有核心球员的更替,战绩起起伏伏,但是基本功这东西,防守积极性,我觉得不随核心球员的变化而变化,就像姚明这样的天赋,其实他应该还有一些先天缺陷,大脚趾较长等,但是他从 CBA 到 NBA,在 NBA 适应并且打成顶尖中锋,离不开刻苦训练,任何的成功都不是纯天赋的,必须要付出足够的努力。
    说回足球,如果像前面那么洗地(体育弱国),那能给我维持住一个稳定的排名我也能接受,问题是我们的经济物质资源比 2000 年前应该有了质的变化,身体素质也越来越好,即使是体育弱国,这么继续走下坡路,半死不活的,不觉得是打了自己的脸么。足球也需要基本功,基本的体能,力量这些,看看现在这些国足运动员的体型,对比下女足,说实话,如果男足这些运动员都练得不错的体脂率,耐力等,成绩即使不好,也不会比现在更差。
    纯主观吐槽,勿喷。

    -]]>
    - - 生活 - 运动 - - - 生活 - -
    从清华美院学姐聊聊我们身边的恶人 /2020/11/29/%E4%BB%8E%E6%B8%85%E5%8D%8E%E7%BE%8E%E9%99%A2%E5%AD%A6%E5%A7%90%E8%81%8A%E8%81%8A%E6%88%91%E4%BB%AC%E8%BA%AB%E8%BE%B9%E7%9A%84%E6%81%B6%E4%BA%BA/ - 前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    + 前几天清华美院学姐的热点火了,然后仔细看了下,其实是个学姐诬陷以为其貌不扬的男同学摸她屁股

    然后还在朋友圈发文想让他社死,我也是挺晚才知道这个词什么意思,然后后面我看到了这个图片,挺有意思的

    本来其实也没什么想聊这个的,是在 B 站看了个吐槽这个的,然后刚好晚上乘公交的时候又碰到了有点类似的问题
    故事描述下,我们从始发站做了公交,这辆公交司机上次碰到过一回,就是会比较关注乘客的佩戴情况,主要考虑到目前国内疫情,然后这次在差不多人都坐满的情况下,可能在提示了三次让车内乘客戴好口罩,但是他指的那个中年女性还是没有反应,司机就转头比较大声指着这个乘客(中年女性)让戴好口罩,然后这个乘客(中年女性)就大声的说“我口罩是滑下来了,你指着我干嘛,你态度这么差,要吃了我一样,我要投诉你”等等,然后可能跟她一块的一个中年女性也是这么帮腔指责司机,比较基本的理解,车子里这么多乘客,假如是处于这位乘客口罩滑下来了而不自知的情况下,司机在提示了三次以后回头指着她说,我想的是没什么问题的,但是这位却反而指责这位司机指着她,并且说是态度差,要吃了她,完全是不可理喻的,并且一直喋喋不休说她口罩滑掉了有什么错,要投诉这个司机,让他可以提前退休了,在其他乘客的劝说下司机准备继续开车时,又口吐芬芳“你个傻,你来打我呀”,真的是让我再次体会到了所谓的恶人先告状的又一完美呈现,后面还有个乘客还是表示要打死司机这个傻,让我有点不明所以,俗话说有人是得理不饶人,前提是得理,这种理亏不饶人真的是挺让人长见识的,试想下,司机在提示三次后,这位乘客还是没有把口罩戴好,如何在不指着这位乘客的情况下能准确的提示到她呢,并且觉得语气态度不好,司机要载着一车的人,因为你这一个乘客不戴好口罩而不能正常出发,有些着急应该很正常吧,可能是平时自己在家里耀武扬威的使唤别人习惯了吧,别人不敢这么大声跟她说话,其实想想这位中年女性应该年纪不会很大,还比较时髦的吧,像一些常见的中年杭州本地人可能是不会说傻*这个词的吧。
    杭州的公交可能是在二月份疫情还比较严重的时候是要求上车出示健康码,后面比较缓和以后只要求佩戴好口罩,但是在我们小绍兴,目前还是一律要求检验健康码和佩戴口罩,对于疫情中,并且目前阶段国内也时有报出小范围的疫情的情况下,司机尽职要求佩戴好口罩其实也是为了乘客着想,另一种情况如果司机不严格要求,万一车上有个感染者,这位中年女性被传染了,如果能找到这个司机的话,是不是想“打死”这个司机,竟然让感染者上了车,反正她自己是不可能有错的,上来就是对方态度差,要投诉,自己不戴好口罩啥都没错,我就想知道如果因为自己没戴好口罩被感染了,是不是也是司机的错,毕竟没有像仆人那样点头哈腰求着她戴好口罩。
    再说回来,整个车就她一个人没戴好口罩,并且还有个细节,其实这个乘客是上了车之后就没戴好了,本来上车的时候是戴好的,这种比较有可能是觉得上车的时候司机那看一眼就好了,如果好好戴着口罩,一点事情都没有,唉,纯粹是太气愤了,调理逻辑什么的就忽略吧

    ]]>
    生活 @@ -7566,42 +7566,22 @@ user3:
    - 周末我在老丈人家打了天小工 - /2020/08/16/%E5%91%A8%E6%9C%AB%E6%88%91%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E6%89%93%E4%BA%86%E5%A4%A9%E5%B0%8F%E5%B7%A5/ - 这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
    这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
    下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
    最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

    -]]>
    - - 生活 - 运动 - 跑步 - 干活 - - - 生活 - 运动 - 减肥 - 跑步 - 干活 - -
    - - 在老丈人家的小工记三 - /2020/09/13/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E4%B8%89/ - 小工记三

    前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
    差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
    下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

    + 分享记录一下一个 git 操作方法 + /2022/02/06/%E5%88%86%E4%BA%AB%E8%AE%B0%E5%BD%95%E4%B8%80%E4%B8%8B%E4%B8%80%E4%B8%AA-git-%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95/ + 前阵子一个同事因为发现某个分支上的代码好像有缺失导致无法正常运行,然后就对比了下把缺失的代码从另一个分支上拷了过来,可能有所欠考虑,不过主要是说下操作过程和最后的处理方法,这位同学的操作是改一些代码commit 一下,这样的 commit 了大概五六次,并且已经 push 到了远端,然后就在想要怎么去处理,在本地可以 reset,已经到远端了,一个很不优雅的操作就是本地 reset 了用 force push,这个当然是不可取的,然后就是 revert 了,但是又已经 commit 了好几次了,网上看了下,好像处理方法还挺成熟的,git revert 命令本质上就是一个逆向的 git cherry-pick 操作。 它将你提交中的变更的以完全相反的方式的应用到一个新创建的提交中,本质上就是撤销或者倒转。可以理解为就是提交一个反向的操作,这里其实我们可以用range revert来进行 git revert, 用法就是

    +
    git revert OLDER_COMMIT^..NEWER_COMMIT
    +

    这样就可以解决上面的问题了,但是还有个问题是这样会根据前面的 commit 数量提交对应数量个 revert commit 会显得比较乱,如果要比较干净的 commit 历史,
    可以看下 git revert 命令说明

    然后就可以用 -n 参数,表示不自动提交

    +
    git revert -n OLDER_COMMIT^..NEWER_COMMIT
    +git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"
    + + ]]>
    - 生活 - 运动 - 跑步 - 干活 + git + 小技巧 - 生活 - 小技巧 - 运动 - 减肥 - 跑步 - 干活 + git
    @@ -7624,28 +7604,9 @@ user3: - 分享记录一下一个 git 操作方法 - /2022/02/06/%E5%88%86%E4%BA%AB%E8%AE%B0%E5%BD%95%E4%B8%80%E4%B8%8B%E4%B8%80%E4%B8%AA-git-%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95/ - 前阵子一个同事因为发现某个分支上的代码好像有缺失导致无法正常运行,然后就对比了下把缺失的代码从另一个分支上拷了过来,可能有所欠考虑,不过主要是说下操作过程和最后的处理方法,这位同学的操作是改一些代码commit 一下,这样的 commit 了大概五六次,并且已经 push 到了远端,然后就在想要怎么去处理,在本地可以 reset,已经到远端了,一个很不优雅的操作就是本地 reset 了用 force push,这个当然是不可取的,然后就是 revert 了,但是又已经 commit 了好几次了,网上看了下,好像处理方法还挺成熟的,git revert 命令本质上就是一个逆向的 git cherry-pick 操作。 它将你提交中的变更的以完全相反的方式的应用到一个新创建的提交中,本质上就是撤销或者倒转。可以理解为就是提交一个反向的操作,这里其实我们可以用range revert来进行 git revert, 用法就是

    -
    git revert OLDER_COMMIT^..NEWER_COMMIT
    -

    这样就可以解决上面的问题了,但是还有个问题是这样会根据前面的 commit 数量提交对应数量个 revert commit 会显得比较乱,如果要比较干净的 commit 历史,
    可以看下 git revert 命令说明

    然后就可以用 -n 参数,表示不自动提交

    -
    git revert -n OLDER_COMMIT^..NEWER_COMMIT
    -git commit -m "revert OLDER_COMMIT to NEWER_COMMIT"
    - - -]]>
    - - git - 小技巧 - - - git - -
    - - 在老丈人家的小工记五 - /2020/10/18/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E4%BA%94/ - 终于回忆起来了,年纪大了写这种东西真的要立马写,不然很容易想不起来,那天应该是 9 月 12 日,也就是上周六,因为我爸也去了,而且娘亲(丈母娘,LD 这么叫,我也就随了她这么叫,当然是背后,当面就叫妈)也在那,早上一到那二爹就给我爸指挥了活,要挖一条院子的出水道,自己想出来的词,因为觉得下水道是竖的,在那稍稍帮了一会会忙,然后我还是比较惯例的跟着 LD 还有娘亲去住的家里,主要是老丈人可能也不太想让我干太累的活,因为上次已经差不多把三楼都整理干净了,然后就是二楼了,二楼说实话我也帮不上什么忙,主要是衣服被子什么的,正好是有张以前小孩子睡过的那种摇篮床,看上去虽然有一些破损,整体还是不错的,所以打算拿过去,我就负责把它拆掉了,比较简单的是只要拧螺丝就行了,但是其实是用了好多好多工具才搞定的,一开始只要螺丝刀就行了,但是因为年代久了,后面的螺帽也有点锈住或者本身就会串着会一起动,所以拿来了个扳手,大部分的其实都被这两个工具给搞定了,但是后期大概还剩下四分之一的时候,有一颗完全锈住,并且螺纹跟之前那些都不一样,但是这个已经是最大的螺丝刀了,也没办法换个大的了,所以又去找来个一字的,因为十字的不是也可以用一字的拧嘛,结果可能是我买的工具箱里的一字螺丝刀太新了,口子那很锋利,直接把螺丝花纹给划掉了,大的小的都划掉,然后真的变成凹进去一个圆柱体了,然后就想能不能隔一层布去拧,然而因为的确是已经变成圆柱体了,布也不太给力,不放弃的我又去找来了个老虎钳,妄图把划掉的螺丝用老虎钳钳住,另一端用扳手拧开螺帽,但是这个螺丝跟螺帽真的是生锈的太严重了,外加上钳不太牢,完全是两边一起转,实在是没办法了,在征得同意之后,直接掰断了,火死了,一颗螺丝折腾得比我拆一张床还久,那天因为早上去的也比较晚了,然后就快吃午饭了,正好想着带一点东西过去,就把一些脸盆,泡脚桶啥的拿过去了,先是去吃了饭,还是在那家快餐店,菜的口味还是依然不错,就是人比较多,我爸旁边都是素菜,都没怎么吃远一点的荤菜,下次要早点去,把荤菜放我爸旁边😄(PS:他们家饭还是依然尴尬,需要等),吃完就开到在修的房子那把东西拿了出来,我爸已经动作很快的打了一小半的地沟了,说实话那玩意真的是很重,我之前把它从三楼拿下来,就觉得这个太重了,这次还要用起来,感觉我的手会分分钟废掉,不过一开始我还是跟着LD去了住的家里,惯例睡了午觉,那天睡得比较踏实,竟然睡了一个小时,醒了想了下,其实LD她们收拾也用不上我(没啥力气活),我还是去帮我爸他们,跟LD说了下就去了在修的老房子那,两位老爹在一起钻地,看着就很累,我连忙上去想换一会他们,因为刚好是钻到混凝土地线,特别难,力道不够就会滑开,用蛮力就是钻进去拔不出来,原理是因为本身浇的时候就是很紧实的,需要边钻边动,那家伙实在是太重了,真的是汗如雨下,基本是三个人轮流来,我是个添乱的,经常卡住,然后把地线,其实就是一条混凝土横梁,里面还有14跟18的钢筋,需要割断,这个割断也是很有技巧,钢筋本身在里面是受到挤压的,直接用切割的,到快断掉的时候就会崩一下,非常危险,还是老丈人比较有经验,要留一点点,然后直接用榔头敲断就好了,本来以为这个是最难的了,结果下面是一块非常大的青基石,而且也是石头跟石头挤一块,边上一点点打钻有点杯水车薪,后来是用那种螺旋的钻,钻四个洞,相对位置大概是个长方形,这样子把中间这个长方形钻出来就比较容易地能拿出来了,后面的也容易搞出来了,后面的其实难度不是特别大了,主要是地沟打好之后得看看高低是不是符合要求的,不能本来是往外排水的反而外面高,这个怎么看就又很有技巧了,一般在地上的只要侧着看一下就好了,考究点就用下水平尺,但是在地下的,不用水平尺,其实可以借助于地沟里正要放进去的水管,放点水进去,看水往哪流就行了,铺好水管后,就剩填埋的活了,不是太麻烦了,那天真的是累到了,打那个混凝土的时候我真的是把我整个人压上去了,不过也挺爽的,有点把平时无处发泄的蛮力发泄出去了。

    + 周末我在老丈人家打了天小工 + /2020/08/16/%E5%91%A8%E6%9C%AB%E6%88%91%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E6%89%93%E4%BA%86%E5%A4%A9%E5%B0%8F%E5%B7%A5/ + 这周回家提前约好了要去老丈人家帮下忙,因为在翻修下老房子,活不是特别整的那种,所以大部分都是自己干,或者找个大工临时干几天(我们这那种比较专业的泥工匠叫做大工),像我这样去帮忙的,就是干点小工(把给大工帮忙的,干些偏体力活的叫做小工)的活。从大学毕业以后真的蛮少帮家里干活了,以前上学的时候放假还是帮家里淘个米,简单的扫地拖地啥的,当然刚高考完的时候,还去我爸厂里帮忙干了几天的活,实在是比较累,不过现在想着是觉得自己那时候比较牛,而不是特别排斥这个活,相对于现在的工作来说,导致了一系列的职业病,颈椎腰背都很僵硬,眼镜也不好,还有反流,像我爸那种活反而是脑力加体力的比较好的结合。
    这一天的活前半部分主要是在清理厨房,瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层,这种活特别是瓷砖上的油污,之前在自己家里也干活,还是比较熟悉的,不过前面主要是LD 在干,我主要是先搞墙上和天花板上的,干活还是很需要技巧的,如果直接去铲,那基本我会变成一个灰人,而且吸一鼻子灰,老丈人比较专业,先接上软管用水冲,一冲效果特别好,有些石灰涂料层直接就冲掉了,冲完之后先用带加长杆的刀片铲铲了一圈墙面,说实话因为老房子之前租出去了,所以墙面什么的被糟蹋的比较难看,一层一层的,不过这还算还好,后面主要是天花板上的,这可难倒我了,从小我爸妈是比较把我当小孩管着,爬上爬下的基本都是我爸搞定,但是到了老丈人家也只得硬着头皮上了,爬到跳(一种建筑工地用的架子)上,还有点晃,小心脏扑通扑通跳,而且带加长杆的铲子还是比较重的,铲一会手也有点累,不过坚持着铲完了,上面还是比较平整的,不过下来的时候又把我难住了🤦‍♂️,往下爬的时候有根杆子要跨过去,由于裤子比较紧,强行一把跨过去怕抽筋,所以以一个非常尴尬的姿势停留休息了一会,再跨了过去,幸好事后问 LD,他们都没看到,哈哈哈,然后就是帮忙一起搞瓷砖上的油污,这个太有经验了,不过老丈人更有意思,一会试试啤酒,一会用用沙子,后面在午饭前基本就弄的比较干净了,就坐着等吃饭了,下午午休了会,就继续干活了。
    下午是我这次体验的重点了,因为要清理以前贴的墙纸,真的是个很麻烦的活,只能说贴墙纸的师傅活干得太好了,基本不可能整个撕下来,想用铲子一点点铲下来也不行,太轻了就只铲掉表面一层,太重了就把墙纸跟墙面的石灰啥的整个铲下来了,而且手又累又酸,后来想着是不是继续用水冲一下,对着一小面墙试验了下,效果还不错,但是又发现了个问题,那一面墙又有一块是后面糊上去的,铲掉外层的石灰后不平,然后就是最最重头的,也是让我后遗症持续到第二天的,要把那一块糊上去的水泥敲下来,毛估下大概是敲了80%左右,剩下的我的手已经不会用力了,因为那一块应该是要糊上去的始作俑者,就一块里面凹进去的,我拿着榔头敲到我手已经没法使劲了,而且大下午,感觉没五分钟,我的汗已经糊满脸,眼睛也睁不开,不然就流到眼睛里了,此处获得成就一:用榔头敲墙壁,也是个技术加体力的活,而且需要非常好的技巧,否则手马上就废了,敲下去的反作用力,没一会就不行了,然后是看着老丈人兄弟帮忙拆一个柜子,在我看来是个几天都搞不定的活,他轻轻松松在我敲墙的那会就搞定了,以前总觉得我干的活非常有技术含量,可是这个事情真的也是很有技巧啊,它是个把一间房间分隔开的柜子,从底到顶上,还带着门,我还在旁边帮忙撬一下脚踢,一根木条撬半天,唉,成就二:专业的人就是不一样。
    最后就是成就三了:我之前沾沾自喜的跑了多少步,做了什么锻炼,其实都是渣渣,像这样干一天活,没经历过的,基本大半天就废了,反过来说,如果能经常去这么干一天活,跑步啥的都是渣渣,消耗的能量远远超过跑个十公里啥的。

    ]]>
    生活 @@ -7655,7 +7616,6 @@ user3: 生活 - 小技巧 运动 减肥 跑步 @@ -7663,9 +7623,9 @@ user3:
    - 在老丈人家的小工记四 - /2020/09/26/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E5%9B%9B/ - 小工记四

    第四周去的时候让我们去了现在在住的房子里,去三楼整理东西了,蛮多的东西需要收拾整理,有些需要丢一下,以前往往是把不太要用的东西就放三楼了,但是后面就不会再去收拾整理,LD 跟丈母娘负责收拾,我不太知道哪些还要的,哪些不要了,而且本来也不擅长这种收拾🤦‍♂️,然后就变成她们收拾出来废纸箱,我负责拆掉,压平,这时候终于觉得体重还算是有点作用,总体来说这个事情我其实也不擅长,不擅长的主要是捆起来,可能我总是小题大做,因为纸箱大小不一,如果不做一下分类,然后把大的折小一些的话,直接绑起来,容易拎起来就散掉了,而且一些鞋盒子这种小件的纸盒会比较薄,冰箱这种大件的比较厚,厚的比较不容易变形,需要大力踩踏,而且扎的时候需要用体重压住捆实了之后那样子才是真的捆实的,不然待会又是松松垮垮容易滑出来散架,因为压住了捆好后,下来了之后箱子就会弹开了把绳子崩紧实,感觉又是掌握到生活小技巧了😃,我这里其实比较单调无聊,然后 LD 那可以说非常厉害了,一共理出来 11 把旧电扇,还有好多没用过全新的不锈钢脸盆大大小小的,感觉比店里在卖的还多,还有是有比较多小时候的东西,特别多小时候的衣服,其实这种对我来说最难了,可能需要读一下断舍离,蛮多东西都舍不得扔,但是其实是没啥用了,然后还占地方,这天应该算是比较轻松的一天了,上午主要是把收拾出来要的和不要的搬下楼,然后下午要去把纸板给卖掉。中午还是去小快餐店吃的,在住的家里理东西还有个好处就是中午吃完饭可以小憩一下,因为我个人是非常依赖午休的,不然下午完全没精神,而且心态也会比较烦躁,一方面是客观的的确比较疲惫,另一方面应该主观心理作用也有点影响,就像上班的时候也是觉得不午睡就会很难受,心理作用也有一点,不过总之能睡还是睡一会,真的没办法就心态好点,吃完午饭之后我们就推着小平板车去收废品的地方卖掉了上午我收拾捆起来的纸板,好像卖了一百多,都是直接过地磅了,不用一捆一捆地称,不过有个小插曲,那里另外一个大爷在倒他的三轮车的时候撞了我一下,还好车速慢,屁股上肉垫后,接下来就比较麻烦了,是LD 她们两姐妹从小到大的书,也要去卖掉,小平板车就载不下了,而且着实也不太好推,轮子不太平,导致推着很累,书有好多箱,本来是想去亲戚家借电动三轮车,因为不会开摩托的那种,摩托的那种 LD 邻居家就有,可是到了那发现那个也是很大,而且刹车是用脚踩的那种,俺爹不太放心,就说第二天周日他有空会帮忙去载了卖掉的,然后比较搞笑的来了,丈母娘看错了时间,以为已经快五点了,就让我们顺便在车里带点东西去在修的房子,放到那边三楼去,到了那还跟老丈人说已经这么迟了要赶紧去菜场买菜了,结果我们回来以后才发现看错了一个小时🤦‍♂️。
    前面可以没提,前三周去的我们一般就周六去一天,然后周日因为要早点回杭州,而且可能想让我们周日能休息下,但是这周就因为周日的时候我爸要去帮忙载书,然后 LD 姐姐也会过来收拾东西,我们周日就又去整理收拾了,周日由于俺爹去的很早,我过去的时候书已经木有了,主要是去收拾东西了,把一些有用没用的继续整理,基本上三楼的就处理完毕了,舒了一大口气,毕竟让丈母娘一个人收拾实在是太累了,但是要扔掉的衣服比较棘手,附近知道的青蛙回收桶被推倒了,其他地方也不知道哪里有,我们就先载了一些东西去在修的房子那,然后去找青蛙桶,结果一个小区可以进,但是已经满了,另一个不让进,后来只能让 LD 姐姐带去她们小区扔了,塞了满满一车。因为要赶回杭州的车就没有等我爸一起回来,他还在那帮忙搞卫生间的墙缝。
    虽然这两天不太热,活也不算很吃力,不过我这个体重和易出汗的体质,还是让短袖不知道湿透了多少次,灌了好多水和冰红茶(下午能提提神),回来周一早上称体重也比较喜人,差一点就达到阶段目标,可以想想去哪里吃之前想好的烤肉跟火锅了(估计吃完立马回到解放前)。

    + 在老丈人家的小工记三 + /2020/09/13/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E4%B8%89/ + 小工记三

    前面这两周周末也都去老丈人家帮忙了,上上周周六先是去了那个在装修的旧房子那,把三楼收拾了下,因为要搬进来住,来不及等二楼装修好,就要把三楼里的东西都整理干净,这个活感觉是比较 easy,原来是就准备把三楼当放东西仓储的地方了,我们乡下大部分三层楼都是这么用的,这次也是没办法,之前搬进来的木头什么的都搬出去,主要是这上面灰尘太多,后面清理鼻孔的时候都是黑色的了,把东西都搬出去以后主要是地还是很脏,就扫了地拖了地,因为是水泥地,灰尘又太多了,拖起来都是会灰尘扬起来,整个脱完了的确干净很多,然而这会就出了个大乌龙,我们清理的是三楼的西边一间,结果老丈人上来说要住东边那间的🤦‍♂️,不过其实西边的也得清理,因为还是要放被子什么的,不算是白费功夫,接着清理东边那间,之前这个房子做过群租房,里面有个高低铺的床,当时觉得可以用在放被子什么的就没扔,只是拆掉了放旁边,我们就把它擦干净了又装好,发现螺丝🔩少了几个,亘古不变的真理,拆了以后装要不就多几个要不就少几个,不是很牢靠,不过用来放放被子省得放地上总还是可以的,对了前面还做了个事情就是铺地毯,其实也不是地毯,就是类似于墙布雨篷布那种,别人不用了送给我们的,三楼水泥地也不会铺瓷砖地板了就放一下,干净好看点,不过大小不合适要裁一下,那把剪刀是真的太难用了,我手都要抽筋了,它就是刀口只有一小个点是能剪下来的,其他都是钝的,后来还是用刀片直接裁,铺好以后,真的感觉也不太一样了,焕然一新的感觉
    差不多中午了就去吃饭了,之前两次是去了一家小饭店,还是还比较干净,但是店里菜不好吃,还死贵,这次去了一家小快餐店,口味好,便宜,味道是真的不错,带鱼跟黄鱼都好吃,一点都不腥,我对这类比较腥的鱼真的是很挑剔的,基本上除了家里做的很少吃外面的,那天抱着试试的态度吃了下,真的还不错,后来丈母娘说好像这家老板是给别人结婚喜事酒席当厨师的,怪不得做的好吃,其实本来是有一点小抗拒,怕不干净什么的,后来发现菜很好吃,而且可能是老丈人跟干活的师傅去吃的比较多,老板很客气,我们吃完饭,还给我们买了葡萄吃,不过这家店有一个槽点,就是饭比较不好吃,有时候会夹生,不过后面聊起来其实是这种小菜馆饭点的通病,烧的太早太多容易多出来浪费,烧的迟了不够吃,而且大的电饭锅比较不容易烧好。
    下午前面还是在处理三楼的,窗户上各种钉子,实在是太多了,我后面在走廊上排了一排🤦‍♂️,有些是直接断了,有些是就撬了出来,感觉我在杭州租房也没有这样子各种钉钉子,挂下衣服什么的也不用这么多吧,比较不能理解,搞得到处都是钉子。那天我爸也去帮忙了,主要是在卫生间里做白缝,其实也是个技术活,印象中好像我小时候自己家里也做过这个事情,但是比较模糊了,后面我们三楼搞完了就去帮我爸了,前面是我老婆二爹在那先刷上白缝,这里叫白缝,有些考究的也叫美缝,就是瓷砖铺完之后的缝,如果不去弄的话,里面水泥的颜色就露出来了,而且容易渗水,所以就要用白水泥加胶水搅拌之后糊在缝上,但是也不是直接糊,先要把缝抠一抠,因为铺瓷砖的还不会仔细到每个缝里的水泥都是一样满,而且也需要一些空间糊上去,不然就太表面的一层很容易被水直接冲掉了,然后这次其实也不是用的白水泥,而是直接现成买来就已经配好的用来填缝的,兑水搅拌均匀就好了,后面就主要是我跟我爸在搞,那个时候真的觉得我实在是太胖了,蹲下去真的没一会就受不了了,膝盖什么的太难受了,后面我跪着刷,然后膝盖又疼,也是比较不容易,不过我爸动作很快,我中间跪累了休息一会,我爸就能搞一大片,后面其实我也没做多少(谦虚一下),总体来讲这次不是很累,就是蹲着跪着腿有点受不了,是应该好好减肥了。

    ]]>
    生活 @@ -7683,61 +7643,64 @@ user3:
    - 寄生虫观后感 - /2020/03/01/%E5%AF%84%E7%94%9F%E8%99%AB%E8%A7%82%E5%90%8E%E6%84%9F/ - 寄生虫这部电影在获得奥斯卡之前就有关注了,豆瓣评分很高,一开始看到这个片名以为是像《铁线虫入侵》那种灾难片,后来看到男主,宋康昊,也是老面孔了,从高中时候在学校操场组织看的《汉江怪物》,有点二的感觉,后来在大学寝室电脑上重新看的时候,室友跟我说是韩国国宝级演员,真人不可貌相,感觉是个呆子的形象。

    -

    但是你说这不是个灾难片,而是个反映社会问题的,就业比较容易往这个方向猜,只是剧情会是怎么样的,一时也没啥头绪,后来不知道哪里看了下一个剧情透露,是一个穷人给富人做家教,然后把自己一家都带进富人家,如果是这样的话可能会把这个怎么带进去作为一个主线,不过事实告诉我,这没那么重要,从第一步朋友的介绍,就显得无比顺利,要去当家教了,作为一个穷成这样的人,瞬间转变成一个衣着得体,言行举止都没让富人家看出破绽的延世大学学生,这真的挺难让人理解,所谓江山易改,本性难移,还有就是这人也正好有那么好能力去辅导,并且诡异的是,多惠也是瞬间就喜欢上了男主,多惠跟将男主介绍给她做家教,也就是多惠原来的家教敏赫,应该也有不少的相处时间,这变了有点大了吧,当然这里也可能因为时长需要,如果说这一点是因为时长,那可能我所有的槽点都是因为这个吧,因为我理解的应该是把家里的人如何一步步地带进富人家,这应该是整个剧情的一个需要更多铺垫去克服这个矛盾点,有时候也想过如果我去当导演,是能拍出个啥,没这个机会,可能有也会是很扯淡的,当然这也不能阻拦我谈谈对这个点的一些看法,毕竟评价一台电冰箱不是说我必须得自己会制冷对吧,这大概是我觉得这个电影的第一个槽点,接下去接二连三的,就是我说的这个最核心的矛盾点,不知道谁说过,这种影视剧应该是源自于生活又高于生活,越是好的作品,越要接近生活,这样子才更能有感同身受。

    -

    接下去的点是金基宇介绍金基婷去给多颂当美术家教,这一步又是我理解的败笔吧,就怎么说呢,没什么铺垫,突然从一个社会底层的穷姑娘,转变成一个气场爆表,把富人家太太唬得一愣一愣的,如果说富太太是比较简单无脑的,那富人自己应该是比较有见识而且是做 IT 的,给自己儿子女儿做家教的,查查底细也很正常吧,但是啥都没有,然后呢,她又开始耍司机的心机了,真的是莫名其妙了,司机真的很惨,窈窕淑女君子好逑,而且这个操作也让我摸不着头脑,这是多腹黑并且有经验才会这么操作,脱内裤真的是让我看得一愣愣的,更看得我一愣一愣的,富人竟然也完全按着这个思路去想了,完全没有别的可能呢,甚至可以去查下行车记录仪或者怎样的,或者有没有毛发体液啥的去检验下,毕竟金基婷也乘坐过这辆车,但是最最让我不懂的还是脱内裤这个操作,究竟是什么样的人才会的呢,值得思考。

    -

    金基泽和忠淑的点也是比较奇怪,首先是金基泽,引起最后那个杀人事件的一个由头,大部分观点都是人为朴社长在之前跟老婆啪啪啪的时候说金基泽的身上有股乘地铁的人的味道,简而言之就是穷人的味道,还有去雯光丈夫身下拿钥匙是对金基泽和雯光丈夫身上的味道的鄙夷,可是这个原因真的站不住脚,即使是同样经济水平,如果身上有比较重的异味,背后讨论下,或者闻到了比较重的味道,有不适的表情和动作很正常吧,像雯光丈夫,在地下室里呆了那么久,身上有异味并且比较重太正常了,就跟在厕所呆久了不会觉得味道大,但是从没味道的地方一进有点味道的厕所就会觉得异样,略尴尬的理由;再说忠淑呢,感觉是太厉害了,能胜任这么一家有钱人的各种挑剔的饮食口味要求的保姆职位,也是让人看懵逼了,看到了不禁想到一个问题,这家人开头是那么地穷,不堪,突然转变成这么地像骗子家族,如果有这么好的骗人能力,应该不会到这种地步吧,如果真的是那么穷,没能力,没志气,又怎么会突然变成这么厉害呢,一家人各司其职,把富人家唬得团团转,而这个前提是,这些人的确能胜任这四个位置,这就是我非常不能理解的点。

    -

    然后说回这个标题,寄生虫,不知道是不是翻译过来不准确,如果真的是叫寄生虫的话,这个寄生虫智商未免也太低了,没有像新冠那样机制,致死率低一点,传染能力强一点,潜伏期也能传染,这个寄生虫第一次受到免疫系统的攻击就自爆了;还有呢,作为一个社会比较低层的打工者,乡下人,对这个审题也是不太审的清,是指这一家人是社会的寄生虫,不思进取,并且死的应该,富人是傻白甜,又有钱又善良,这是给有钱人洗地了还是啥,这个奥斯卡真不知道是怎么得的,总觉得奥斯卡,甚至低一点,豆瓣,得奖的,评分高的都是被一群“精英党”把持的,有黑人主角的,得分高;有同性恋的,得分高;结局惨的,得分高;看不懂的,得分高;就像肖申克的救赎,真不知道是哪里好了,最近看了关于明朝那些事的三杨,杨溥的经历应该比这个厉害吧,可是外国人看不懂,就像外国人不懂中国为什么有反分裂国家法,经历了鸦片战争,八国联军,抗日战争等等,其实跟外国对于黑人的权益的问题,因为有南北战争,所以极度重视这个问题,相应的中国也有自己的历史,请理解。

    -

    简而言之我对寄生虫的评分大概 5~6 分吧。

    + 在老丈人家的小工记五 + /2020/10/18/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E4%BA%94/ + 终于回忆起来了,年纪大了写这种东西真的要立马写,不然很容易想不起来,那天应该是 9 月 12 日,也就是上周六,因为我爸也去了,而且娘亲(丈母娘,LD 这么叫,我也就随了她这么叫,当然是背后,当面就叫妈)也在那,早上一到那二爹就给我爸指挥了活,要挖一条院子的出水道,自己想出来的词,因为觉得下水道是竖的,在那稍稍帮了一会会忙,然后我还是比较惯例的跟着 LD 还有娘亲去住的家里,主要是老丈人可能也不太想让我干太累的活,因为上次已经差不多把三楼都整理干净了,然后就是二楼了,二楼说实话我也帮不上什么忙,主要是衣服被子什么的,正好是有张以前小孩子睡过的那种摇篮床,看上去虽然有一些破损,整体还是不错的,所以打算拿过去,我就负责把它拆掉了,比较简单的是只要拧螺丝就行了,但是其实是用了好多好多工具才搞定的,一开始只要螺丝刀就行了,但是因为年代久了,后面的螺帽也有点锈住或者本身就会串着会一起动,所以拿来了个扳手,大部分的其实都被这两个工具给搞定了,但是后期大概还剩下四分之一的时候,有一颗完全锈住,并且螺纹跟之前那些都不一样,但是这个已经是最大的螺丝刀了,也没办法换个大的了,所以又去找来个一字的,因为十字的不是也可以用一字的拧嘛,结果可能是我买的工具箱里的一字螺丝刀太新了,口子那很锋利,直接把螺丝花纹给划掉了,大的小的都划掉,然后真的变成凹进去一个圆柱体了,然后就想能不能隔一层布去拧,然而因为的确是已经变成圆柱体了,布也不太给力,不放弃的我又去找来了个老虎钳,妄图把划掉的螺丝用老虎钳钳住,另一端用扳手拧开螺帽,但是这个螺丝跟螺帽真的是生锈的太严重了,外加上钳不太牢,完全是两边一起转,实在是没办法了,在征得同意之后,直接掰断了,火死了,一颗螺丝折腾得比我拆一张床还久,那天因为早上去的也比较晚了,然后就快吃午饭了,正好想着带一点东西过去,就把一些脸盆,泡脚桶啥的拿过去了,先是去吃了饭,还是在那家快餐店,菜的口味还是依然不错,就是人比较多,我爸旁边都是素菜,都没怎么吃远一点的荤菜,下次要早点去,把荤菜放我爸旁边😄(PS:他们家饭还是依然尴尬,需要等),吃完就开到在修的房子那把东西拿了出来,我爸已经动作很快的打了一小半的地沟了,说实话那玩意真的是很重,我之前把它从三楼拿下来,就觉得这个太重了,这次还要用起来,感觉我的手会分分钟废掉,不过一开始我还是跟着LD去了住的家里,惯例睡了午觉,那天睡得比较踏实,竟然睡了一个小时,醒了想了下,其实LD她们收拾也用不上我(没啥力气活),我还是去帮我爸他们,跟LD说了下就去了在修的老房子那,两位老爹在一起钻地,看着就很累,我连忙上去想换一会他们,因为刚好是钻到混凝土地线,特别难,力道不够就会滑开,用蛮力就是钻进去拔不出来,原理是因为本身浇的时候就是很紧实的,需要边钻边动,那家伙实在是太重了,真的是汗如雨下,基本是三个人轮流来,我是个添乱的,经常卡住,然后把地线,其实就是一条混凝土横梁,里面还有14跟18的钢筋,需要割断,这个割断也是很有技巧,钢筋本身在里面是受到挤压的,直接用切割的,到快断掉的时候就会崩一下,非常危险,还是老丈人比较有经验,要留一点点,然后直接用榔头敲断就好了,本来以为这个是最难的了,结果下面是一块非常大的青基石,而且也是石头跟石头挤一块,边上一点点打钻有点杯水车薪,后来是用那种螺旋的钻,钻四个洞,相对位置大概是个长方形,这样子把中间这个长方形钻出来就比较容易地能拿出来了,后面的也容易搞出来了,后面的其实难度不是特别大了,主要是地沟打好之后得看看高低是不是符合要求的,不能本来是往外排水的反而外面高,这个怎么看就又很有技巧了,一般在地上的只要侧着看一下就好了,考究点就用下水平尺,但是在地下的,不用水平尺,其实可以借助于地沟里正要放进去的水管,放点水进去,看水往哪流就行了,铺好水管后,就剩填埋的活了,不是太麻烦了,那天真的是累到了,打那个混凝土的时候我真的是把我整个人压上去了,不过也挺爽的,有点把平时无处发泄的蛮力发泄出去了。

    ]]>
    生活 - 影评 - 2020 + 运动 + 跑步 + 干活 生活 - 影评 - 寄生虫 + 小技巧 + 运动 + 减肥 + 跑步 + 干活
    - 我是如何走上跑步这条不归路的 - /2020/07/26/%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E8%B5%B0%E4%B8%8A%E8%B7%91%E6%AD%A5%E8%BF%99%E6%9D%A1%E4%B8%8D%E5%BD%92%E8%B7%AF%E7%9A%84/ - 这周因为没有准备技术方面的内容加之之前有想分享下我和跑步的一些事情,我从小学开始就是个体育渣,因为体重大非常胖,小学的时候要做仰卧起坐,基本是一个都起不来,然后那时候跑步也是要我命那种,跟另外一个比较胖的同学在跑步队尾苟延残喘,只有立定跳远还行。

    -

    时光飞逝,我在初中高中的时候因为爱打篮球,以为自己体质已经有了质的变化,所以在体育课跑步的时候妄图跟一位体育非常好的同学一起跑,结果跟的快断气了,最终还是确认了自己是个体育渣,特别是到了大学的第一次体测跑一千米,跑完直接吐了,一则是大学太宅不运动,二则的确是底子不好。那么怎么会去跑步了呢,其实也没什么特殊的原因,就是工作以后因为运动得更少,体质差,而且越来越胖,所以就想运动下,加之跑步也是我觉得成本最低的运动了,刚好那时候17 年租的地方附近小区周围的路车不太多,一圈刚好一公里多,就觉得开始跑跑看,其实想想以前觉得一千米是非常远的,学校塑胶跑道才 400 米,一千米要两圈半,太难了,但是后来在这个小区周围跑的时候好像跑了一圈以后还能再跑一点,最后跑了两圈,可把自己牛坏了,我都能跑两千米了,哈哈,这是个什么概念呢,大学里最让我绝望的两项体育相关的内容就是一千米和十二分钟跑,一千米把我跑吐了,十二分钟跑及格五圈半也能让我跑完花一周时间恢复以及提前一周心理压力爆炸,虽然我那时候跑的不快,但是已经能跑两千米了,瞬间让自己自信心爆炸,并且跑完步出完汗的感觉是非常棒的,毕竟吃奶茶鸡排都能心安理得了,谁叫我跑步了呢😄,其实现在回去看,那时候跑得还算快的,因为还比较瘦,现在要跑得那么快心跳就太快了,关于心跳什么的后面说,开始建立起自信心之后,对跑步这件事就开始不那么排斥跟害怕了,毕竟我能跑两千米了,然后试试三公里,哇,也可以了呢,三公里是什么概念呢,我大学里跑过最多的一次是十二分钟跑五圈半还是六圈,也就是两公里多,不到三公里,几乎是生涯最长了,一时间产生了一些我可能是个被埋没的运动天才的错觉,其实细想下也能明白,只是速度足够慢了就能跑多一点,毕竟提测一千米是要跑进四分钟才及格,自己跑的时候一千米跑六分多钟已经算不慢了(对我自己来说),但是即使是这样还是对把跑步坚持下去这件事有了很大的正面激励作用,并且由于那时候上下班骑车,整个体重控制的比较理想,导致一时间误会跑步就能非常快的减肥(其实这是我跑步历程中比较大的误区之一),因为会在跑步前后称下体重,如果跑个五公里(后面可以跑五公里了),可能体重就能降 0.5 千克,但实际上这只是这五公里跑步身体流失的水分,喝杯水就回来了,那时候能控制体重主要是骑车跟跑步一起的作用,并且工作压力相对来讲比较小,没有过劳肥。

    -

    后面其实跑步慢慢变得一个比较习惯的运动了,从三公里,到五公里,到七公里,再到十公里,十公里差不多对我来说是个坎,一直还不能比较轻松的跑十公里,可能近一两年好了一些(原谅我只是跟自己比较,跟那些大神比差得不知道多远),其实对我来说每次都是个突破,因为其实与他人比较没有特别大意义,比较顶尖的差得太远,比较普通的也不行,都会打击自信心,比较比我差的就更没意义了,所以其实能挑战自己,能把自己的上限提高才是最有意义的,这也是我看着朋友圈里的一些大神的速度除了佩服赞叹之外没什么其他的惭愧或者说嫌弃自己的感觉(阿 Q 精神😄)。

    -

    一直感性地觉得,跑步能解压,跑完浑身汗,有种把身体的负能量都排出去的感觉,也把吃太多的罪恶感排解掉了🤦‍♂️,之前朋友有看一本书,书名差不多叫越跑越接近自己,这个也是我觉得挺准确的一句话,当跑到接近极限了,还想再继续再跑一点,再跑一点就能突破自己上一次的最远记录了,再跑一点就能又一次突破自己了,成人以后,毕业以后,进入社会以后,世事总是难以件件顺遂,磕磕绊绊的往前走,总觉得要崩溃了,但是还是得坚持,再熬一下,再拼一下,可能还是失败,但人生呢,运气好的人和事总是小概率的,唯有面对挫折,还是日拱一卒,来日方长,我们再坚持下,没准下一次,没准再跑一会,就能突破自己,达到新的境界。

    -

    另外个人后期对跑步的一些知识和理解也变得深入一些,比如伤膝盖,其实跑步的确伤膝盖,需要做一些准备和防护,最好的是适合自己的跑鞋和比较好的路(最好的是塑胶跑道了),也要注意热身跟跑后的拉伸(虽然我做的很差),还有就是注意心率,每个人有自己的适宜心率,我这就不冒充科普达人了,可以自行搜索关键字,先说到这吧~

    + 在老丈人家的小工记四 + /2020/09/26/%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E7%9A%84%E5%B0%8F%E5%B7%A5%E8%AE%B0%E5%9B%9B/ + 小工记四

    第四周去的时候让我们去了现在在住的房子里,去三楼整理东西了,蛮多的东西需要收拾整理,有些需要丢一下,以前往往是把不太要用的东西就放三楼了,但是后面就不会再去收拾整理,LD 跟丈母娘负责收拾,我不太知道哪些还要的,哪些不要了,而且本来也不擅长这种收拾🤦‍♂️,然后就变成她们收拾出来废纸箱,我负责拆掉,压平,这时候终于觉得体重还算是有点作用,总体来说这个事情我其实也不擅长,不擅长的主要是捆起来,可能我总是小题大做,因为纸箱大小不一,如果不做一下分类,然后把大的折小一些的话,直接绑起来,容易拎起来就散掉了,而且一些鞋盒子这种小件的纸盒会比较薄,冰箱这种大件的比较厚,厚的比较不容易变形,需要大力踩踏,而且扎的时候需要用体重压住捆实了之后那样子才是真的捆实的,不然待会又是松松垮垮容易滑出来散架,因为压住了捆好后,下来了之后箱子就会弹开了把绳子崩紧实,感觉又是掌握到生活小技巧了😃,我这里其实比较单调无聊,然后 LD 那可以说非常厉害了,一共理出来 11 把旧电扇,还有好多没用过全新的不锈钢脸盆大大小小的,感觉比店里在卖的还多,还有是有比较多小时候的东西,特别多小时候的衣服,其实这种对我来说最难了,可能需要读一下断舍离,蛮多东西都舍不得扔,但是其实是没啥用了,然后还占地方,这天应该算是比较轻松的一天了,上午主要是把收拾出来要的和不要的搬下楼,然后下午要去把纸板给卖掉。中午还是去小快餐店吃的,在住的家里理东西还有个好处就是中午吃完饭可以小憩一下,因为我个人是非常依赖午休的,不然下午完全没精神,而且心态也会比较烦躁,一方面是客观的的确比较疲惫,另一方面应该主观心理作用也有点影响,就像上班的时候也是觉得不午睡就会很难受,心理作用也有一点,不过总之能睡还是睡一会,真的没办法就心态好点,吃完午饭之后我们就推着小平板车去收废品的地方卖掉了上午我收拾捆起来的纸板,好像卖了一百多,都是直接过地磅了,不用一捆一捆地称,不过有个小插曲,那里另外一个大爷在倒他的三轮车的时候撞了我一下,还好车速慢,屁股上肉垫后,接下来就比较麻烦了,是LD 她们两姐妹从小到大的书,也要去卖掉,小平板车就载不下了,而且着实也不太好推,轮子不太平,导致推着很累,书有好多箱,本来是想去亲戚家借电动三轮车,因为不会开摩托的那种,摩托的那种 LD 邻居家就有,可是到了那发现那个也是很大,而且刹车是用脚踩的那种,俺爹不太放心,就说第二天周日他有空会帮忙去载了卖掉的,然后比较搞笑的来了,丈母娘看错了时间,以为已经快五点了,就让我们顺便在车里带点东西去在修的房子,放到那边三楼去,到了那还跟老丈人说已经这么迟了要赶紧去菜场买菜了,结果我们回来以后才发现看错了一个小时🤦‍♂️。
    前面可以没提,前三周去的我们一般就周六去一天,然后周日因为要早点回杭州,而且可能想让我们周日能休息下,但是这周就因为周日的时候我爸要去帮忙载书,然后 LD 姐姐也会过来收拾东西,我们周日就又去整理收拾了,周日由于俺爹去的很早,我过去的时候书已经木有了,主要是去收拾东西了,把一些有用没用的继续整理,基本上三楼的就处理完毕了,舒了一大口气,毕竟让丈母娘一个人收拾实在是太累了,但是要扔掉的衣服比较棘手,附近知道的青蛙回收桶被推倒了,其他地方也不知道哪里有,我们就先载了一些东西去在修的房子那,然后去找青蛙桶,结果一个小区可以进,但是已经满了,另一个不让进,后来只能让 LD 姐姐带去她们小区扔了,塞了满满一车。因为要赶回杭州的车就没有等我爸一起回来,他还在那帮忙搞卫生间的墙缝。
    虽然这两天不太热,活也不算很吃力,不过我这个体重和易出汗的体质,还是让短袖不知道湿透了多少次,灌了好多水和冰红茶(下午能提提神),回来周一早上称体重也比较喜人,差一点就达到阶段目标,可以想想去哪里吃之前想好的烤肉跟火锅了(估计吃完立马回到解放前)。

    ]]>
    生活 运动 跑步 + 干活 生活 + 小技巧 运动 减肥 跑步 + 干活
    - 看完了扫黑风暴,聊聊感想 - /2021/10/24/%E7%9C%8B%E5%AE%8C%E4%BA%86%E6%89%AB%E9%BB%91%E9%A3%8E%E6%9A%B4-%E8%81%8A%E8%81%8A%E6%84%9F%E6%83%B3/ - 一直在想这篇怎么写,看了这部剧其实对我的一些观念是有影响的,应该是在 9 月份就看完了,到现在可能才会稍微平静一点,一开始是没有想看这部剧,因为同期有一部差不多同名的电影,被投诉了对湖南埋尸案家属伤害很大,我以为就是投诉的这部电视剧,后来同事跟我说不是,所以就想着看一下,但是没有马上看,因为一直不喜欢追这种比较纠结的剧,当时看人民的名义,就是后面等不了了直接看了小说,所以差不多是等到更完了才看的。

    -

    尝试保持一个比较冷静的状态来聊聊,在看的时候有一点感想就是如果要剧里的坏人排个名,因为明眼看都是孙兴是个穷凶极恶的坏人,干尽了坏事,而且可能是演员表演地好,让人真的恨的牙痒痒,但是更多地还是停留在那些剧情中的表现和他的表情,其实对应的真实案例有更多的,这里尽量不展开,有兴趣可以自行搜索关键字,所以其实我想排个名的话,孙兴的母亲应该是我心目中是造成这个结果的比较大占比的始作俑者,因为是方方面面的,包括对林汉的栽赃迫害,最后串起来是因为他看到了孙兴又出来了,就是那句老话,撒了一个谎以后就要用无数个谎来圆,贺芸为了孙兴,作了第一个恶以后就用了一系列的丧心病狂的操作来保护孙兴,而且这之后所做的事情一件比一件可怕,并且如果不是督导组各种想方设法地去破解谜题,这个事情还可以一直被通过各种操作瞒下去,而孙兴还可以继续地为虎作伥,当然其他的包括高明远以及后面的王政,当然是为了这个操作也提供的各种方式的帮助,甚至是主导了这些操作,但是这里贺芸还是在这个位子上能够通过权力做出非常关键的动作,包括栽赃林汉,并且搞掉了李成阳。其中还有一点是我对剧情设计的质疑,也是我前面提到过一点,因为里面孙兴好像是很爱他的母亲贺芸,似乎想表达的是孙兴作的恶是因为得不到母爱,并且个人感觉如果是一个比较敬爱自己母亲的儿子,似乎应该有所畏惧,对他的行为也会有所限制,不应该变成这样一个无恶不作的恶霸,这也是我一直以来的观点,很多人作恶太多可能是因为没有信仰,不管是信基督耶稣还是信道教佛教,总归有一些制约,当然不是说就绝对不会作恶,只是偏向于有所畏惧敬畏,除了某绿哈。

    -

    而对于其他的人感觉演技都不错,只是最后有一些虎头蛇尾吧,不知道是不是审核的原因,也不细说了怕被请喝茶,还有提一点就是麦佳的这个事情,她其实是里面很惨的一个人,把高明远当成最亲近的人,而其实真相令人感觉不寒而栗,杀父杀母的仇人,对于麦佳这个演员,一直觉得印象深刻,后来才想起来就是在爱情公寓里演被关谷救了要以身相遇的那个女孩,长相其实蛮令人印象深刻的,但好像也一直不温不火,不过也不能说演技很好吧,只是在这里演的任务真的是很可怜了,剧情设计里也应该是个很重要的串联人物,最终被高明远献给了大佬,这里扯开一点,好像有的观点说贺芸之前也是这样的,只是一种推测了。

    -

    看完这部剧其实有很多想说的,但是也为了不被请喝茶,尽量少说了,只想说珍爱生命,还是自己小心吧

    + 寄生虫观后感 + /2020/03/01/%E5%AF%84%E7%94%9F%E8%99%AB%E8%A7%82%E5%90%8E%E6%84%9F/ + 寄生虫这部电影在获得奥斯卡之前就有关注了,豆瓣评分很高,一开始看到这个片名以为是像《铁线虫入侵》那种灾难片,后来看到男主,宋康昊,也是老面孔了,从高中时候在学校操场组织看的《汉江怪物》,有点二的感觉,后来在大学寝室电脑上重新看的时候,室友跟我说是韩国国宝级演员,真人不可貌相,感觉是个呆子的形象。

    +

    但是你说这不是个灾难片,而是个反映社会问题的,就业比较容易往这个方向猜,只是剧情会是怎么样的,一时也没啥头绪,后来不知道哪里看了下一个剧情透露,是一个穷人给富人做家教,然后把自己一家都带进富人家,如果是这样的话可能会把这个怎么带进去作为一个主线,不过事实告诉我,这没那么重要,从第一步朋友的介绍,就显得无比顺利,要去当家教了,作为一个穷成这样的人,瞬间转变成一个衣着得体,言行举止都没让富人家看出破绽的延世大学学生,这真的挺难让人理解,所谓江山易改,本性难移,还有就是这人也正好有那么好能力去辅导,并且诡异的是,多惠也是瞬间就喜欢上了男主,多惠跟将男主介绍给她做家教,也就是多惠原来的家教敏赫,应该也有不少的相处时间,这变了有点大了吧,当然这里也可能因为时长需要,如果说这一点是因为时长,那可能我所有的槽点都是因为这个吧,因为我理解的应该是把家里的人如何一步步地带进富人家,这应该是整个剧情的一个需要更多铺垫去克服这个矛盾点,有时候也想过如果我去当导演,是能拍出个啥,没这个机会,可能有也会是很扯淡的,当然这也不能阻拦我谈谈对这个点的一些看法,毕竟评价一台电冰箱不是说我必须得自己会制冷对吧,这大概是我觉得这个电影的第一个槽点,接下去接二连三的,就是我说的这个最核心的矛盾点,不知道谁说过,这种影视剧应该是源自于生活又高于生活,越是好的作品,越要接近生活,这样子才更能有感同身受。

    +

    接下去的点是金基宇介绍金基婷去给多颂当美术家教,这一步又是我理解的败笔吧,就怎么说呢,没什么铺垫,突然从一个社会底层的穷姑娘,转变成一个气场爆表,把富人家太太唬得一愣一愣的,如果说富太太是比较简单无脑的,那富人自己应该是比较有见识而且是做 IT 的,给自己儿子女儿做家教的,查查底细也很正常吧,但是啥都没有,然后呢,她又开始耍司机的心机了,真的是莫名其妙了,司机真的很惨,窈窕淑女君子好逑,而且这个操作也让我摸不着头脑,这是多腹黑并且有经验才会这么操作,脱内裤真的是让我看得一愣愣的,更看得我一愣一愣的,富人竟然也完全按着这个思路去想了,完全没有别的可能呢,甚至可以去查下行车记录仪或者怎样的,或者有没有毛发体液啥的去检验下,毕竟金基婷也乘坐过这辆车,但是最最让我不懂的还是脱内裤这个操作,究竟是什么样的人才会的呢,值得思考。

    +

    金基泽和忠淑的点也是比较奇怪,首先是金基泽,引起最后那个杀人事件的一个由头,大部分观点都是人为朴社长在之前跟老婆啪啪啪的时候说金基泽的身上有股乘地铁的人的味道,简而言之就是穷人的味道,还有去雯光丈夫身下拿钥匙是对金基泽和雯光丈夫身上的味道的鄙夷,可是这个原因真的站不住脚,即使是同样经济水平,如果身上有比较重的异味,背后讨论下,或者闻到了比较重的味道,有不适的表情和动作很正常吧,像雯光丈夫,在地下室里呆了那么久,身上有异味并且比较重太正常了,就跟在厕所呆久了不会觉得味道大,但是从没味道的地方一进有点味道的厕所就会觉得异样,略尴尬的理由;再说忠淑呢,感觉是太厉害了,能胜任这么一家有钱人的各种挑剔的饮食口味要求的保姆职位,也是让人看懵逼了,看到了不禁想到一个问题,这家人开头是那么地穷,不堪,突然转变成这么地像骗子家族,如果有这么好的骗人能力,应该不会到这种地步吧,如果真的是那么穷,没能力,没志气,又怎么会突然变成这么厉害呢,一家人各司其职,把富人家唬得团团转,而这个前提是,这些人的确能胜任这四个位置,这就是我非常不能理解的点。

    +

    然后说回这个标题,寄生虫,不知道是不是翻译过来不准确,如果真的是叫寄生虫的话,这个寄生虫智商未免也太低了,没有像新冠那样机制,致死率低一点,传染能力强一点,潜伏期也能传染,这个寄生虫第一次受到免疫系统的攻击就自爆了;还有呢,作为一个社会比较低层的打工者,乡下人,对这个审题也是不太审的清,是指这一家人是社会的寄生虫,不思进取,并且死的应该,富人是傻白甜,又有钱又善良,这是给有钱人洗地了还是啥,这个奥斯卡真不知道是怎么得的,总觉得奥斯卡,甚至低一点,豆瓣,得奖的,评分高的都是被一群“精英党”把持的,有黑人主角的,得分高;有同性恋的,得分高;结局惨的,得分高;看不懂的,得分高;就像肖申克的救赎,真不知道是哪里好了,最近看了关于明朝那些事的三杨,杨溥的经历应该比这个厉害吧,可是外国人看不懂,就像外国人不懂中国为什么有反分裂国家法,经历了鸦片战争,八国联军,抗日战争等等,其实跟外国对于黑人的权益的问题,因为有南北战争,所以极度重视这个问题,相应的中国也有自己的历史,请理解。

    +

    简而言之我对寄生虫的评分大概 5~6 分吧。

    ]]>
    生活 + 影评 + 2020 生活 影评 + 寄生虫
    @@ -7754,15 +7717,24 @@ user3: - 是何原因竟让两人深夜奔袭十公里 - /2022/06/05/%E6%98%AF%E4%BD%95%E5%8E%9F%E5%9B%A0%E7%AB%9F%E8%AE%A9%E4%B8%A4%E4%BA%BA%E6%B7%B1%E5%A4%9C%E5%A5%94%E8%A2%AD%E5%8D%81%E5%85%AC%E9%87%8C/ - 偶尔来个标题党,不过也是一次比较神奇的经历
    上周五下班后跟 LD 约好去吃牛蛙,某个朋友好像对这类都不太能接受,我以前小时候也不常吃,但是这类其实都是口味比较重,没有那种肉本身的腥味,而且肉质比较特殊,吃过几次以后就有点爱上了,这次刚好是 LD 买的新店开业券,比较优惠(我们俩都是有点勤俭持家的,想着小电驴还有三格电,这家店又有点远,骑车单趟大概要 10 公里左右,有点担心,LD 说应该可以的,就一起骑了过去(跟她轮换着骑电驴和共享单车),结果大概离吃牛蛙的店还有一辆公里的时候,电量就报警了,只有最后一个红色的了,一共是五格,最后一格是红色的,提示我们该充电了,这样子是真的有点慌了,之前开了几个月都是还有一两格电的时候就充电了,没有试验过究竟这最后一格电能开多远,总之先到了再说。
    这家牛蛙没想到还挺热闹的,我们到那已经快八点了,还有十几个排队的,有个人还想插队(向来是不惯着这种,一边去),旁边刚好是有些商店就逛了下,就跟常规的商业中心差不多,开业的比较早也算是这一边比较核心的商业综合体了,各种品牌都有,而且还有彩票售卖点的,只是不太理解现在的彩票都是兑图案的,而且要 10 块钱一张,我的概念里还是以前 2 块钱一张的双色球,偶尔能中个五块十块的。排队还剩四五个的时候我们就去门口坐着等了,又等了大概二十分钟才排到我们,靠近我们等的里面的位置,好像好几个小女生在那还叫了外卖奶茶,然后各种拍照,小朋友的生活还是丰富多彩的,我们到了就点了蒜蓉的,没有点传说中紫苏的,菜单上画了 N 个🌶,LD 还是想体验下说下次人多点可以试试,我们俩吃怕太辣了吃不消,口味还是不错的,这家貌似是 LD 闺蜜推荐的,口碑有保证。两个人光吃一个蛙锅就差不多了,本来还想再点个其他的,后面实在吃不下了就没点,吃完还是惯例点了个奶茶,不过是真的不好找,太大了。
    本来是就回个家的事了,结果就因为前面铺垫的小电驴已经只有一格电了,标题的深夜奔袭十公里就出现了,这个电驴估计续航也虚标挺严重的,电量也是这样,骑的时候显示只有一格电,关掉再开起来又有三格,然后我们回去骑了没一公里就没电了,这下是真的完球了,觉得车子也比较新,直接停外面也不放心,就开始了深夜的十公里推电驴奔袭,LD 看我太累还帮我中间推了一段,虽然是跑过十公里的,但是推着个没电的电驴,还是着实不容易的,LD 也是陪我推着车走,中间好几次说我们把电驴停着打车回去,把电池带回去充满了明天再过来骑车,可能是心态已经转变了,这应该算是一次很特殊的体验,从我们吃完出来大概十点,到最后我们推到小区,大概是过了两个小时的样子,说句深夜也不太过分,把这次这么推车看成了一种意志力的考验,很多事情也都是怕坚持,或者说怕不能坚持,想走得远,没有持续的努力坚持肯定是不行的,所以还是坚持着把车推回来(好吧,我其实主要是怕车被偷,毕竟刚来杭州上学没多久就被偷了自行车留下了阴影),中间感谢 LD,跟我轮着推了一段路,有些下坡的时候还在那坐着用脚蹬一下,离家里大概还有一公里的时候,有个骑电瓶车的大叔还停下来问我们是车破了还是没电了,应该是出于好意吧,最后快到的时候真的非常渴,买了2.5 升的水被我一口气喝了大半瓶,奶茶已经不能起到解渴的作用了,本来以为这样能消耗很多,结果第二天一称还重了,(我的称一定有问题 233

    + 我是如何走上跑步这条不归路的 + /2020/07/26/%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E8%B5%B0%E4%B8%8A%E8%B7%91%E6%AD%A5%E8%BF%99%E6%9D%A1%E4%B8%8D%E5%BD%92%E8%B7%AF%E7%9A%84/ + 这周因为没有准备技术方面的内容加之之前有想分享下我和跑步的一些事情,我从小学开始就是个体育渣,因为体重大非常胖,小学的时候要做仰卧起坐,基本是一个都起不来,然后那时候跑步也是要我命那种,跟另外一个比较胖的同学在跑步队尾苟延残喘,只有立定跳远还行。

    +

    时光飞逝,我在初中高中的时候因为爱打篮球,以为自己体质已经有了质的变化,所以在体育课跑步的时候妄图跟一位体育非常好的同学一起跑,结果跟的快断气了,最终还是确认了自己是个体育渣,特别是到了大学的第一次体测跑一千米,跑完直接吐了,一则是大学太宅不运动,二则的确是底子不好。那么怎么会去跑步了呢,其实也没什么特殊的原因,就是工作以后因为运动得更少,体质差,而且越来越胖,所以就想运动下,加之跑步也是我觉得成本最低的运动了,刚好那时候17 年租的地方附近小区周围的路车不太多,一圈刚好一公里多,就觉得开始跑跑看,其实想想以前觉得一千米是非常远的,学校塑胶跑道才 400 米,一千米要两圈半,太难了,但是后来在这个小区周围跑的时候好像跑了一圈以后还能再跑一点,最后跑了两圈,可把自己牛坏了,我都能跑两千米了,哈哈,这是个什么概念呢,大学里最让我绝望的两项体育相关的内容就是一千米和十二分钟跑,一千米把我跑吐了,十二分钟跑及格五圈半也能让我跑完花一周时间恢复以及提前一周心理压力爆炸,虽然我那时候跑的不快,但是已经能跑两千米了,瞬间让自己自信心爆炸,并且跑完步出完汗的感觉是非常棒的,毕竟吃奶茶鸡排都能心安理得了,谁叫我跑步了呢😄,其实现在回去看,那时候跑得还算快的,因为还比较瘦,现在要跑得那么快心跳就太快了,关于心跳什么的后面说,开始建立起自信心之后,对跑步这件事就开始不那么排斥跟害怕了,毕竟我能跑两千米了,然后试试三公里,哇,也可以了呢,三公里是什么概念呢,我大学里跑过最多的一次是十二分钟跑五圈半还是六圈,也就是两公里多,不到三公里,几乎是生涯最长了,一时间产生了一些我可能是个被埋没的运动天才的错觉,其实细想下也能明白,只是速度足够慢了就能跑多一点,毕竟提测一千米是要跑进四分钟才及格,自己跑的时候一千米跑六分多钟已经算不慢了(对我自己来说),但是即使是这样还是对把跑步坚持下去这件事有了很大的正面激励作用,并且由于那时候上下班骑车,整个体重控制的比较理想,导致一时间误会跑步就能非常快的减肥(其实这是我跑步历程中比较大的误区之一),因为会在跑步前后称下体重,如果跑个五公里(后面可以跑五公里了),可能体重就能降 0.5 千克,但实际上这只是这五公里跑步身体流失的水分,喝杯水就回来了,那时候能控制体重主要是骑车跟跑步一起的作用,并且工作压力相对来讲比较小,没有过劳肥。

    +

    后面其实跑步慢慢变得一个比较习惯的运动了,从三公里,到五公里,到七公里,再到十公里,十公里差不多对我来说是个坎,一直还不能比较轻松的跑十公里,可能近一两年好了一些(原谅我只是跟自己比较,跟那些大神比差得不知道多远),其实对我来说每次都是个突破,因为其实与他人比较没有特别大意义,比较顶尖的差得太远,比较普通的也不行,都会打击自信心,比较比我差的就更没意义了,所以其实能挑战自己,能把自己的上限提高才是最有意义的,这也是我看着朋友圈里的一些大神的速度除了佩服赞叹之外没什么其他的惭愧或者说嫌弃自己的感觉(阿 Q 精神😄)。

    +

    一直感性地觉得,跑步能解压,跑完浑身汗,有种把身体的负能量都排出去的感觉,也把吃太多的罪恶感排解掉了🤦‍♂️,之前朋友有看一本书,书名差不多叫越跑越接近自己,这个也是我觉得挺准确的一句话,当跑到接近极限了,还想再继续再跑一点,再跑一点就能突破自己上一次的最远记录了,再跑一点就能又一次突破自己了,成人以后,毕业以后,进入社会以后,世事总是难以件件顺遂,磕磕绊绊的往前走,总觉得要崩溃了,但是还是得坚持,再熬一下,再拼一下,可能还是失败,但人生呢,运气好的人和事总是小概率的,唯有面对挫折,还是日拱一卒,来日方长,我们再坚持下,没准下一次,没准再跑一会,就能突破自己,达到新的境界。

    +

    另外个人后期对跑步的一些知识和理解也变得深入一些,比如伤膝盖,其实跑步的确伤膝盖,需要做一些准备和防护,最好的是适合自己的跑鞋和比较好的路(最好的是塑胶跑道了),也要注意热身跟跑后的拉伸(虽然我做的很差),还有就是注意心率,每个人有自己的适宜心率,我这就不冒充科普达人了,可以自行搜索关键字,先说到这吧~

    ]]>
    生活 + 运动 + 跑步 生活 + 运动 + 减肥 + 跑步
    @@ -7853,6 +7825,48 @@ user3: utf8mb4_general_ci + + 是何原因竟让两人深夜奔袭十公里 + /2022/06/05/%E6%98%AF%E4%BD%95%E5%8E%9F%E5%9B%A0%E7%AB%9F%E8%AE%A9%E4%B8%A4%E4%BA%BA%E6%B7%B1%E5%A4%9C%E5%A5%94%E8%A2%AD%E5%8D%81%E5%85%AC%E9%87%8C/ + 偶尔来个标题党,不过也是一次比较神奇的经历
    上周五下班后跟 LD 约好去吃牛蛙,某个朋友好像对这类都不太能接受,我以前小时候也不常吃,但是这类其实都是口味比较重,没有那种肉本身的腥味,而且肉质比较特殊,吃过几次以后就有点爱上了,这次刚好是 LD 买的新店开业券,比较优惠(我们俩都是有点勤俭持家的,想着小电驴还有三格电,这家店又有点远,骑车单趟大概要 10 公里左右,有点担心,LD 说应该可以的,就一起骑了过去(跟她轮换着骑电驴和共享单车),结果大概离吃牛蛙的店还有一辆公里的时候,电量就报警了,只有最后一个红色的了,一共是五格,最后一格是红色的,提示我们该充电了,这样子是真的有点慌了,之前开了几个月都是还有一两格电的时候就充电了,没有试验过究竟这最后一格电能开多远,总之先到了再说。
    这家牛蛙没想到还挺热闹的,我们到那已经快八点了,还有十几个排队的,有个人还想插队(向来是不惯着这种,一边去),旁边刚好是有些商店就逛了下,就跟常规的商业中心差不多,开业的比较早也算是这一边比较核心的商业综合体了,各种品牌都有,而且还有彩票售卖点的,只是不太理解现在的彩票都是兑图案的,而且要 10 块钱一张,我的概念里还是以前 2 块钱一张的双色球,偶尔能中个五块十块的。排队还剩四五个的时候我们就去门口坐着等了,又等了大概二十分钟才排到我们,靠近我们等的里面的位置,好像好几个小女生在那还叫了外卖奶茶,然后各种拍照,小朋友的生活还是丰富多彩的,我们到了就点了蒜蓉的,没有点传说中紫苏的,菜单上画了 N 个🌶,LD 还是想体验下说下次人多点可以试试,我们俩吃怕太辣了吃不消,口味还是不错的,这家貌似是 LD 闺蜜推荐的,口碑有保证。两个人光吃一个蛙锅就差不多了,本来还想再点个其他的,后面实在吃不下了就没点,吃完还是惯例点了个奶茶,不过是真的不好找,太大了。
    本来是就回个家的事了,结果就因为前面铺垫的小电驴已经只有一格电了,标题的深夜奔袭十公里就出现了,这个电驴估计续航也虚标挺严重的,电量也是这样,骑的时候显示只有一格电,关掉再开起来又有三格,然后我们回去骑了没一公里就没电了,这下是真的完球了,觉得车子也比较新,直接停外面也不放心,就开始了深夜的十公里推电驴奔袭,LD 看我太累还帮我中间推了一段,虽然是跑过十公里的,但是推着个没电的电驴,还是着实不容易的,LD 也是陪我推着车走,中间好几次说我们把电驴停着打车回去,把电池带回去充满了明天再过来骑车,可能是心态已经转变了,这应该算是一次很特殊的体验,从我们吃完出来大概十点,到最后我们推到小区,大概是过了两个小时的样子,说句深夜也不太过分,把这次这么推车看成了一种意志力的考验,很多事情也都是怕坚持,或者说怕不能坚持,想走得远,没有持续的努力坚持肯定是不行的,所以还是坚持着把车推回来(好吧,我其实主要是怕车被偷,毕竟刚来杭州上学没多久就被偷了自行车留下了阴影),中间感谢 LD,跟我轮着推了一段路,有些下坡的时候还在那坐着用脚蹬一下,离家里大概还有一公里的时候,有个骑电瓶车的大叔还停下来问我们是车破了还是没电了,应该是出于好意吧,最后快到的时候真的非常渴,买了2.5 升的水被我一口气喝了大半瓶,奶茶已经不能起到解渴的作用了,本来以为这样能消耗很多,结果第二天一称还重了,(我的称一定有问题 233

    +]]>
    + + 生活 + + + 生活 + +
    + + 看完了扫黑风暴,聊聊感想 + /2021/10/24/%E7%9C%8B%E5%AE%8C%E4%BA%86%E6%89%AB%E9%BB%91%E9%A3%8E%E6%9A%B4-%E8%81%8A%E8%81%8A%E6%84%9F%E6%83%B3/ + 一直在想这篇怎么写,看了这部剧其实对我的一些观念是有影响的,应该是在 9 月份就看完了,到现在可能才会稍微平静一点,一开始是没有想看这部剧,因为同期有一部差不多同名的电影,被投诉了对湖南埋尸案家属伤害很大,我以为就是投诉的这部电视剧,后来同事跟我说不是,所以就想着看一下,但是没有马上看,因为一直不喜欢追这种比较纠结的剧,当时看人民的名义,就是后面等不了了直接看了小说,所以差不多是等到更完了才看的。

    +

    尝试保持一个比较冷静的状态来聊聊,在看的时候有一点感想就是如果要剧里的坏人排个名,因为明眼看都是孙兴是个穷凶极恶的坏人,干尽了坏事,而且可能是演员表演地好,让人真的恨的牙痒痒,但是更多地还是停留在那些剧情中的表现和他的表情,其实对应的真实案例有更多的,这里尽量不展开,有兴趣可以自行搜索关键字,所以其实我想排个名的话,孙兴的母亲应该是我心目中是造成这个结果的比较大占比的始作俑者,因为是方方面面的,包括对林汉的栽赃迫害,最后串起来是因为他看到了孙兴又出来了,就是那句老话,撒了一个谎以后就要用无数个谎来圆,贺芸为了孙兴,作了第一个恶以后就用了一系列的丧心病狂的操作来保护孙兴,而且这之后所做的事情一件比一件可怕,并且如果不是督导组各种想方设法地去破解谜题,这个事情还可以一直被通过各种操作瞒下去,而孙兴还可以继续地为虎作伥,当然其他的包括高明远以及后面的王政,当然是为了这个操作也提供的各种方式的帮助,甚至是主导了这些操作,但是这里贺芸还是在这个位子上能够通过权力做出非常关键的动作,包括栽赃林汉,并且搞掉了李成阳。其中还有一点是我对剧情设计的质疑,也是我前面提到过一点,因为里面孙兴好像是很爱他的母亲贺芸,似乎想表达的是孙兴作的恶是因为得不到母爱,并且个人感觉如果是一个比较敬爱自己母亲的儿子,似乎应该有所畏惧,对他的行为也会有所限制,不应该变成这样一个无恶不作的恶霸,这也是我一直以来的观点,很多人作恶太多可能是因为没有信仰,不管是信基督耶稣还是信道教佛教,总归有一些制约,当然不是说就绝对不会作恶,只是偏向于有所畏惧敬畏,除了某绿哈。

    +

    而对于其他的人感觉演技都不错,只是最后有一些虎头蛇尾吧,不知道是不是审核的原因,也不细说了怕被请喝茶,还有提一点就是麦佳的这个事情,她其实是里面很惨的一个人,把高明远当成最亲近的人,而其实真相令人感觉不寒而栗,杀父杀母的仇人,对于麦佳这个演员,一直觉得印象深刻,后来才想起来就是在爱情公寓里演被关谷救了要以身相遇的那个女孩,长相其实蛮令人印象深刻的,但好像也一直不温不火,不过也不能说演技很好吧,只是在这里演的任务真的是很可怜了,剧情设计里也应该是个很重要的串联人物,最终被高明远献给了大佬,这里扯开一点,好像有的观点说贺芸之前也是这样的,只是一种推测了。

    +

    看完这部剧其实有很多想说的,但是也为了不被请喝茶,尽量少说了,只想说珍爱生命,还是自己小心吧

    +]]>
    + + 生活 + + + 生活 + 影评 + +
    + + 给小电驴上牌 + /2022/03/20/%E7%BB%99%E5%B0%8F%E7%94%B5%E9%A9%B4%E4%B8%8A%E7%89%8C/ + 三八节活动的时候下决心买了个小电驴,主要是上下班路上现在通勤条件越来越恶劣了,之前都是觉得坐公交就行了,实际路程就比较短,但是现在或者说大概是年前那两个月差不多就开始了,基本是堵一路,个人感觉是天目山路那边在修地铁,而且蚂蚁的几个空间都在那,上班的时间点都差不多,前一个修地铁感觉挺久了,机动车保有量也越来越多,总体是古墩路就越来越堵,还有个原因就是早上上班的点共享单车都被骑走了,有时候整整走一路都没一辆,有时候孤零零地有一辆基本都是破的;走路其实也是一种选择,但是因为要赶着上班,走得太慢就要很久,可能要 45 分钟这样,走得比较快就一身汗挺难受的。所以考虑自行车和电动车,这里还有一点就是不管是乘公交还是骑共享单车,其实都要从楼下走出去蛮远,公司回来也是,也就是这种通勤方式在准备阶段就花了比较多时间,比如总的从下班到到家的时间是半小时,可能在骑共享单车和公交车上的时间都不到十分钟,就比较难受。觉得这种比例太浪费时间,如果能有这种比较点对点的方式,估计能省时省力不少,前面说的骑共享单车的方式其实在之前是比较可行的,但是后来越来越少车,基本都是每周的前几天,周一到周三都是没有车,走路到公司再冷的天都是走出一身的汗,下雨天就更难受,本来下雨天应该是优先选择坐公交,但是一般下雨天堵车会更严重,而且车子到我上车的那个站,下雨天就挤得不行,总体说下来感觉事情都不打,但是几年下来,还是会挺不爽的。

    +

    电驴看的比较草率,主要是考虑续航,然后锂电池外加 48v 和 24AH,这样一般来讲还是价格比较高的,只是原来没预料到这个限速,以为现在的车子都比较快,但是现在的新国标车子都是 25km/h 的限速,然后 15km/h 都是会要提醒,虽然说有一些特殊的解除限速的方法,但是解了也就 35km/h ,差距不是特别大,而且现在的车子都是比较小,也不太能载东西,特别是上下班路程也不远的情况下,其实不是那么需要速度,就像我朋友说的,可能骑车的时间还不如等红绿灯多,所以就还好,也不打算解除限速,只是品牌上也仔细看,后来选了绿源,目前大部分还是雅迪,爱玛,台羚,绿源,小牛等,路上看的话还是雅迪比较多,不过价格也比较贵一点,还有就是小牛了,是比较新兴的品牌,手机 App 什么的做得比较好,而且也比较贵,最后以相对比较便宜的价格买了个锂电 48V24AH 的小车子,后来发现还是有点不方便的点就是没有比较大的筐,也不好装,这样就是下雨天雨衣什么的比较不方便放。

    +

    聊回来主题上牌这个事情,这个事情也是颇费心力,提车的时候店里的让我跟他早上一起去,但是因为不确定时间,也比较远就没跟着去,因为我是线上买的,线下自提,线下的店可能没啥利润可以拿,就不肯帮忙代上牌,朋友说在线下店里买是可以代上的,自己上牌过程也比较曲折,一开始是头盔没到,然后是等开发票,主要的东西就是需要骑着车子去车管所,不能只自己去,然后需要预约,附近比较近的都是提前一周就预约完了号了,要提前在支付宝上进行预约,比较空的就是店里推荐的景区大队,但是随之而来就是比较蛋疼的,这个景区大队太远了,看下骑车距离有十几公里,所以就有点拖延症,但是总归要上的,不然一直不能开是白买了,上牌的材料主要是车辆合格证,发票,然后车子上的浙品码,在车架上和电池上,然后车架号什么的都要跟合格证上完全对应,整体车子要跟合格证上一毛一样,如果有额外的反光镜,后面副座都需要拆掉,脚踏板要装上,到了那其实还比较顺利,就是十几公里外加那天比较冷,吹得头疼。

    +]]>
    + + 生活 + + + 生活 + +
    聊一下 RocketMQ 的 DefaultMQPushConsumer 源码 /2020/06/26/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84-Consumer/ @@ -8646,20 +8660,6 @@ user3: 源码解析 - - 给小电驴上牌 - /2022/03/20/%E7%BB%99%E5%B0%8F%E7%94%B5%E9%A9%B4%E4%B8%8A%E7%89%8C/ - 三八节活动的时候下决心买了个小电驴,主要是上下班路上现在通勤条件越来越恶劣了,之前都是觉得坐公交就行了,实际路程就比较短,但是现在或者说大概是年前那两个月差不多就开始了,基本是堵一路,个人感觉是天目山路那边在修地铁,而且蚂蚁的几个空间都在那,上班的时间点都差不多,前一个修地铁感觉挺久了,机动车保有量也越来越多,总体是古墩路就越来越堵,还有个原因就是早上上班的点共享单车都被骑走了,有时候整整走一路都没一辆,有时候孤零零地有一辆基本都是破的;走路其实也是一种选择,但是因为要赶着上班,走得太慢就要很久,可能要 45 分钟这样,走得比较快就一身汗挺难受的。所以考虑自行车和电动车,这里还有一点就是不管是乘公交还是骑共享单车,其实都要从楼下走出去蛮远,公司回来也是,也就是这种通勤方式在准备阶段就花了比较多时间,比如总的从下班到到家的时间是半小时,可能在骑共享单车和公交车上的时间都不到十分钟,就比较难受。觉得这种比例太浪费时间,如果能有这种比较点对点的方式,估计能省时省力不少,前面说的骑共享单车的方式其实在之前是比较可行的,但是后来越来越少车,基本都是每周的前几天,周一到周三都是没有车,走路到公司再冷的天都是走出一身的汗,下雨天就更难受,本来下雨天应该是优先选择坐公交,但是一般下雨天堵车会更严重,而且车子到我上车的那个站,下雨天就挤得不行,总体说下来感觉事情都不打,但是几年下来,还是会挺不爽的。

    -

    电驴看的比较草率,主要是考虑续航,然后锂电池外加 48v 和 24AH,这样一般来讲还是价格比较高的,只是原来没预料到这个限速,以为现在的车子都比较快,但是现在的新国标车子都是 25km/h 的限速,然后 15km/h 都是会要提醒,虽然说有一些特殊的解除限速的方法,但是解了也就 35km/h ,差距不是特别大,而且现在的车子都是比较小,也不太能载东西,特别是上下班路程也不远的情况下,其实不是那么需要速度,就像我朋友说的,可能骑车的时间还不如等红绿灯多,所以就还好,也不打算解除限速,只是品牌上也仔细看,后来选了绿源,目前大部分还是雅迪,爱玛,台羚,绿源,小牛等,路上看的话还是雅迪比较多,不过价格也比较贵一点,还有就是小牛了,是比较新兴的品牌,手机 App 什么的做得比较好,而且也比较贵,最后以相对比较便宜的价格买了个锂电 48V24AH 的小车子,后来发现还是有点不方便的点就是没有比较大的筐,也不好装,这样就是下雨天雨衣什么的比较不方便放。

    -

    聊回来主题上牌这个事情,这个事情也是颇费心力,提车的时候店里的让我跟他早上一起去,但是因为不确定时间,也比较远就没跟着去,因为我是线上买的,线下自提,线下的店可能没啥利润可以拿,就不肯帮忙代上牌,朋友说在线下店里买是可以代上的,自己上牌过程也比较曲折,一开始是头盔没到,然后是等开发票,主要的东西就是需要骑着车子去车管所,不能只自己去,然后需要预约,附近比较近的都是提前一周就预约完了号了,要提前在支付宝上进行预约,比较空的就是店里推荐的景区大队,但是随之而来就是比较蛋疼的,这个景区大队太远了,看下骑车距离有十几公里,所以就有点拖延症,但是总归要上的,不然一直不能开是白买了,上牌的材料主要是车辆合格证,发票,然后车子上的浙品码,在车架上和电池上,然后车架号什么的都要跟合格证上完全对应,整体车子要跟合格证上一毛一样,如果有额外的反光镜,后面副座都需要拆掉,脚踏板要装上,到了那其实还比较顺利,就是十几公里外加那天比较冷,吹得头疼。

    -]]>
    - - 生活 - - - 生活 - -
    聊一下 RocketMQ 的 NameServer 源码 /2020/07/05/%E8%81%8A%E4%B8%80%E4%B8%8B-RocketMQ-%E7%9A%84-NameServer-%E6%BA%90%E7%A0%81/ @@ -9296,7 +9296,7 @@ user3: -

    vms95Z

    +

    vms95Z

    如上面的图显示的,要在用户态跟内核态进行切换,数据还需要在内核缓冲跟用户缓冲之间拷贝多次,

      @@ -9314,7 +9314,7 @@ user3:
      tmp_buf = mmap(file, len);
       write(socket, tmp_buf, len);
      -

      I68mFx

      +

      I68mFx

      第一步:mmap系统调用使得文件内容被DMA引擎复制到内核缓冲区。然后该缓冲区与用户进程共享,在内核和用户内存空间之间不进行任何拷贝。

      第二步:写系统调用使得内核将数据从原来的内核缓冲区复制到与套接字相关的内核缓冲区。

      @@ -9354,7 +9354,7 @@ user3: public static final int CQ_STORE_UNIT_SIZE = 20;

    所以文件大小是5.7M 左右

    -

    5udpag

    +

    5udpag

    ConsumeQueue 的构建是通过org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService运行后的 doReput 方法,而启动是的 reputFromOffset 则是通过org.apache.rocketmq.store.DefaultMessageStore#start中下面代码设置并启动

    log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
                     maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(), this.commitLog.getConfirmOffset());
    @@ -9478,7 +9478,7 @@ user3:
         this.byteBufferIndex.putLong(tagsCode);

    这里也可以看到 ConsumeQueue 的存储格式,

    -

    AA6Tve

    +

    AA6Tve

    偏移量,消息大小,跟 tag 的 hashCode

    ]]> @@ -9499,7 +9499,7 @@ user3:
    // CommitLog file size,default is 1G
     private int mapedFileSizeCommitLog = 1024 * 1024 * 1024;
    -

    kLahwW

    +

    kLahwW

    本地跑个 demo 验证下,也是这样,这里奇妙有几个比较巧妙的点(个人观点),首先文件就刚好是 1G,并且按照大小偏移量去生成下一个文件,这样获取消息的时候按大小算一下就知道在哪个文件里了,

    代码中写入 CommitLog 的逻辑可以从这开始看

    public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
    @@ -10015,7 +10015,7 @@ user3:
             return false;
         }
    -

    具体可以看一下这个简略的示意图

    +

    具体可以看一下这个简略的示意图

    ]]> MQ @@ -10368,7 +10368,7 @@ user3: }

    第一个公有方法 createStudent 首先调用了 manager 层的创建方法,然后再通过引入的 studentService 调用了createStudent2,我们先跑一下看看会出现啥情况,果不其然报错了,正是这个报错让我纠结了很久

    -

    EdR7oB

    +

    EdR7oB

    报了个空指针,而且是在 createStudent2 已经被调用到了,在它的内部,报的 studentManager 是 null,首先 cglib 作为动态代理它是通过继承的方式来实现的,相当于是会在调用目标对象的代理方法时调用 cglib 生成的子类,具体的代理切面逻辑在子类实现,然后在调用目标对象的目标方法,但是继承的方式对于 final 和私有方法其实是没法进行代理的,因为没法继承,所以我最开始的想法是应该通过 studentService 调用 createStudent2 的时候就报错了,也就是不会进入这个方法内部,后面才发现犯了个特别二的错误,继承的方式去调用父类的私有方法,对于 Java 来说是可以调用到的,父类的私有方法并不由子类的InstanceKlass维护,只能通过子类的InstanceKlass找到Java类对应的_super,这样间接地访问。也就是说子类其实是可以访问的,那为啥访问了会报空指针呢,这里报的是studentManager 是空的,可以往依赖注入方面去想,如果忽略依赖注入,我这个studentManager 的确是 null,那是不是就没有被依赖注入呢,但是为啥前面那个可以呢

    这个问题着实查了很久,不废话来看代码

    @Override
    @@ -10473,7 +10473,7 @@ user3:
     }

    而如何使用这个 lookupKey 呢,就涉及到我们的 DataSource 配置了,原来就是我们可以直接通过spring 的 jdbc 配置数据源,像这样

    -

    +

    现在我们要使用 Druid 作为数据源了,然后配置 DynamicDataSource 的参数,通过 key 来选择对应的 DataSource,也就是下面配的 master1 和 master2

    <bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
               destroy-method="close"
    @@ -10635,9 +10635,9 @@ user3:
     
     

    通过接口判断是否带有注解跟是注解的值,DatabaseType 的配置不太好,不过先忽略了,然后在切点后进行清理

    这是我 master1 的数据,

    -

    +

    master2 的数据

    -

    +

    然后跑一下简单的 demo,

    @Override
     public void run(String...args) {
    @@ -10648,7 +10648,7 @@ user3:
     }

    看一下运行结果

    -

    +

    其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。

    ]]> @@ -10951,7 +10951,7 @@ user3: 聊聊 Dubbo 的容错机制 /2020/11/22/%E8%81%8A%E8%81%8A-Dubbo-%E7%9A%84%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6/ - 之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    + 之前看了 dubbo 的一些代码,在学习过程中,主要关注那些比较“高级”的内容,SPI,自适应扩展等,却忘了一些作为一个 rpc 框架最核心需要的部分,比如如何通信,序列化,网络,容错机制等等,因为其实这个最核心的就是远程调用,自适应扩展其实就是让代码可扩展性,可读性,更优雅等,写的搓一点其实也问题不大,但是一个合适的通信协议,序列化方法,如何容错等却是真正保证是一个 rpc 框架最重要的要素。
    首先来看这张图
    cluster
    在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
    各节点关系:

    • 这里的 InvokerProvider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
    • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
    • @@ -11127,9 +11127,9 @@ public Result invoke(final Invocation invocation) throws RpcException { } }
    -

    Untitled

    +

    Untitled

    然后可以看到打印输出,当然这里因为对齐方式,我们看到的其实顺序是反过来的,按最后三位去看,我们这是 001,好像偏向锁都没开,这里使用的是 jdk1.8,默认开始偏向锁的,其实这里有涉及到了一个配置,jdk1.8 中偏向锁会延迟 4 秒开启,可以通过添加启动参数 -XX:+PrintFlagsFinal,看到

    -

    偏向锁延迟

    +

    偏向锁延迟

    因为在初始化的时候防止线程竞争有大量的偏向锁撤销升级,所以会延迟 4s 开启

    我们再来延迟 5s 看看

    public class ObjectHeaderDemo {
    @@ -11140,7 +11140,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
     		}
     } 
    -

    https://gitee.com/nicksxs/images/raw/master/uPic/2LBKpX.jpg

    +

    https://img.nicksxs.com/uPic/2LBKpX.jpg

    可以看到偏向锁设置已经开启了,我们来是一下加个偏向锁

    public class ObjectHeaderDemo {
         public static void main(String[] args) throws InterruptedException {
    @@ -11157,7 +11157,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
     }

    看下运行结果

    -

    https://gitee.com/nicksxs/images/raw/master/uPic/V2l78m.png

    +

    https://img.nicksxs.com/uPic/V2l78m.png

    可以看到是加上了 101 = 5 也就是偏向锁,后面是线程 id

    当我再使用一个线程来竞争这个锁的时候

    public class ObjectHeaderDemo {
    @@ -11191,7 +11191,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
     		}
     }
    -

    https://gitee.com/nicksxs/images/raw/master/uPic/bRMvlR.png

    +

    https://img.nicksxs.com/uPic/bRMvlR.png

    可以看到变成了轻量级锁,在线程没有争抢,只是进行了切换,就会使用轻量级锁,当两个线程在竞争了,就又会升级成重量级锁

    public class ObjectHeaderDemo {
         public static void main(String[] args) throws InterruptedException {
    @@ -11243,7 +11243,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
         private boolean myboolean = true;
     }
    -

    https://gitee.com/nicksxs/images/raw/master/uPic/LMzMtR.png

    +

    https://img.nicksxs.com/uPic/LMzMtR.png

    可以看到变成了重量级锁。

    ]]> @@ -11532,7 +11532,7 @@ public Result invoke(final Invocation invocation) throws RpcException { System.out.println(object instanceof ClassLoaderTest); } }
    -

    可以看下结果

    这里说明了当一个是由虚拟机的应用程序类加载器所加载的和另一个由自己写的自定义类加载器加载的,虽然是同一个类,但是 instanceof 的结果就是 false 的

    +

    可以看下结果

    这里说明了当一个是由虚拟机的应用程序类加载器所加载的和另一个由自己写的自定义类加载器加载的,虽然是同一个类,但是 instanceof 的结果就是 false 的

    双亲委派

    自 JDK1.2 以来,Java 一直有些三层类加载器、双亲委派的类加载架构

    启动类加载器

    首先是启动类加载器,Bootstrap Class Loader,这个类加载器负责加载放在\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java 虚拟机能够识别的(按照文件名识别,如 rt.jar、tools.jar,名字不符合的类库即使放在 lib 目录中,也不会被加载)类库加载到虚拟机的内存中,启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把家在请求为派给引导类加载器去处理,那直接使用 null 代替即可,可以看下 java.lang.ClassLoader.getClassLoader()方法的代码片段

    /**
    @@ -11717,7 +11717,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
     
         }

    它负责加载用户类路径(ClassPath)上所有的类库,我们可以直接在代码中使用这个类加载器,如果我们的代码中没有自定义的类在加载器,一般情况下这个就是程序中默认的类加载器

    -

    双亲委派模型


    双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试家在这个类,而是把这个请求为派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的家在请求最终都应该传送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去完成加载。
    使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是 Java 中的类随着它的类加载器一起举杯了一种带有优先级的层次关系。例如类 java.lang.Object,它存放在 rt.jar 之中,无论哪一个类加载器要家在这个类,最终都是委派给处于模型最顶层的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双薪委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为 java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中就会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
    可以来看下双亲委派模型的代码实现

    +

    双亲委派模型


    双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试家在这个类,而是把这个请求为派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的家在请求最终都应该传送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去完成加载。
    使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是 Java 中的类随着它的类加载器一起举杯了一种带有优先级的层次关系。例如类 java.lang.Object,它存放在 rt.jar 之中,无论哪一个类加载器要家在这个类,最终都是委派给处于模型最顶层的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双薪委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为 java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中就会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
    可以来看下双亲委派模型的代码实现

    /**
          * Loads the class with the specified <a href="#name">binary name</a>.  The
          * default implementation of this method searches for classes in the
    @@ -11820,7 +11820,7 @@ public Result invoke(final Invocation invocation) throws RpcException {
         聊聊 Java 自带的那些*逆天*工具
         /2020/08/02/%E8%81%8A%E8%81%8A-Java-%E8%87%AA%E5%B8%A6%E7%9A%84%E9%82%A3%E4%BA%9B%E9%80%86%E5%A4%A9%E5%B7%A5%E5%85%B7/
         原谅我的标题党,其实这些工具的确很厉害,之前其实介绍过一点相关的,是从我一次问题排查的过程中用到的,但是最近又有碰到一次排查问题,发现其实用 idea 直接 dump thread 是不现实的,毕竟服务器环境的没法这么操作,那就得用 Java 的那些工具了

    -

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    +

    jstack & jps

    譬如 jstack,这个命令其实不能更简单了
    看看 help 信息

    -l参数可以打出锁的额外信息,然后后面的 pid 就是进程 id 咯,机智的小伙伴会问了(就你这个小白才问这么蠢的问题🤦‍♂️),怎么看 Java 应用的进程呢
    那就是 jps 了,命令也很简单,一般直接用 jps命令就好了,不过也可以 help 看一下

    稍微解释下,-q是只显示进程 id,-m是输出给main 方法的参数,比如我在配置中加给参数

    然后用 jps -m查看

    -v加上小 v 的话就是打印 jvm 参数

    还是有点东西,然后就继续介绍 jstack 了,然后我们看看 jstack 出来是啥,为了加点内容我加了个死锁

    public static void main(String[] args) throws InterruptedException {
             SpringApplication.run(ThreadDumpDemoApplication.class, args);
             ReentrantLock lock1 = new ReentrantLock();
    @@ -12321,8 +12321,8 @@ JNI global references: com.nicksxs.thread_dump_demo.ThreadDumpDemoApplication$1.run(ThreadDumpDemoApplication.java:22)
     
     Found 1 deadlock.
    -

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    -

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    +

    前面的信息其实上次就看过了,后面就可以发现有个死锁了,

    上面比较长,把主要的截出来,就是这边的,这点就很强大了。

    +

    jmap

    惯例还是看一下帮助信息

    这个相对命令比较多,不过因为现在 dump 下来我们可能会用文件模式,然后将文件下载下来使用 mat 进行分析,所以可以使用
    jmap -dump:live,format=b,file=heap.bin <pid>
    命令照着上面看的就是打印活着的对象,然后以二进制格式,文件名叫 heap.bin 然后最后就是进程 id,打印出来以后可以用 mat 打开

    这样就可以很清晰的看到应用里的各种信息,jmap 直接在命令中还可以看很多信息,比如使用jmap -histo <pid>打印对象的实例数和对象占用的内存

    jmap -finalizerinfo <pid> 打印正在等候回收的对象

    小tips

    对于一些应用内存已经占满了,jstack 和 jmap 可能会连不上的情况,可以使用-F参数强制打印线程或者 dump 文件,但是要注意这两者使用的用户必须与 java 进程启动用户一致,并且使用的 jdk 也要一致

    ]]>
    @@ -12345,7 +12345,7 @@ JNI global references: -H :Threads-mode operation Instructs top to display individual threads. Without this command-line option a summation of all threads in each process is shown. Later this can be changed with the `H' interactive command.
    -

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    +

    这样就能用在 Java 中去 jstack 中找到对应的线程
    其实还有比较重要的两个操作,
    一个是在 top 启动状态下,按c键,这样能把比如说是一个 Java 进程,具体的进程命令显示出来
    像这样
    执行前是这样

    执行后是这样

    第二个就是排序了

    SORTING of task window
     
               For  compatibility,  this top supports most of the former top sort keys.  Since this is primarily a service to former top users, these commands
    @@ -12373,8 +12373,8 @@ JNI global references: = result Broker
           
       
    +  
    +    聊聊 Sharding-Jdbc 分库分表下的分页方案
    +    /2022/01/09/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%8B%E7%9A%84%E5%88%86%E9%A1%B5%E6%96%B9%E6%A1%88/
    +    前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    +
    CREATE TABLE `student_time_0` (
    +  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    +  `user_id` int(11) NOT NULL,
    +  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
    +  `age` tinyint(3) unsigned DEFAULT NULL,
    +  `create_time` bigint(20) DEFAULT NULL,
    +  PRIMARY KEY (`id`)
    +) ENGINE=InnoDB AUTO_INCREMENT=674 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    +

    有这样的三个表,student_time_0, student_time_1, student_time_2, 以 user_id 作为分表键,根据表数量取模作为分表依据
    这里先构造点数据,

    +
    insert into student_time (`name`, `user_id`, `age`, `create_time`) values (?, ?, ?, ?)
    +

    主要是为了保证 create_time 唯一比较好说明问题,

    +
    int i = 0;
    +try (
    +        Connection conn = dataSource.getConnection();
    +        PreparedStatement ps = conn.prepareStatement(insertSql)) {
    +    do {
    +        ps.setString(1, localName + new Random().nextInt(100));
    +        ps.setLong(2, 10086L + (new Random().nextInt(100)));
    +        ps.setInt(3, 18);
    +        ps.setLong(4, new Date().getTime());
    +
    +
    +        int result = ps.executeUpdate();
    +        LOGGER.info("current execute result: {}", result);
    +        Thread.sleep(new Random().nextInt(100));
    +        i++;
    +    } while (i <= 2000);
    +

    三个表的数据分别是 673,678,650,说明符合预期了,各个表数据不一样,接下来比如我们想要做一个这样的分页查询

    +
    select * from student_time ORDER BY create_time ASC limit 1000, 5;
    +

    student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表,首先从一无所知去想这个查询如果我们自己来处理应该是怎么做,
    首先是不是可以每个表都从 333 开始取 5 条数据,类似于下面的查询,然后进行 15 条的合并重排序获取前面的 5 条

    +
    select * from student_time_0 ORDER BY create_time ASC limit 333, 5;
    +select * from student_time_1 ORDER BY create_time ASC limit 333, 5;
    +select * from student_time_2 ORDER BY create_time ASC limit 333, 5;
    +

    忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
    因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

    黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
    那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
    第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
    第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
    这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

    +
    t0
    +334 10158 nick95  18  1641548941767
    +335 10098 nick11  18  1641548941879
    +336 10167 nick51  18  1641548942089
    +337 10167 nick3 18  1641548942119
    +338 10170 nick57  18  1641548942169
    +
    +
    +t1
    +334 10105 nick98  18  1641548939071   最小
    +335 10174 nick94  18  1641548939377
    +336 10129 nick85  18  1641548939442
    +337 10141 nick84  18  1641548939480
    +338 10096 nick74  18  1641548939668
    +
    +t2
    +334 10184 nick11  18  1641548945075
    +335 10109 nick93  18  1641548945382
    +336 10181 nick41  18  1641548945583
    +337 10130 nick80  18  1641548945993
    +338 10184 nick19  18  1641548946294  最大
    +

    然后要做什么呢,其实目标比较明白,因为前面那种方法其实就是我知道了前一页的偏移量,所以可以直接当做条件来进行查询,那这里我也想着拿到这个条件,所以我将第一遍查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,其实主要是最小值,因为我拿着最小值去查以后我就能知道这个最小值在每个表里处在什么位置,

    +
    t0
    +322 10161 nick81  18  1641548939284
    +323 10113 nick16  18  1641548939393
    +324 10110 nick56  18  1641548939577
    +325 10116 nick69  18  1641548939588
    +326 10173 nick51  18  1641548939646
    +
    +t1
    +334 10105 nick98  18  1641548939071
    +335 10174 nick94  18  1641548939377
    +336 10129 nick85  18  1641548939442
    +337 10141 nick84  18  1641548939480
    +338 10096 nick74  18  1641548939668
    +
    +t2
    +297 10136 nick28  18  1641548939161
    +298 10142 nick68  18  1641548939177
    +299 10124 nick41  18  1641548939237
    +300 10148 nick87  18  1641548939510
    +301 10169 nick23  18  1641548939715
    +

    我只贴了前五条数据,为了方便知道偏移量,每个分表都使用了自增主键,我们可以看到前一次查询的最小值分别在其他两个表里的位置分别是 322-1 和 297-1,那么对于总体来说这个时间应该是在 322 - 1 + 333 + 297 - 1 = 951,那这样子我只要对后面的数据最多每个表查 1000 - 951 + 5 = 54 条数据再进行合并排序就可以获得最终正确的结果。
    这个就是传说中的二次查询法。

    +]]>
    + + Java + + + Java + Sharding-Jdbc + +
    聊聊 Sharding-Jdbc 的简单使用 /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/ @@ -12969,7 +13059,7 @@ result = result } }
    -

    然后我们就可以在使用这个 DataSource 了,先看下这两个表的数据

    +

    然后我们就可以在使用这个 DataSource 了,先看下这两个表的数据

    @Override
         public void run(String... args) {
             LOGGER.info("run here");
    @@ -13006,7 +13096,36 @@ result = result .printStackTrace();
             }
         }
    -

    看下查询结果

    +

    看下查询结果

    +]]> + + Java + + + Java + Sharding-Jdbc + + + + 聊聊 Sharding-Jdbc 的简单原理初篇 + /2021/12/26/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E5%8E%9F%E7%90%86%E5%88%9D%E7%AF%87/ + 在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
    就是我在执行 query 的时候犯过一个比较难发现的错误,

    +
    ResultSet resultSet = ps.executeQuery(sql);
    +

    实际上应该是

    +
    ResultSet resultSet = ps.executeQuery();
    +

    而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

    这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
    那么在我上面的写错的代码里

    +
    @Override
    +public final ResultSet executeQuery(final String sql) throws SQLException {
    +    throw new SQLFeatureNotSupportedException("executeQuery with SQL for PreparedStatement");
    +}
    +

    这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

    看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

    在前一篇的 demo 中的

    +
    Connection conn = dataSource.getConnection();
    +

    其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
    然后获得java.sql.PreparedStatement

    +
    PreparedStatement ps = conn.prepareStatement(sql)
    +

    就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
    然后就是执行

    +
    ResultSet resultSet = ps.executeQuery();
    +

    然后获得结果
    org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

    +

    其实像 mybatis 也是基于这样去实现的

    ]]>
    Java @@ -13019,7 +13138,7 @@ result = result 聊聊 dubbo 的线程池 /2021/04/04/%E8%81%8A%E8%81%8A-dubbo-%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%B1%A0/ - 之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
    看下代码目录主要是这几个

    + 之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
    看下代码目录主要是这几个

    • FixedThreadPool:创建一个复用固定个数线程的线程池。
      简单看下代码
      public Executor getExecutor(URL url) {
               String name = url.getParameter("threadname", "Dubbo");
      @@ -13274,14 +13393,14 @@ $
       call nullIndex1();

      然后看下我们的 is null 查询

      EXPLAIN select * from null_index_t WHERE null_key is null;
      -


      再来看看另一个

      +


      再来看看另一个

      EXPLAIN select * from null_index_t WHERE null_key is not null;
      -


      从这里能看出来啥呢,可以思考下

      -

      从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
      我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

      然后再来看看执行结果

      +


      从这里能看出来啥呢,可以思考下

      +

      从上面可以发现,is null应该是用上了索引了,所以至少不是一刀切不能用,但是看着is not null好像不太行额
      我们在做一点小改动,把这个表里的数据改成 9100 条是 null,剩下 900 条是有值的,然后再执行下

      然后再来看看执行结果

      EXPLAIN select * from null_index_t WHERE null_key is null;
      -

      +

      EXPLAIN select * from null_index_t WHERE null_key is not null;
      -


      是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
      其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

      +


      是不是不一样了,这里再补充下我试验使用的 mysql 是 5.7 的,不保证在其他版本的一致性,
      其实可以看出随着数据量的变化,mysql 会不会使用索引是会变化的,不是说 is not null 一定会使用,也不是一定不会使用,而是优化器会根据查询成本做个预判,这个预判尽可能会减小查询成本,主要包括回表啥的,但是也不一定完全准确。

      ]]> Mysql @@ -13310,22 +13429,97 @@ $

      解决缓存雪崩

      雪崩的情况是指大批量的 key 都一起过期了,击穿的放大版,大批量的请求都打到数据库上了,一方面有可能直接缓存不可用了,就需要用集群化高可用的缓存服务,然后对于实际使用中也可以使用本地缓存结合 redis 缓存,去提高可用性,再配合一些限流措施,然后就是缓存使用过程总的过期时间最好能加一些随机值,防止在同一时间过期而导致雪崩,结合互斥锁防止大量请求打到数据库。

      ]]> - Redis - 应用 - 缓存 - 缓存 - 穿透 - 击穿 - 雪崩 + Redis + 应用 + 缓存 + 缓存 + 穿透 + 击穿 + 雪崩 + + + Redis + 缓存穿透 + 缓存击穿 + 缓存雪崩 + 布隆过滤器 + bloom filter + 互斥锁 + + + + 聊聊Java中的单例模式 + /2019/12/21/%E8%81%8A%E8%81%8AJava%E4%B8%AD%E7%9A%84%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/ + 这是个 Java 面试的高频问题,我也遇到过,以往都是觉得这类题没意思,网上一搜一大堆,也不愿意记,其实说回来,主要还是没静下心来好好去理解,今天无意中看到一个课程,基本帮我把一些疑惑的点讲清楚了,首先单例是啥意思,这个其实是有范围一说,比如我起了个Spring Boot应用,在这个应用范围内,我的常规 bean 是单例的,意味着 getBean 的时候其实永远只会拿到那一个对象,那要怎么来写一个单例呢,首先就是传说中的饿汉模式,也是最简单的

      +

      饿汉模式

      public class Singleton1 {
      +    // 首先,将构造方法变成私有的
      +    private Singleton1() {};
      +    // 创建私有静态实例,这样第一次使用的时候就会进行创建
      +    private static Singleton instance = new Singleton1();
      +
      +    // 使用这个对象都是通过这个 getInstance 来获取
      +    public static Singleton1 getInstance() {
      +        return instance;
      +    }
      +    // 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
      +    // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
      +    public static Date getDate(String mode) {return new Date();}
      +}
      +

      上面借鉴了一些代码,其实这是最基本,也不会错的方法,但是正如其中getDate方法里说的问题,有时候并没有想那这个对象,但是因为我调用了这个类的静态方法,导致对象已经生成了,可能这也是饿汉模式名字的来由,不管三七二十一给你生成个单例就完事了,不管有没有用,但是这种个人觉得也没啥大问题,如果是面试的话最好说出来它的缺点

      +

      饱汉模式

      public class Singleton2 {
      +    // 首先,也是先堵死 new Singleton() 这条路,将构造方法变成私有
      +    private Singleton2() {}
      +    // 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
      +    private static volatile Singleton2 instance = null;
      +
      +    private int m = 9;
      +
      +    public static Singleton getInstance() {
      +        if (instance == null) {
      +            // 加锁
      +            synchronized (Singleton2.class) {
      +                // 这一次判断也是必须的,不然会有并发问题
      +                if (instance == null) {
      +                    instance = new Singleton2();
      +                }
      +            }
      +        }
      +        return instance;
      +    }
      +}
      +

      这里容易错的有三点,理解了其实就比较好记了

      +

      第一点,为啥不在 getInstance 上整个代码块加 synchronized,这个其实比较容易理解,就是锁的力度太大,性能太差了,这点其实也要去理解,可以举个夸张的例子,比如我一个电商的服务,如果为了避免一个人的订单出现问题,是不是可以从请求入口就把他锁住,到请求结束释放,那么里面做的事情都有保障,然而这显然不可能,因为我们想要这种竞态条件抢占资源的时间尽量减少,防止其他线程等待。
      第二点,为啥synchronized之已经检查了 instance == null,还要在里面再检查一次,这个有个术语,叫 double check lock,但是为啥要这么做呢,其实很简单,想象当有两个线程,都过了第一步为空判断,这个时候只有一个线程能拿到这个锁,另一个线程就等待了,如果不再判断一次,那么第一个线程新建完对象释放锁之后,第二个线程又能拿到锁,再去创建一个对象。
      第三点,为啥要volatile关键字,原先对它的理解是它修饰的变量在 JMM 中能及时将变量值写到主存中,但是它还有个很重要的作用,就是防止指令重排序,instance = new Singleton();这行代码其实在底层是分成三条指令执行的,第一条是在堆上申请了一块内存放这个对象,但是对象的字段啥的都还是默认值,第二条是设置对象的值,比如上面的 m 是 9,然后第三条是将这个对象和虚拟机栈上的指针建立引用关联,那么如果我不用volatile关键字,这三条指令就有可能出现重排,比如变成了 1-3-2 这种顺序,当执行完第二步时,有个线程来访问这个对象了,先判断是不是空,发现不是空的,就拿去直接用了,是不是就出现问题了,所以这个volatile也是不可缺少的

      +

      嵌套类

      public class Singleton3 {
      +
      +    private Singleton3() {}
      +    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
      +    private static class Holder {
      +        private static Singleton3 instance = new Singleton3();
      +    }
      +    public static Singleton3 getInstance() {
      +        return Holder.instance;
      +    }
      +}
      +

      这个我个人感觉是饿汉模式的升级版,可以在调用getInstance的时候去实例化对象,也是比较推荐的

      +

      枚举单例

      public enum Singleton {
      +    INSTANCE;
      +    
      +    public void doSomething(){
      +        //todo doSomething
      +    }
      +}
      +

      枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。

      +]]>
      + + Java + Design Patterns + Singleton - Redis - 缓存穿透 - 缓存击穿 - 缓存雪崩 - 布隆过滤器 - bloom filter - 互斥锁 + 设计模式 + Design Patterns + 单例 + Singleton
      @@ -13678,110 +13872,6 @@ $ AutoConfiguration - - 聊聊 Sharding-Jdbc 的简单原理初篇 - /2021/12/26/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E7%9A%84%E7%AE%80%E5%8D%95%E5%8E%9F%E7%90%86%E5%88%9D%E7%AF%87/ - 在上一篇 sharding-jdbc 的介绍中其实碰到过一个问题,这里也引出了一个比较有意思的话题
      就是我在执行 query 的时候犯过一个比较难发现的错误,

      -
      ResultSet resultSet = ps.executeQuery(sql);
      -

      实际上应该是

      -
      ResultSet resultSet = ps.executeQuery();
      -

      而这里的差别就是,是否传 sql 这个参数,首先我们要知道这个 ps 是什么,它也是个接口java.sql.PreparedStatement,而真正的实现类是org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement,我们来看下继承关系

      这里可以看到继承关系里有org.apache.shardingsphere.driver.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement
      那么在我上面的写错的代码里

      -
      @Override
      -public final ResultSet executeQuery(final String sql) throws SQLException {
      -    throw new SQLFeatureNotSupportedException("executeQuery with SQL for PreparedStatement");
      -}
      -

      这个报错一开始让我有点懵,后来点进去了发现是这么个异常,但是我其实一开始是用的更新语句,以为更新不支持,因为平时使用没有深究过,以为是不是需要使用 Mybatis 才可以执行更新,但是理论上也不应该,再往上看原来这些异常是由 sharding-jdbc 包装的,也就是在上面说的AbstractUnsupportedOperationPreparedStatement,这其实也是一种设计思想,本身 jdbc 提供了一系列接口,由各家去支持,包括 mysql,sql server,oracle 等,而正因为这个设计,所以 sharding-jdbc 也可以在此基础上进行设计,我们可以总体地看下 sharding-jdbc 的实现基础

      看了前面ShardingSpherePreparedStatement的继承关系,应该也能猜到这里的几个类都是实现了 jdbc 的基础接口,

      在前一篇的 demo 中的

      -
      Connection conn = dataSource.getConnection();
      -

      其实就获得了org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection#ShardingSphereConnection
      然后获得java.sql.PreparedStatement

      -
      PreparedStatement ps = conn.prepareStatement(sql)
      -

      就是获取了org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement
      然后就是执行

      -
      ResultSet resultSet = ps.executeQuery();
      -

      然后获得结果
      org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet

      -

      其实像 mybatis 也是基于这样去实现的

      -]]>
      - - Java - - - Java - Sharding-Jdbc - -
      - - 聊聊Java中的单例模式 - /2019/12/21/%E8%81%8A%E8%81%8AJava%E4%B8%AD%E7%9A%84%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/ - 这是个 Java 面试的高频问题,我也遇到过,以往都是觉得这类题没意思,网上一搜一大堆,也不愿意记,其实说回来,主要还是没静下心来好好去理解,今天无意中看到一个课程,基本帮我把一些疑惑的点讲清楚了,首先单例是啥意思,这个其实是有范围一说,比如我起了个Spring Boot应用,在这个应用范围内,我的常规 bean 是单例的,意味着 getBean 的时候其实永远只会拿到那一个对象,那要怎么来写一个单例呢,首先就是传说中的饿汉模式,也是最简单的

      -

      饿汉模式

      public class Singleton1 {
      -    // 首先,将构造方法变成私有的
      -    private Singleton1() {};
      -    // 创建私有静态实例,这样第一次使用的时候就会进行创建
      -    private static Singleton instance = new Singleton1();
      -
      -    // 使用这个对象都是通过这个 getInstance 来获取
      -    public static Singleton1 getInstance() {
      -        return instance;
      -    }
      -    // 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
      -    // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
      -    public static Date getDate(String mode) {return new Date();}
      -}
      -

      上面借鉴了一些代码,其实这是最基本,也不会错的方法,但是正如其中getDate方法里说的问题,有时候并没有想那这个对象,但是因为我调用了这个类的静态方法,导致对象已经生成了,可能这也是饿汉模式名字的来由,不管三七二十一给你生成个单例就完事了,不管有没有用,但是这种个人觉得也没啥大问题,如果是面试的话最好说出来它的缺点

      -

      饱汉模式

      public class Singleton2 {
      -    // 首先,也是先堵死 new Singleton() 这条路,将构造方法变成私有
      -    private Singleton2() {}
      -    // 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
      -    private static volatile Singleton2 instance = null;
      -
      -    private int m = 9;
      -
      -    public static Singleton getInstance() {
      -        if (instance == null) {
      -            // 加锁
      -            synchronized (Singleton2.class) {
      -                // 这一次判断也是必须的,不然会有并发问题
      -                if (instance == null) {
      -                    instance = new Singleton2();
      -                }
      -            }
      -        }
      -        return instance;
      -    }
      -}
      -

      这里容易错的有三点,理解了其实就比较好记了

      -

      第一点,为啥不在 getInstance 上整个代码块加 synchronized,这个其实比较容易理解,就是锁的力度太大,性能太差了,这点其实也要去理解,可以举个夸张的例子,比如我一个电商的服务,如果为了避免一个人的订单出现问题,是不是可以从请求入口就把他锁住,到请求结束释放,那么里面做的事情都有保障,然而这显然不可能,因为我们想要这种竞态条件抢占资源的时间尽量减少,防止其他线程等待。
      第二点,为啥synchronized之已经检查了 instance == null,还要在里面再检查一次,这个有个术语,叫 double check lock,但是为啥要这么做呢,其实很简单,想象当有两个线程,都过了第一步为空判断,这个时候只有一个线程能拿到这个锁,另一个线程就等待了,如果不再判断一次,那么第一个线程新建完对象释放锁之后,第二个线程又能拿到锁,再去创建一个对象。
      第三点,为啥要volatile关键字,原先对它的理解是它修饰的变量在 JMM 中能及时将变量值写到主存中,但是它还有个很重要的作用,就是防止指令重排序,instance = new Singleton();这行代码其实在底层是分成三条指令执行的,第一条是在堆上申请了一块内存放这个对象,但是对象的字段啥的都还是默认值,第二条是设置对象的值,比如上面的 m 是 9,然后第三条是将这个对象和虚拟机栈上的指针建立引用关联,那么如果我不用volatile关键字,这三条指令就有可能出现重排,比如变成了 1-3-2 这种顺序,当执行完第二步时,有个线程来访问这个对象了,先判断是不是空,发现不是空的,就拿去直接用了,是不是就出现问题了,所以这个volatile也是不可缺少的

      -

      嵌套类

      public class Singleton3 {
      -
      -    private Singleton3() {}
      -    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
      -    private static class Holder {
      -        private static Singleton3 instance = new Singleton3();
      -    }
      -    public static Singleton3 getInstance() {
      -        return Holder.instance;
      -    }
      -}
      -

      这个我个人感觉是饿汉模式的升级版,可以在调用getInstance的时候去实例化对象,也是比较推荐的

      -

      枚举单例

      public enum Singleton {
      -    INSTANCE;
      -    
      -    public void doSomething(){
      -        //todo doSomething
      -    }
      -}
      -

      枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。

      -]]>
      - - Java - Design Patterns - Singleton - - - 设计模式 - Design Patterns - 单例 - Singleton - -
      聊聊一次 brew update 引发的血案 /2020/06/13/%E8%81%8A%E8%81%8A%E4%B8%80%E6%AC%A1-brew-update-%E5%BC%95%E5%8F%91%E7%9A%84%E8%A1%80%E6%A1%88/ @@ -13864,7 +13954,7 @@ $ static class Man { String name = "nick"; }
    -

    这里构造了两个线程,一个先往里设值,一个后从里取,运行看下结果,

    知道这个用法的话肯定知道是取不到值的,只是具体的原理原来搞错了,我们来看下设值 set 方法

    +

    这里构造了两个线程,一个先往里设值,一个后从里取,运行看下结果,

    知道这个用法的话肯定知道是取不到值的,只是具体的原理原来搞错了,我们来看下设值 set 方法

    public void set(T value) {
         Thread t = Thread.currentThread();
         ThreadLocalMap map = getMap(t);
    @@ -13877,7 +13967,7 @@ $
     
    ThreadLocalMap getMap(Thread t) {
         return t.threadLocals;
     }
    -

    获取 t 的 threadLocals 成员变量,那这个 threadLocals 又是啥呢

    它其实是线程 Thread 中的一个类型是java.lang.ThreadLocal.ThreadLocalMap的成员变量
    这是 ThreadLocal 的一个静态成员变量

    +

    获取 t 的 threadLocals 成员变量,那这个 threadLocals 又是啥呢

    它其实是线程 Thread 中的一个类型是java.lang.ThreadLocal.ThreadLocalMap的成员变量
    这是 ThreadLocal 的一个静态成员变量

    static class ThreadLocalMap {
     
             /**
    @@ -13898,150 +13988,60 @@ $
                 }
             }
         }
    -

    全部代码有点长,只截取了一小部分,然后我们再回头来分析前面说的 set 过程,再 copy 下代码

    -
    public void set(T value) {
    -    Thread t = Thread.currentThread();
    -    ThreadLocalMap map = getMap(t);
    -    if (map != null)
    -        map.set(this, value);
    -    else
    -        createMap(t, value);
    -}
    -

    获取到 map 以后呢,如果 map 不为空,就往 map 里 set,这里注意 key 是啥,其实是当前这个 ThreadLocal,这里就比较明白了究竟是啥结构,每个线程都会维护自身的 ThreadLocalMap,它是线程的一个成员变量,当创建 ThreadLocal 的时候,进行设值的时候其实是往这个 map 里以 ThreadLocal 作为 key,往里设 value。

    -

    内存泄漏是什么鬼

    这里又要看下前面的 ThreadLocalMap 结构了,类似 HashMap,它有个 Entry 结构,在设置的时候会先包装成一个 Entry

    -
    private void set(ThreadLocal<?> key, Object value) {
    -
    -        // We don't use a fast path as with get() because it is at
    -        // least as common to use set() to create new entries as
    -        // it is to replace existing ones, in which case, a fast
    -        // path would fail more often than not.
    -
    -        Entry[] tab = table;
    -        int len = tab.length;
    -        int i = key.threadLocalHashCode & (len-1);
    -
    -        for (Entry e = tab[i];
    -             e != null;
    -             e = tab[i = nextIndex(i, len)]) {
    -            ThreadLocal<?> k = e.get();
    -
    -            if (k == key) {
    -                e.value = value;
    -                return;
    -            }
    -
    -            if (k == null) {
    -                replaceStaleEntry(key, value, i);
    -                return;
    -            }
    -        }
    -
    -        tab[i] = new Entry(key, value);
    -        int sz = ++size;
    -        if (!cleanSomeSlots(i, sz) && sz >= threshold)
    -            rehash();
    -}
    -

    这里其实比较重要的就是前面的 Entry 的构造方法,Entry 是个 WeakReference 的子类,然后在构造方法里可以看到 key 会被包装成一个弱引用,这里为什么使用弱引用,其实是方便这个 key 被回收,如果前面的 ThreadLocal tl实例被设置成 null 了,如果这里是直接的强引用的话,就只能等到线程整个回收了,但是其实是弱引用也会有问题,主要是因为这个 value,如果在 ThreadLocal tl 被设置成 null 了,那么其实这个 value 就会没法被访问到,所以最好的操作还是在使用完了就 remove 掉

    -]]> - - Java - - - Java - ThreadLocal - 弱引用 - 内存泄漏 - WeakReference - - - - 聊聊 Sharding-Jdbc 分库分表下的分页方案 - /2022/01/09/%E8%81%8A%E8%81%8A-Sharding-Jdbc-%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E4%B8%8B%E7%9A%84%E5%88%86%E9%A1%B5%E6%96%B9%E6%A1%88/ - 前面在聊 Sharding-Jdbc 的时候看到了一篇文章,关于一个分页的查询,一直比较直接的想法就是分库分表下的分页是非常不合理的,一般我们的实操方案都是分表加上 ES 搜索做分页,或者通过合表读写分离的方案,因为对于 sharding-jdbc 如果没有带分表键,查询基本都是只能在所有分表都执行一遍,然后再加上分页,基本上是分页越大后续的查询越耗资源,但是仔细的去想这个细节还是这次,就简单说说
    首先就是我的分表结构

    -
    CREATE TABLE `student_time_0` (
    -  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    -  `user_id` int(11) NOT NULL,
    -  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
    -  `age` tinyint(3) unsigned DEFAULT NULL,
    -  `create_time` bigint(20) DEFAULT NULL,
    -  PRIMARY KEY (`id`)
    -) ENGINE=InnoDB AUTO_INCREMENT=674 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    -

    有这样的三个表,student_time_0, student_time_1, student_time_2, 以 user_id 作为分表键,根据表数量取模作为分表依据
    这里先构造点数据,

    -
    insert into student_time (`name`, `user_id`, `age`, `create_time`) values (?, ?, ?, ?)
    -

    主要是为了保证 create_time 唯一比较好说明问题,

    -
    int i = 0;
    -try (
    -        Connection conn = dataSource.getConnection();
    -        PreparedStatement ps = conn.prepareStatement(insertSql)) {
    -    do {
    -        ps.setString(1, localName + new Random().nextInt(100));
    -        ps.setLong(2, 10086L + (new Random().nextInt(100)));
    -        ps.setInt(3, 18);
    -        ps.setLong(4, new Date().getTime());
    -
    -
    -        int result = ps.executeUpdate();
    -        LOGGER.info("current execute result: {}", result);
    -        Thread.sleep(new Random().nextInt(100));
    -        i++;
    -    } while (i <= 2000);
    -

    三个表的数据分别是 673,678,650,说明符合预期了,各个表数据不一样,接下来比如我们想要做一个这样的分页查询

    -
    select * from student_time ORDER BY create_time ASC limit 1000, 5;
    -

    student_time 对于我们使用的 sharding-jdbc 来说当然是逻辑表,首先从一无所知去想这个查询如果我们自己来处理应该是怎么做,
    首先是不是可以每个表都从 333 开始取 5 条数据,类似于下面的查询,然后进行 15 条的合并重排序获取前面的 5 条

    -
    select * from student_time_0 ORDER BY create_time ASC limit 333, 5;
    -select * from student_time_1 ORDER BY create_time ASC limit 333, 5;
    -select * from student_time_2 ORDER BY create_time ASC limit 333, 5;
    -

    忽略前面 limit 差的 1,这个结果除非三个表的分布是绝对的均匀,否则结果肯定会出现一定的偏差,以为每个表的 333 这个位置对于其他表来说都不一定是一样的,这样对于最后整体的结果,就会出现偏差
    因为一直在纠结怎么让这个更直观的表现出来,所以尝试画了个图

    黑色的框代表我从每个表里按排序从 334 到 338 的 5 条数据, 他们在每个表里都是代表了各自正确的排序值,但是对于我们想要的其实是合表后的 1001,1005 这五条,然后我们假设总的排序值位于前 1000 的分布是第 0 个表是 320 条,第 1 个表是 340 条,第 2 个表是 340 条,那么可以明显地看出来我这么查的结果简单合并肯定是不对的。
    那么 sharding-jdbc 是如何保证这个结果的呢,其实就是我在每个表里都查分页偏移量和分页大小那么多的数据,在我这个例子里就是对于 0,1,2 三个分表每个都查 1005 条数据,即使我的数据不平衡到最极端的情况,前 1005 条数据都出在某个分表中,也可以正确获得最后的结果,但是明显的问题就是大分页,数据较多,就会导致非常大的问题,即使如 sharding-jdbc 对于合并排序的优化做得比较好,也还是需要传输那么大量的数据,并且查询也耗时,那么有没有解决方案呢,应该说有两个,或者说主要是想讲后者
    第一个办法是像这种查询,如果业务上不需要进行跳页,而是只给下一页,那么我们就能把前一次的最大偏移量的 create_time 记录下来,下一页就可以拿着这个偏移量进行查询,这个比较简单易懂,就不多说了
    第二个办法是看的58 沈剑的一篇文章,尝试理解讲述一下,
    这个办法的第一步跟前面那个错误的方法或者说不准确的方法一样,先是将分页偏移量平均后在三个表里进行查询

    -
    t0
    -334 10158 nick95  18  1641548941767
    -335 10098 nick11  18  1641548941879
    -336 10167 nick51  18  1641548942089
    -337 10167 nick3 18  1641548942119
    -338 10170 nick57  18  1641548942169
    +

    全部代码有点长,只截取了一小部分,然后我们再回头来分析前面说的 set 过程,再 copy 下代码

    +
    public void set(T value) {
    +    Thread t = Thread.currentThread();
    +    ThreadLocalMap map = getMap(t);
    +    if (map != null)
    +        map.set(this, value);
    +    else
    +        createMap(t, value);
    +}
    +

    获取到 map 以后呢,如果 map 不为空,就往 map 里 set,这里注意 key 是啥,其实是当前这个 ThreadLocal,这里就比较明白了究竟是啥结构,每个线程都会维护自身的 ThreadLocalMap,它是线程的一个成员变量,当创建 ThreadLocal 的时候,进行设值的时候其实是往这个 map 里以 ThreadLocal 作为 key,往里设 value。

    +

    内存泄漏是什么鬼

    这里又要看下前面的 ThreadLocalMap 结构了,类似 HashMap,它有个 Entry 结构,在设置的时候会先包装成一个 Entry

    +
    private void set(ThreadLocal<?> key, Object value) {
     
    +        // We don't use a fast path as with get() because it is at
    +        // least as common to use set() to create new entries as
    +        // it is to replace existing ones, in which case, a fast
    +        // path would fail more often than not.
     
    -t1
    -334 10105 nick98  18  1641548939071   最小
    -335 10174 nick94  18  1641548939377
    -336 10129 nick85  18  1641548939442
    -337 10141 nick84  18  1641548939480
    -338 10096 nick74  18  1641548939668
    +        Entry[] tab = table;
    +        int len = tab.length;
    +        int i = key.threadLocalHashCode & (len-1);
     
    -t2
    -334 10184 nick11  18  1641548945075
    -335 10109 nick93  18  1641548945382
    -336 10181 nick41  18  1641548945583
    -337 10130 nick80  18  1641548945993
    -338 10184 nick19  18  1641548946294  最大
    -

    然后要做什么呢,其实目标比较明白,因为前面那种方法其实就是我知道了前一页的偏移量,所以可以直接当做条件来进行查询,那这里我也想着拿到这个条件,所以我将第一遍查出来的最小的 create_time 和最大的 create_time 找出来,然后再去三个表里查询,其实主要是最小值,因为我拿着最小值去查以后我就能知道这个最小值在每个表里处在什么位置,

    -
    t0
    -322 10161 nick81  18  1641548939284
    -323 10113 nick16  18  1641548939393
    -324 10110 nick56  18  1641548939577
    -325 10116 nick69  18  1641548939588
    -326 10173 nick51  18  1641548939646
    +        for (Entry e = tab[i];
    +             e != null;
    +             e = tab[i = nextIndex(i, len)]) {
    +            ThreadLocal<?> k = e.get();
     
    -t1
    -334 10105 nick98  18  1641548939071
    -335 10174 nick94  18  1641548939377
    -336 10129 nick85  18  1641548939442
    -337 10141 nick84  18  1641548939480
    -338 10096 nick74  18  1641548939668
    +            if (k == key) {
    +                e.value = value;
    +                return;
    +            }
     
    -t2
    -297 10136 nick28  18  1641548939161
    -298 10142 nick68  18  1641548939177
    -299 10124 nick41  18  1641548939237
    -300 10148 nick87  18  1641548939510
    -301 10169 nick23  18  1641548939715
    -

    我只贴了前五条数据,为了方便知道偏移量,每个分表都使用了自增主键,我们可以看到前一次查询的最小值分别在其他两个表里的位置分别是 322-1 和 297-1,那么对于总体来说这个时间应该是在 322 - 1 + 333 + 297 - 1 = 951,那这样子我只要对后面的数据最多每个表查 1000 - 951 + 5 = 54 条数据再进行合并排序就可以获得最终正确的结果。
    这个就是传说中的二次查询法。

    + if (k == null) { + replaceStaleEntry(key, value, i); + return; + } + } + + tab[i] = new Entry(key, value); + int sz = ++size; + if (!cleanSomeSlots(i, sz) && sz >= threshold) + rehash(); +}
    +

    这里其实比较重要的就是前面的 Entry 的构造方法,Entry 是个 WeakReference 的子类,然后在构造方法里可以看到 key 会被包装成一个弱引用,这里为什么使用弱引用,其实是方便这个 key 被回收,如果前面的 ThreadLocal tl实例被设置成 null 了,如果这里是直接的强引用的话,就只能等到线程整个回收了,但是其实是弱引用也会有问题,主要是因为这个 value,如果在 ThreadLocal tl 被设置成 null 了,那么其实这个 value 就会没法被访问到,所以最好的操作还是在使用完了就 remove 掉

    ]]>
    Java Java - Sharding-Jdbc + ThreadLocal + 弱引用 + 内存泄漏 + WeakReference
    @@ -14178,6 +14178,20 @@ t2 骑车 + + 聊聊最近平淡的生活之《花束般的恋爱》观后感 + /2021/12/31/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E3%80%8A%E8%8A%B1%E6%9D%9F%E8%88%AC%E7%9A%84%E6%81%8B%E7%88%B1%E3%80%8B%E8%A7%82%E5%90%8E%E6%84%9F/ + 周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    +

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    +]]>
    + + 生活 + + + 生活 + 看剧 + +
    聊聊最近平淡的生活之看《神探狄仁杰》 /2021/12/19/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E7%9C%8B%E3%80%8A%E7%A5%9E%E6%8E%A2%E7%8B%84%E4%BB%81%E6%9D%B0%E3%80%8B/ @@ -14227,51 +14241,6 @@ t2 修电脑的 - - 聊聊那些加塞狗 - /2021/01/17/%E8%81%8A%E8%81%8A%E9%82%A3%E4%BA%9B%E5%8A%A0%E5%A1%9E%E7%8B%97/ - 今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    -]]>
    - - 生活 - 开车 - - - 生活 - 开车 - 加塞 - 糟心事 - 规则 - -
    - - 聊聊部分公交车的设计bug - /2021/12/05/%E8%81%8A%E8%81%8A%E9%83%A8%E5%88%86%E5%85%AC%E4%BA%A4%E8%BD%A6%E7%9A%84%E8%AE%BE%E8%AE%A1bug/ - 今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    -]]>
    - - 生活 - - - 生活 - 公交 - 杭州 - -
    - - 聊聊最近平淡的生活之《花束般的恋爱》观后感 - /2021/12/31/%E8%81%8A%E8%81%8A%E6%9C%80%E8%BF%91%E5%B9%B3%E6%B7%A1%E7%9A%84%E7%94%9F%E6%B4%BB%E4%B9%8B%E3%80%8A%E8%8A%B1%E6%9D%9F%E8%88%AC%E7%9A%84%E6%81%8B%E7%88%B1%E3%80%8B%E8%A7%82%E5%90%8E%E6%84%9F/ - 周末在领导的提议下看了豆瓣的年度榜单,本来感觉没啥心情看的,看到主演有有村架纯就觉得可以看一下,颜值即正义嘛,男主小麦跟女主小娟(后面简称小麦跟小娟)是两个在一次非常偶然的没赶上地铁末班车事件中相识,这里得说下日本这种通宵营业的店好像挺不错的,看着也挺正常,国内估计只有酒吧之类的可以。晚上去的地方是有点暗暗的,好像也有点类似酒吧,旁边有类似于 dj 那种,然后同桌的还有除了男女主的另外一对男女,也是因为没赶上地铁末班车的,但也是陌生人,然后小麦突然看到了有个非常有名的电影人,小娟竟然也认识,然后旁边那对完全不认识,还在那吹自己看过很多电影,比如《肖申克的救赎》,于是男女主都特别鄙夷地看着他们,然后他们又去了另一个有点像泡澡的地方席地而坐,他们发现了自己的鞋子都是一样的,然后在女的去上厕所的时候,小麦暗恋的学姐也来了,然后小麦就去跟学姐他们一起坐了,小娟回来后有点不开心就说去朋友家睡,幸好小麦看出来了(他竟然看出来了,本来以为应该是没填过恋爱很木讷的),就追出去,然后就去了小麦家,到了家小娟发现小麦家的书柜上的书简直就跟她自己家的一模一样,小麦还给小娟吹了头发,一起吃烤饭团,看电影,第二天送小娟上了公交,还约好了一起看木乃伊展,然而并没有交换联系方式,但是他们还是约上了一起看了木乃伊展,在餐馆就出现了片头那一幕的来源,因为餐馆他们想一起听歌,就用有线耳机一人一个耳朵听,但是旁边就有个大叔说“你们是不是不爱音乐,左右耳朵是不一样的,只有一起听才是真正的音乐”这样的话,然后的剧情有点跳,因为是指他们一直在这家餐馆吃饭,中间有他们一起出去玩情节穿插着,也是在这他们确立了关系,可以说主体就是体现了他们非常的合拍和默契,就像一些影评说的,这部电影是说如何跟百分百合拍的人分手,然后就是正常的恋爱开始啪啪啪,一直腻在床上,也没去就业说明会,后面也有讲了一点小麦带着小娟去认识他的朋友,也把小娟介绍给了他们认识,这里算是个小伏笔,后面他们分手也有这里的人的一些关系,接下去的剧情说实话我是不太喜欢的,如果一部八分的电影只是说恋爱被现实打败的话,我觉得在我这是不合格的,但是事实也是这样,小麦其实是有家里的资助,所以后面还是按自己的喜好给一些机构画点插画,小娟则要出去工作,因为小娟家庭观念也是要让她出去有正经工作,用脚指头想也能知道肯定不顺利,然后就是暂时在一家蛋糕店工作,小麦就每天去接小娟,日子过得甜甜蜜蜜,后面小娟在自己的努力下考了个什么资格证,去了一家医院还是什么做前台行政,这中间当然就有父母来见面吃饭了,他们在开始恋爱不久就同居合租了,然后小娟父母就是来说要让她有个正经工作,对男的说的话就是人生就是责任这类的话,而小麦爸爸算是个导火索,因为小麦家里是做烟花生意的,他爸让他就做烟花生意,因为要回老家,并且小麦也不想做,所以就拒绝了,然后他爸就说不给他每个月五万的资助,这也导致了小麦需要去找工作,这个过程也是很辛苦,本来想要年前找好工作,然后事与愿违,后面有一次小娟被同事吐槽怎么从来不去团建,于是她就去了(我以为会拒绝),正在团建的时候小麦给她电话,说找到工作了,是一个创业物流公司这种,这里剧情就是我觉得比较俗套的,小麦各种被虐,累成狗,但是就像小娟爸爸说的话,人生就是责任,所以一直在坚持,但是这样也导致了跟小娟的交流也越来越少,他们原来最爱的漫画,爱玩的游戏,也只剩小娟一个人看,一个人玩,而正是这个时候,小娟说她辞掉了工作,去做一个不是太靠谱的漫画改造的密室逃脱,然后这里其实有一点后面争议很大的,就是这个工作其实是前面小麦介绍给小娟的那些朋友中一个的女朋友介绍的,而在有个剧情就是小娟有一次在这个密室逃脱的老板怀里醒过来,是在 KTV 那样的场景里,这就有很多人觉得小娟是不是出轨了,我觉得其实不那么重要,因为这个离职的事情已经让一切矛盾都摆在眼前,小麦其实是接受这种需要承担责任的生活,也想着要跟小娟结婚,但是小娟似乎还是想要过着那样理想的生活,做自己想做的事情,看自己爱看的漫画,也要小麦能像以前那样一直那么默契的有着相同的爱好,这里的触发点其实还有个是那个小麦的朋友(也就是他女朋友介绍小娟那个不靠谱工作的)的葬礼上,小麦在参加完葬礼后有挺多想倾诉的,而小娟只是想睡了,这个让小麦第二天起来都不想理小娟,只是这里我不太理解,难道这点闹情绪都不能接受吗,所谓的合拍也只是毫无限制的情况下的合拍吧,真正的生活怎么可能如此理想呢,即使没有物质生活的压力,也会有其他的各种压力和限制,在这之后其实小麦想说的是小娟是不是没有想跟自己继续在一起的想法了,而小娟觉得都不说话了,还怎么结婚呢,后面其实导演搞了个小 trick,突然放了异常婚礼,但是不是男女主的,我并不觉得这个桥段很好,在婚礼里男女主都觉得自己想要跟对方说分手了,但是当他们去了最开始一直去的餐馆的时候,一个算是一个现实映照的就是他们一直坐的位子被占了,可能也是导演想通过这个来说明他们已经回不去了,在餐馆交谈的时候,小麦其实是说他们结婚吧,并没有想前面婚礼上预设地要分手,但是小娟放弃了,不想结婚,因为不想过那样的生活了,而小麦觉得可能生活就是那样,不可能一直保持刚恋爱时候的那种感觉,生活就是责任,人生就意味着责任。

    -

    我的一些观点也在前面说了,恋爱到婚姻,即使物质没问题,经济没问题,也会有各种各样的问题,需要一起去解决,因为结婚就意味着需要相互扶持,而不是各取所需,可能我的要求比较高,后面男女主在分手后还一起住了一段时间,我原来还在想会不会通过这个方式让他们继续去磨合同步,只是我失望了,最后给个打分可能是 5 到 6 分吧,勉强及格,好的影视剧应该源于生活高于生活,这一部可能还比不上生活。

    -]]>
    - - 生活 - - - 生活 - 看剧 - -
    聊聊这次换车牌及其他 /2022/02/20/%E8%81%8A%E8%81%8A%E8%BF%99%E6%AC%A1%E6%8D%A2%E8%BD%A6%E7%89%8C%E5%8F%8A%E5%85%B6%E4%BB%96/ @@ -14289,60 +14258,34 @@ t2 - 这周末我又在老丈人家打了天小工 - /2020/08/30/%E8%BF%99%E5%91%A8%E6%9C%AB%E6%88%91%E5%8F%88%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E6%89%93%E4%BA%86%E5%A4%A9%E5%B0%8F%E5%B7%A5/ - 因为活实在比较多,也不太好叫大工(活比较杂散),相比上一次我跟 LD 俩人晚起了一点,我真的是只要有事,早上就醒的很早,准备八点出发的,六点就醒了,然后想继续睡就一直做梦🤦‍♂️,差不多八点半多到的丈人家,他们应该已经干了有一会了,我们到了以后就分配给我撬地板的活,上次说的那个敲掉柜子的房间里,还铺着质地还不错的木地板,但是也不想要了,得撬掉重新铺。
    拿着撬棍和榔头就上楼去干了,浙江这几天的天气,最高温度一般 38、9,楼上那个房间也没风扇,有了也不能用,都是灰尘,撬了两下,我感觉我体内的水就像真气爆发一样变成汗炸了出来,眼睛全被汗糊住了,可能大部分人不太了解地板是怎么铺的,一般是在地面先铺一层混凝土,混凝土中间嵌进去规则的长条木条,然后真正的地板一块块的都是钉在那个木条上,用那种气枪钉和普通的钉子,并且块跟块之前还有一个木头的槽结构相互耦合,然后边缘的一圈在用较薄的木板将整个木地板封边(这些词都是我现造的),边缘的用的钉子会更多,所以那几下真的很用力,而且撬地板,得蹲下起来,如此反复,对于我这个体重快超过身高的中年人来说的确是非常大的挑战,接下来继续撬了几个,已经有种要虚脱晕倒的感觉了,及时去喝水擦了汗,又歇了一会,为啥一上来就这么拼呢,主要是因为那个房间丈人在干活的时候是直接看得到的🤦‍♂️,后来被 LD 一顿教育,本来就是去帮忙的,又不是专业做这个的,急啥。
    喝了水之后,又稍稍歇了一会,就开始继续撬了,本来觉得这个地板撬着好像还行,房间不大,没多久就撬完了,撬完之后喝了点饮料(补充点糖分,早餐吃得少,有点低血糖),然后看到 LD 在撬下面的木条了,这个动作开始了那天最大的经验值收集行动,前面说了这个木条一般是跟混凝土一块铺上去的,但是谁也没想到,这个混凝土铺上去的时候竟然处理的这么随意,根本没考虑跟下面的贴合,所以撬木条的时候直接把木条跟木条中间大块大块的混凝土一块撬起来了,想想那重量,于是我这靠蛮力干活的,就用力把木条带着混凝土一块撬了起来,还沾沾自喜,但是发现结果是撬起来一块之后,体力值瞬间归零,上一篇我也提到了,其实干这类活也是很有技巧性的,但是上次的是我没学会,可能需要花时间学的,但是这次是LD 用她的纤细胳膊教会我的,我在撬的时候,屏住一口气,双手用力,起,大概是吃好几口奶的力气都用出来了,但是 LD 在我休息的时候,慢慢悠悠的,先把撬棍挤到木条或者混凝土跟下层的缝里,然后往下垫一小块混凝土碎石,然后轻轻松松的扳两下,就撬开了,亏我高中的时候引以为傲的物理成绩,作为物理课代表,这么浅显易懂的杠杆原理都完全不会用到生活里,后面在用这个技巧撬的过程中,真的觉得自己蠢到家了,当然在明白了用点杠杆原理之后,撬地板的活就变得慢慢悠悠,悠哉悠哉的了(其实还是很热的,披着毛巾擦眼睛)。
    上午的活差不多完了,后面就是把撬出来的混凝土和地板条丢下去,地上铺着不用了的被子,然后就是午饭和午休环节了,午饭换了一家快餐,味道非常可以,下午的活就比较单调了,帮忙清理了上去扔下来的混凝土碎块跟木条,然后稍微打扫了下,老丈人就让我们回家了,接着上次说的,还是觉得比跑步啥的消耗大太多了,那汗流的,一口就能喝完一瓶 500 毫升左右的矿泉水。

    -]]>
    - - 生活 - 运动 - 跑步 - 干活 - - - 生活 - 运动 - 减肥 - 跑步 - 干活 - -
    - - 重看了下《蛮荒记》说说感受 - /2021/10/10/%E9%87%8D%E7%9C%8B%E4%BA%86%E4%B8%8B%E3%80%8A%E8%9B%AE%E8%8D%92%E8%AE%B0%E3%80%8B%E8%AF%B4%E8%AF%B4%E6%84%9F%E5%8F%97/ - 周末把《蛮荒记》看完了,前面是发现微信读书有《搜神记》和《蛮荒记》,但是《搜神记》看了会发现很多都是跳段了,不知道为啥,貌似也没什么少儿不宜的情节,所以就上网找了原版来看,为什么看这个呢,主要还是高中的时候看过,觉得写得很不错,属于那时候的玄幻小说里的独一档,基于山海经创造了一个半架空的大荒宇宙,五族帝尊,人物名都是听说过的,而且又能契合部分历史,整个故事布局非常宏大,并且情节矛盾埋得很深,这里就不对具体情节作介绍了,只是聊聊对书中的一些人物和情节的看法感受。

    -

    乌丝兰玛是个贯穿两部,甚至在蛮荒的最后还要再搞事情,极其坚定的自以为是的大 boss,其实除了最后被我们的主人公打败,前面几乎就是无所不能,下了一盘无比巨大的棋,主人公都只是其中一个棋子和意外,但是正如很多反派,一直以来都是背着一个信念,并且这个所谓的信念是比较正义的,只是为了这个正义的信念和目标却做了各种丧尽天良的事情,说起来跟灭霸有点像,为了环保哈哈,相对来说感觉姬远玄也只是个最大牌的工具人,或者说是中间人,深爱的妹妹冰夷也意外被蚩尤怒拿一血。

    -

    但是中间那个赤霞仙子一定要给烈烟石的心上锁,导致最后认不出来蚩尤,也间接导致了蚩尤被杀,如果不考虑最后情节或者推动故事的需求,这个还是我很讨厌的,有点类似于《驴得水》里那个校长,看着貌似是个正常的,做的事情也是正派,但是其实是害人不浅,即使南阳仙子因此被抛进了火山,那也是有贱人在那挑食,并且赤松子是赤飚怒的儿子,烈烟石跟蚩尤又没这层关系,就很像倚天屠龙记里的灭绝师太和极品家丁里的那个玉德仙坊的院主,后者还好一些,前者几乎就是导致周芷若一生悲剧的始作俑者,自己偏执的善恶观,还要给徒弟灌输如此恶毒的理念和让她立下像紧箍咒似的誓言,在人一生中本来就有很多不能如愿的,又被最亲最尊敬的人下了这样的紧箍咒,人生的不幸也加倍了。

    -

    似乎习惯了总要有个总结的,想说的应该是我觉得这些剧也好,书也好,我觉得最坏的人可能是大部分人眼中的一些次要人物,或者至少大 boss 才是最坏的人,当然这个坏也不是严格的二分法,只是我觉得最让我觉得负面的人物,这些人可能看起来情景出现的不多,只是说了很少的话,做了很少的事,但是在我看来却做了最大的恶。

    + 聊聊那些加塞狗 + /2021/01/17/%E8%81%8A%E8%81%8A%E9%82%A3%E4%BA%9B%E5%8A%A0%E5%A1%9E%E7%8B%97/ + 今天真的是被气得不轻,情况是碰到一个有 70 多秒的直行红灯,然后直行就排了很长的队,但是左转车道没车,就有好几辆车占着左转车道,准备往直行车道插队加塞,一般这种加塞的,会挑个不太计较的,如果前面一辆不让的话就再等等,我因为赶着回家,就不想让,结果那辆车几次车头直接往里冲,当时怒气值基本已经蓄满了,我真的是分毫都不想让,如果路上都是让着这种人的,那么这种情况只会越来越严重,我理解的这种心态,就赌你怕麻烦,多一事不如少一事,结果就是每次都能顺利插队加塞,其实延伸到我们社会中的种种实质性的排队或者等同于排队的情况,都已经有这种惯有思维,一方面这种不符合规则,可能在严重程度上容易被很多人所忽视,基本上已经被很多人当成是“合理”行为,另一方面,对于这些“微小”的违规行为,本身管理层面也基本没有想要管的意思,就更多的成为了纵容这些行为的导火索,并且大多数人都是想着如果不让,发生点小剐小蹭的要浪费很多时间精力来处理,甚至会觉得会被别人觉得自己太小气等等,诸多内外成本结合起来,会真的去硬刚的可能少之又少了,这样也就让更多的人觉得这种行为是被默许的,再举个非常小的例子,以我们公司疫情期间的盒饭发放为例,有两个比较“有意思”的事情,第一个就是因为疫情,本来是让排队要间隔一米,但是可能除了我比较怕死会跟前面的人保持点距离基本没别人会不挨着前面的人,甚至我跟我前面的人保持点距离,后面的同学会推着我让我上去;第二个是关于拿饭,这么多人排着队拿饭,然后有部分同学,一个人拿好几份,帮组里其他人的都拿了,有些甚至一个人拿十份,假如这个盒饭发放是说明了可以按部门直接全领了那就没啥问题,但是当时的状况是个人排队领自己的那一份,如果一个同学直接帮着组里十几个人都拿了,后面排队的人是什么感受呢,甚至有些是看到队伍排长了,就找队伍里自己认识的比较靠前的人说你帮我也拿一份,其实作为我这个比较按规矩办事的“愣头青”来说,我是比较不能接受这两件小事里的行为的,再往下说可能就有点偏激了,先说到这~

    ]]>
    生活 + 开车 生活 - 看书 + 开车 + 加塞 + 糟心事 + 规则
    - 闲聊下乘公交的用户体验 - /2021/02/28/%E9%97%B2%E8%81%8A%E4%B8%8B%E4%B9%98%E5%85%AC%E4%BA%A4%E7%9A%84%E7%94%A8%E6%88%B7%E4%BD%93%E9%AA%8C/ - 新年开工开车来杭州,因为没有车位加限行今天来就没开车来了,从东站做公交回住的地方,这班神奇的车我之前也吐槽过了,有神奇的乘客和神奇的司机,因为基本上这班车是从我毕业就开始乘了,所以也算是比较熟悉了,以前总体感觉不太好的是乘坐时间太长了,不过这个也不能怪车,是我自己住得远(离东站),后来住到了现在的地方,也算是直达,并且 LD 比较喜欢直达的,不爱更快却要换乘的地铁,所以坐的频率比较高,也说过前面那些比较气人的乘客,自己不好好戴口罩,反而联合一起上车的乘客诽谤司机,说他要吃人了要打人了,也正是这个司机比较有意思,上车就让戴好口罩,还给大家讲,哪里哪里又有疫情了,我觉得其实这个司机还是不错的,特殊时期,对于这种公共交通,这样的确是比较负责任的做法,只是说话方式,语气这个因人而异,他也不是来伺候人的,而且这么一大车人,说了一遍不行,再说一遍,三遍以上了,嗓门大一点也属于正常的人的行为。
    还是说回今天要说的,今天这位司机我看着跟前面说的那位有点像,因为上车的时候比较暗没看清脸,主要原因是这位司机开车比较猛,比较急,然后车上因为这个时间点,比较多大学开学来的学生,拎着个行李箱,一开始是前面已经都站满了人,后面还有很多空位,因为后面没地方放行李箱,就因为这样前面站着的有几个就在说司机开慢点,结果司机貌似也没听进去,还是我行我素,过了会又有人说司机开稳一点,就在这个人说完没一会,停在红绿灯路口的车里,就有人问有没有垃圾桶,接着又让司机开门,说晕车太严重了,要下车,司机开了门,我望出去两个妹子下了车,好像在路边草丛吐了,前面开门下车的时候就有人说她们第一次来杭州,可能有点责怪司机开的不稳,也影响了杭州交通给新来杭州的人的感受,说完了事情经过,其实我有蛮多感触,对于杭州公交司机,我大概是大一来了没多久,陪室友去文三路买电脑就晕车,下车的时候在公交车站吐了,可能是从大学开始缺乏锻炼,又饮食不规律,更加容易晕车,大部分晕车我觉得都是我自己的原因,有时候是上车前吃太多了,或者早上起太早,没睡好,没吃东西,反正自己也是挺多原因的,说到司机的原因的话,我觉得可能这班车还算好的,最让我难受的还是上下班高峰的时候,因为经过的那条路是比较重要的主干道,路比较老比较窄,并且还有很多人行道,所以经常一脚油门连带着一脚刹车,真的很难受,这种算是我觉得真的是公交体验比较差的一点,但是这一点呢也不能完全怪公交司机,杭州的路政规划是很垃圾,没看错,是垃圾,所以总体结论是公交还行,主要是路政规划就是垃圾,包括这条主干道这么多人行道,并且两边都是老小区,老年人在上班高峰可能要买菜送娃或者其他事情,在通畅的情况下可能只需要六分钟的路程,有时候因为各种原因,半小时都开不完,扯开去一点,杭州的路,核心的高速说封就封,本来是高架可以直接通到城西,结果没造,到了路本已经很拥挤的时候开始来造隧道,各种破坏,隧道接高架的地方,无尽的加塞,对于我这样的小白司机来说真的是太恶心了,所以我一直想说的就是杭州这个地方房价领先基础设施十年,地铁,高架,高速通通不行,地面道路就更不行了。
    总结下,其实杭州的真正的公交体验差,应该还是路造成的,对于前面的那两位妹子来说,有可能是她们来自于公交司机都是开的特别稳,并且路况也很好的地方,也或者是我被虐习惯了🤦‍♂️

    + 聊聊部分公交车的设计bug + /2021/12/05/%E8%81%8A%E8%81%8A%E9%83%A8%E5%88%86%E5%85%AC%E4%BA%A4%E8%BD%A6%E7%9A%84%E8%AE%BE%E8%AE%A1bug/ + 今天惯例坐公交回住的地方,不小心撞了头,原因是我们想坐倒数第二排,然后LD 走在我后面,我就走到最后一排中间等着,但是最后一排是高一截的,等 LD 坐进去以后,我就往前走,结果撞到了车顶的扶手杆子的一端,差点撞昏了去,这里我觉得其实杆子长度应该短一点,不然从最后一排出来,还是有比较大概率因为没注意看而撞到头,特别是没注意看的情况,发力其实会比较大,一头撞上就会像我这样,眼前一黑,又痛得要死。
    还有一点就是座位设计了,先来看个图

    图里大致画了两条线,因为可能是轮胎还是什么原因,后排中间会有那么大的突起,但是看两条红线可以发现,靠近过道的座位边缘跟地面突起的边缘不是一样宽的,这样导致的结果就是坐着的时候有一个脚没地儿搁,要不就得侧着斜着坐,或者就是一个脚悬空,短程的可能还好,路程远一点还是比较难受的,特别是像我现在这样,大腿外侧有点难受的情况,就会更难受。
    虽然说这两个点,基本是屁用没有,但是我也是在自己这个博客说说,也当是个树洞了。

    ]]>
    生活 - 公交 生活 - 开车 - 加塞 - 糟心事 - 规则 公交 - 路政规划 - 基础设施 杭州 - 高速
    @@ -14463,30 +14406,74 @@ server.6=192.168< - 闲话篇-也算碰到了为老不尊和坏人变老了的典型案例 - /2022/05/22/%E9%97%B2%E8%AF%9D%E7%AF%87-%E4%B9%9F%E7%AE%97%E7%A2%B0%E5%88%B0%E4%BA%86%E4%B8%BA%E8%80%81%E4%B8%8D%E5%B0%8A%E5%92%8C%E5%9D%8F%E4%BA%BA%E5%8F%98%E8%80%81%E4%BA%86%E7%9A%84%E5%85%B8%E5%9E%8B%E6%A1%88%E4%BE%8B/ - 在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。

    -

    对对面这户人家的印象,就是对面的老头子经常是我出门上班去了他回来,看着他颤颤巍巍地走楼梯,我看到了都靠边走,而且有几次还听见好像是他儿子在说他,”年假这么大了,还是少出去吧”,说实话除了这次的事情,之前就有一次水管阀门爆裂了,算是有点交集,那次大概是去年冬天,天气已经很冷了,我们周日下午回来看到楼梯有点湿,但是没什么特别的异常就没怎么注意,到晚上洗完澡,楼下的邻居就来敲门,说我们门外的水表那一直在流水,出门一看真的是懵了,外面水表那在哗哗哗地流水,导致楼梯那就跟水帘洞一样,仔细看看是对面家的水表阀门那在漏水,我只能先用塑料袋包一下,然后大冬天(刚洗完澡)穿着凉拖跑下去找物业保安,走到一楼的时候发现水一直流到一楼了,楼梯上都是水流下来,五楼那是最惨的,感觉门框周边都浸透了,五楼的也是态度比较差的让我一定要把水弄好了,但是前面也说了谁是从对门那户的水表阀那出来的,理论上应该让对面的处理,结果我敲门敲了半天对面都没反应,想着我放着不管也不太好,就去找了物业保安,保安上来看了只能先把总阀关了,我也打电话给维修自来水管的,自来水公司的人过了会也是真的来修了,我那会是挺怕不来修,自来水公司的师傅到了以后拿开一看是对面那户的有个阀门估计是自己换上去的,跟我们这的完全不一样,看上去就比较劣质,师傅也挺气的,大晚上被叫过来,我又尝试着去敲门也还是没人应,也没办法,对面老人家我敲太响到时候出来说我吓到他们啥的,第二天去说也没现场了。

    -

    前面的这件事是个重要铺垫,前几天 LD 下班后把厨余垃圾套好袋子放在门口,打算等我下班了因为要去做核酸(hz 48 小时核酸)顺便带下去,结果到了七点多,说对面的老太太在那疯狂砸门了,LD 被吓到了不敢开门,老太太在外面一边砸门一边骂,“你们年轻人怎么素质这么差”(他们家也经常在门口放垃圾,我们刚来住的时候在楼梯转角他们就放这个废弃的洗衣机,每次走楼梯带点东西都要小心翼翼地走,不然都过不去,然后我赶紧赶回去,结果她听到我回家了,还特意开门继续骂,“你们年轻人怎么素质这么差,垃圾放在这里”,我说我们刚才放在这,打算待会做核酸的时候去扔掉,结果他们家老头,都已经没了牙齿,在那瞪大眼睛说,“你们早上就放在这了的,”我说是LD 刚才下班了放的,争论了一会,我说这个事情我们门口放了垃圾,这会我就去扔掉了,但是你们家老太太这么砸门总不太好,像之前门口水管爆掉了,我敲了门没人应,我也没要砸门一定把你们叫醒,结果老头老太说我们的水管从来没换过,不可能破的(其实到这,再往后说就没意思了,跟这么不要脸的人说多了也只是瞎扯),一会又回到这个垃圾的问题,那个老头说“你们昨天就放在这里了的”,睁着眼说瞎话可真是 666,感觉不是老太太拦着点他马上就要冲上来揍我了一样,事后我想想,这种情况我大概只能躺地上装死了,当这个事情发生之前我真的快把前面说的事情(水管阀坏了)给忘了,虽然这是理论上不该我来处理,除非是老头老太太请求我帮忙,这事后面我也从没说起过,本来完全没交集,对他们的是怎么样的人也没概念,总觉得年纪大了可能还比较心宽和蔼点,结果没想到就是一典型的坏人变老了,我说你们这么砸门,我老婆都被吓得不敢开门,结果对面老头老太太的儿子也出来了说,“我们就是敲下门,我母亲是机关单位退休的,所以肯定不会敲门很大声的,你老婆觉得吓到了是你们人生观价值观有问题”,听到这话我差点笑出来,连着两个可笑至极的脑残逻辑,无语他妈给无语开门,无语到家了。对门家我们之前有个印象就是因为我们都是顶楼,这边老小区以前都是把前后阳台包进来的,然后社区就来咨询大家的意见是不是统一把包进来的违建拆掉,还特地上来六楼跟他们说,结果对面的老头就说,“我要去住建局投诉你们”,本来这个事情是违法的,但是社区的意思也是征求各位业主的意见,结果感觉是社区上门强拆了一样,为老不尊,坏人变老了的典范了。

    + 这周末我又在老丈人家打了天小工 + /2020/08/30/%E8%BF%99%E5%91%A8%E6%9C%AB%E6%88%91%E5%8F%88%E5%9C%A8%E8%80%81%E4%B8%88%E4%BA%BA%E5%AE%B6%E6%89%93%E4%BA%86%E5%A4%A9%E5%B0%8F%E5%B7%A5/ + 因为活实在比较多,也不太好叫大工(活比较杂散),相比上一次我跟 LD 俩人晚起了一点,我真的是只要有事,早上就醒的很早,准备八点出发的,六点就醒了,然后想继续睡就一直做梦🤦‍♂️,差不多八点半多到的丈人家,他们应该已经干了有一会了,我们到了以后就分配给我撬地板的活,上次说的那个敲掉柜子的房间里,还铺着质地还不错的木地板,但是也不想要了,得撬掉重新铺。
    拿着撬棍和榔头就上楼去干了,浙江这几天的天气,最高温度一般 38、9,楼上那个房间也没风扇,有了也不能用,都是灰尘,撬了两下,我感觉我体内的水就像真气爆发一样变成汗炸了出来,眼睛全被汗糊住了,可能大部分人不太了解地板是怎么铺的,一般是在地面先铺一层混凝土,混凝土中间嵌进去规则的长条木条,然后真正的地板一块块的都是钉在那个木条上,用那种气枪钉和普通的钉子,并且块跟块之前还有一个木头的槽结构相互耦合,然后边缘的一圈在用较薄的木板将整个木地板封边(这些词都是我现造的),边缘的用的钉子会更多,所以那几下真的很用力,而且撬地板,得蹲下起来,如此反复,对于我这个体重快超过身高的中年人来说的确是非常大的挑战,接下来继续撬了几个,已经有种要虚脱晕倒的感觉了,及时去喝水擦了汗,又歇了一会,为啥一上来就这么拼呢,主要是因为那个房间丈人在干活的时候是直接看得到的🤦‍♂️,后来被 LD 一顿教育,本来就是去帮忙的,又不是专业做这个的,急啥。
    喝了水之后,又稍稍歇了一会,就开始继续撬了,本来觉得这个地板撬着好像还行,房间不大,没多久就撬完了,撬完之后喝了点饮料(补充点糖分,早餐吃得少,有点低血糖),然后看到 LD 在撬下面的木条了,这个动作开始了那天最大的经验值收集行动,前面说了这个木条一般是跟混凝土一块铺上去的,但是谁也没想到,这个混凝土铺上去的时候竟然处理的这么随意,根本没考虑跟下面的贴合,所以撬木条的时候直接把木条跟木条中间大块大块的混凝土一块撬起来了,想想那重量,于是我这靠蛮力干活的,就用力把木条带着混凝土一块撬了起来,还沾沾自喜,但是发现结果是撬起来一块之后,体力值瞬间归零,上一篇我也提到了,其实干这类活也是很有技巧性的,但是上次的是我没学会,可能需要花时间学的,但是这次是LD 用她的纤细胳膊教会我的,我在撬的时候,屏住一口气,双手用力,起,大概是吃好几口奶的力气都用出来了,但是 LD 在我休息的时候,慢慢悠悠的,先把撬棍挤到木条或者混凝土跟下层的缝里,然后往下垫一小块混凝土碎石,然后轻轻松松的扳两下,就撬开了,亏我高中的时候引以为傲的物理成绩,作为物理课代表,这么浅显易懂的杠杆原理都完全不会用到生活里,后面在用这个技巧撬的过程中,真的觉得自己蠢到家了,当然在明白了用点杠杆原理之后,撬地板的活就变得慢慢悠悠,悠哉悠哉的了(其实还是很热的,披着毛巾擦眼睛)。
    上午的活差不多完了,后面就是把撬出来的混凝土和地板条丢下去,地上铺着不用了的被子,然后就是午饭和午休环节了,午饭换了一家快餐,味道非常可以,下午的活就比较单调了,帮忙清理了上去扔下来的混凝土碎块跟木条,然后稍微打扫了下,老丈人就让我们回家了,接着上次说的,还是觉得比跑步啥的消耗大太多了,那汗流的,一口就能喝完一瓶 500 毫升左右的矿泉水。

    +]]>
    + + 生活 + 运动 + 跑步 + 干活 + + + 生活 + 运动 + 减肥 + 跑步 + 干活 + +
    + + 重看了下《蛮荒记》说说感受 + /2021/10/10/%E9%87%8D%E7%9C%8B%E4%BA%86%E4%B8%8B%E3%80%8A%E8%9B%AE%E8%8D%92%E8%AE%B0%E3%80%8B%E8%AF%B4%E8%AF%B4%E6%84%9F%E5%8F%97/ + 周末把《蛮荒记》看完了,前面是发现微信读书有《搜神记》和《蛮荒记》,但是《搜神记》看了会发现很多都是跳段了,不知道为啥,貌似也没什么少儿不宜的情节,所以就上网找了原版来看,为什么看这个呢,主要还是高中的时候看过,觉得写得很不错,属于那时候的玄幻小说里的独一档,基于山海经创造了一个半架空的大荒宇宙,五族帝尊,人物名都是听说过的,而且又能契合部分历史,整个故事布局非常宏大,并且情节矛盾埋得很深,这里就不对具体情节作介绍了,只是聊聊对书中的一些人物和情节的看法感受。

    +

    乌丝兰玛是个贯穿两部,甚至在蛮荒的最后还要再搞事情,极其坚定的自以为是的大 boss,其实除了最后被我们的主人公打败,前面几乎就是无所不能,下了一盘无比巨大的棋,主人公都只是其中一个棋子和意外,但是正如很多反派,一直以来都是背着一个信念,并且这个所谓的信念是比较正义的,只是为了这个正义的信念和目标却做了各种丧尽天良的事情,说起来跟灭霸有点像,为了环保哈哈,相对来说感觉姬远玄也只是个最大牌的工具人,或者说是中间人,深爱的妹妹冰夷也意外被蚩尤怒拿一血。

    +

    但是中间那个赤霞仙子一定要给烈烟石的心上锁,导致最后认不出来蚩尤,也间接导致了蚩尤被杀,如果不考虑最后情节或者推动故事的需求,这个还是我很讨厌的,有点类似于《驴得水》里那个校长,看着貌似是个正常的,做的事情也是正派,但是其实是害人不浅,即使南阳仙子因此被抛进了火山,那也是有贱人在那挑食,并且赤松子是赤飚怒的儿子,烈烟石跟蚩尤又没这层关系,就很像倚天屠龙记里的灭绝师太和极品家丁里的那个玉德仙坊的院主,后者还好一些,前者几乎就是导致周芷若一生悲剧的始作俑者,自己偏执的善恶观,还要给徒弟灌输如此恶毒的理念和让她立下像紧箍咒似的誓言,在人一生中本来就有很多不能如愿的,又被最亲最尊敬的人下了这样的紧箍咒,人生的不幸也加倍了。

    +

    似乎习惯了总要有个总结的,想说的应该是我觉得这些剧也好,书也好,我觉得最坏的人可能是大部分人眼中的一些次要人物,或者至少大 boss 才是最坏的人,当然这个坏也不是严格的二分法,只是我觉得最让我觉得负面的人物,这些人可能看起来情景出现的不多,只是说了很少的话,做了很少的事,但是在我看来却做了最大的恶。

    ]]>
    生活 生活 + 看书
    - 难得的大扫除 - /2022/04/10/%E9%9A%BE%E5%BE%97%E7%9A%84%E5%A4%A7%E6%89%AB%E9%99%A4/ - 因为房东要来续签合同,记得之前她说要来看看,后来一直都没来成,一方面我们没打扫过也不想被看到,小房子东西从搬进来以后越来越多,虽然不是脏乱差,但也觉得有点不满意干净状态,这里不得不感叹房东家的有钱程度,买了房子自己都没进房子看过,买来只是为了个学籍,去年前房东把房子卖给新房东后,我们还是比较担心会要换房子了,这里其实是个我们在乎的优点略大于缺点的小房子,面积比较小,但是交通便利以及上下班通勤,周边配套也还不错,有个比较大的菜市场,虽然不常去,因为不太会挑不会还价,还是主要去附近一公里左右的超市,可以安静地挑菜,但是说实在的菜场的菜还是比超市新鲜一些。
    大扫除说实在的住在这边以后就没有一次真正意义上的大扫除,因为平时也有在正常打扫,只有偶尔的厨房煤气灶和厕所专门清理下,平时扫地拖地都有做,但是因为说实在的这房子也比较老了,地板什么的都有明显的老化,表面上的油漆都已经被磨损掉了,一些污渍很难拖干净,而且包括厨房和厕所的瓷砖都是纹路特别多,加上磨损,基本是污渍很多,特别是厨房的,又有油渍,我们搬进来的时候厨房的地就已经不太干净了,还有一点就是虽然不是在乡下的房子,但是旁边有两条主干道,一般只要开着窗没几天就灰尘积起来了,公司的电脑在家两天不到就一层灰,而且有些灰在地上时间久一点就会变成那种棉絮状的,看起来就会觉得更脏,并且地板我们平时就是扫一下,然后拖一下没明显的脏东西跟大灰尘就好了,有一些脏的就很难拖干净。
    这次的算是整体的大扫除,把柜子,桌子,茶几台,窗边的灰尘都要擦掉,有一些角落还是有蛮多灰尘,当然特别难受的就是电脑那些接口,线缆上的,都杂糅在一块,如果要全都解开了理顺了还是比较麻烦,并且得断电,所以还是尽力清理,但没有全弄开了(我承认我是在偷懒,这里得说下清理了键盘,键盘之前都是放着用,也没盖住,按键缝里就很容易积灰也很难清理,这次索性直接把键全拔了,但是里面的清理也还是挺麻烦,因为不是平板一块,而且还有小孔,有些缝隙也比较难擦进去,只能慢慢地用牙线棒裹着抹布还有棉签擦一下,然后把键帽用洗手液什么的都擦一下洗洗干净,最后晾干了装好感觉就是一把新键盘了,后面主要是拖地了,这次最神奇的就是这个拖地,本来我就跟 LD 吹牛说拖地我是专业的,从小拖到大,有些地板缝边上的污渍,我又是用力来回拖,再用脚踩着拖,还是能把一些原来以为拖不掉的污渍给拖干净了,但是后来的厨房就比较难,用洗洁精来回拖感觉一点都起不来,可能是污渍积了太久了,一开始都想要放弃了,就打算拖干就好了,后来突然看到旁边有个洗衣服的板刷,结果竟然能刷起来,这样就停不下来了,说累是真的非常累,感觉刷一块瓷砖就要休息一会,但是整体刷完之后就是焕然一新的赶脚,简直太有成就感了。

    + 闲聊下乘公交的用户体验 + /2021/02/28/%E9%97%B2%E8%81%8A%E4%B8%8B%E4%B9%98%E5%85%AC%E4%BA%A4%E7%9A%84%E7%94%A8%E6%88%B7%E4%BD%93%E9%AA%8C/ + 新年开工开车来杭州,因为没有车位加限行今天来就没开车来了,从东站做公交回住的地方,这班神奇的车我之前也吐槽过了,有神奇的乘客和神奇的司机,因为基本上这班车是从我毕业就开始乘了,所以也算是比较熟悉了,以前总体感觉不太好的是乘坐时间太长了,不过这个也不能怪车,是我自己住得远(离东站),后来住到了现在的地方,也算是直达,并且 LD 比较喜欢直达的,不爱更快却要换乘的地铁,所以坐的频率比较高,也说过前面那些比较气人的乘客,自己不好好戴口罩,反而联合一起上车的乘客诽谤司机,说他要吃人了要打人了,也正是这个司机比较有意思,上车就让戴好口罩,还给大家讲,哪里哪里又有疫情了,我觉得其实这个司机还是不错的,特殊时期,对于这种公共交通,这样的确是比较负责任的做法,只是说话方式,语气这个因人而异,他也不是来伺候人的,而且这么一大车人,说了一遍不行,再说一遍,三遍以上了,嗓门大一点也属于正常的人的行为。
    还是说回今天要说的,今天这位司机我看着跟前面说的那位有点像,因为上车的时候比较暗没看清脸,主要原因是这位司机开车比较猛,比较急,然后车上因为这个时间点,比较多大学开学来的学生,拎着个行李箱,一开始是前面已经都站满了人,后面还有很多空位,因为后面没地方放行李箱,就因为这样前面站着的有几个就在说司机开慢点,结果司机貌似也没听进去,还是我行我素,过了会又有人说司机开稳一点,就在这个人说完没一会,停在红绿灯路口的车里,就有人问有没有垃圾桶,接着又让司机开门,说晕车太严重了,要下车,司机开了门,我望出去两个妹子下了车,好像在路边草丛吐了,前面开门下车的时候就有人说她们第一次来杭州,可能有点责怪司机开的不稳,也影响了杭州交通给新来杭州的人的感受,说完了事情经过,其实我有蛮多感触,对于杭州公交司机,我大概是大一来了没多久,陪室友去文三路买电脑就晕车,下车的时候在公交车站吐了,可能是从大学开始缺乏锻炼,又饮食不规律,更加容易晕车,大部分晕车我觉得都是我自己的原因,有时候是上车前吃太多了,或者早上起太早,没睡好,没吃东西,反正自己也是挺多原因的,说到司机的原因的话,我觉得可能这班车还算好的,最让我难受的还是上下班高峰的时候,因为经过的那条路是比较重要的主干道,路比较老比较窄,并且还有很多人行道,所以经常一脚油门连带着一脚刹车,真的很难受,这种算是我觉得真的是公交体验比较差的一点,但是这一点呢也不能完全怪公交司机,杭州的路政规划是很垃圾,没看错,是垃圾,所以总体结论是公交还行,主要是路政规划就是垃圾,包括这条主干道这么多人行道,并且两边都是老小区,老年人在上班高峰可能要买菜送娃或者其他事情,在通畅的情况下可能只需要六分钟的路程,有时候因为各种原因,半小时都开不完,扯开去一点,杭州的路,核心的高速说封就封,本来是高架可以直接通到城西,结果没造,到了路本已经很拥挤的时候开始来造隧道,各种破坏,隧道接高架的地方,无尽的加塞,对于我这样的小白司机来说真的是太恶心了,所以我一直想说的就是杭州这个地方房价领先基础设施十年,地铁,高架,高速通通不行,地面道路就更不行了。
    总结下,其实杭州的真正的公交体验差,应该还是路造成的,对于前面的那两位妹子来说,有可能是她们来自于公交司机都是开的特别稳,并且路况也很好的地方,也或者是我被虐习惯了🤦‍♂️

    +]]>
    + + 生活 + 公交 + + + 生活 + 开车 + 加塞 + 糟心事 + 规则 + 公交 + 路政规划 + 基础设施 + 杭州 + 高速 + +
    + + 闲话篇-也算碰到了为老不尊和坏人变老了的典型案例 + /2022/05/22/%E9%97%B2%E8%AF%9D%E7%AF%87-%E4%B9%9F%E7%AE%97%E7%A2%B0%E5%88%B0%E4%BA%86%E4%B8%BA%E8%80%81%E4%B8%8D%E5%B0%8A%E5%92%8C%E5%9D%8F%E4%BA%BA%E5%8F%98%E8%80%81%E4%BA%86%E7%9A%84%E5%85%B8%E5%9E%8B%E6%A1%88%E4%BE%8B/ + 在目前的房子也差不多租了四五年了,楼下邻居换了两拨了,我们这栋楼装修了不知道多少次,因为是学区的原因,房子交易的频率还是比较高的,不过比较神奇的我们对门的没换过,而且一直也没什么交集(除了后面说的水管爆裂),就进出的时候偶尔看到应该是住着一对老夫妻,感觉年纪也有个七八十了。

    +

    对对面这户人家的印象,就是对面的老头子经常是我出门上班去了他回来,看着他颤颤巍巍地走楼梯,我看到了都靠边走,而且有几次还听见好像是他儿子在说他,”年假这么大了,还是少出去吧”,说实话除了这次的事情,之前就有一次水管阀门爆裂了,算是有点交集,那次大概是去年冬天,天气已经很冷了,我们周日下午回来看到楼梯有点湿,但是没什么特别的异常就没怎么注意,到晚上洗完澡,楼下的邻居就来敲门,说我们门外的水表那一直在流水,出门一看真的是懵了,外面水表那在哗哗哗地流水,导致楼梯那就跟水帘洞一样,仔细看看是对面家的水表阀门那在漏水,我只能先用塑料袋包一下,然后大冬天(刚洗完澡)穿着凉拖跑下去找物业保安,走到一楼的时候发现水一直流到一楼了,楼梯上都是水流下来,五楼那是最惨的,感觉门框周边都浸透了,五楼的也是态度比较差的让我一定要把水弄好了,但是前面也说了谁是从对门那户的水表阀那出来的,理论上应该让对面的处理,结果我敲门敲了半天对面都没反应,想着我放着不管也不太好,就去找了物业保安,保安上来看了只能先把总阀关了,我也打电话给维修自来水管的,自来水公司的人过了会也是真的来修了,我那会是挺怕不来修,自来水公司的师傅到了以后拿开一看是对面那户的有个阀门估计是自己换上去的,跟我们这的完全不一样,看上去就比较劣质,师傅也挺气的,大晚上被叫过来,我又尝试着去敲门也还是没人应,也没办法,对面老人家我敲太响到时候出来说我吓到他们啥的,第二天去说也没现场了。

    +

    前面的这件事是个重要铺垫,前几天 LD 下班后把厨余垃圾套好袋子放在门口,打算等我下班了因为要去做核酸(hz 48 小时核酸)顺便带下去,结果到了七点多,说对面的老太太在那疯狂砸门了,LD 被吓到了不敢开门,老太太在外面一边砸门一边骂,“你们年轻人怎么素质这么差”(他们家也经常在门口放垃圾,我们刚来住的时候在楼梯转角他们就放这个废弃的洗衣机,每次走楼梯带点东西都要小心翼翼地走,不然都过不去,然后我赶紧赶回去,结果她听到我回家了,还特意开门继续骂,“你们年轻人怎么素质这么差,垃圾放在这里”,我说我们刚才放在这,打算待会做核酸的时候去扔掉,结果他们家老头,都已经没了牙齿,在那瞪大眼睛说,“你们早上就放在这了的,”我说是LD 刚才下班了放的,争论了一会,我说这个事情我们门口放了垃圾,这会我就去扔掉了,但是你们家老太太这么砸门总不太好,像之前门口水管爆掉了,我敲了门没人应,我也没要砸门一定把你们叫醒,结果老头老太说我们的水管从来没换过,不可能破的(其实到这,再往后说就没意思了,跟这么不要脸的人说多了也只是瞎扯),一会又回到这个垃圾的问题,那个老头说“你们昨天就放在这里了的”,睁着眼说瞎话可真是 666,感觉不是老太太拦着点他马上就要冲上来揍我了一样,事后我想想,这种情况我大概只能躺地上装死了,当这个事情发生之前我真的快把前面说的事情(水管阀坏了)给忘了,虽然这是理论上不该我来处理,除非是老头老太太请求我帮忙,这事后面我也从没说起过,本来完全没交集,对他们的是怎么样的人也没概念,总觉得年纪大了可能还比较心宽和蔼点,结果没想到就是一典型的坏人变老了,我说你们这么砸门,我老婆都被吓得不敢开门,结果对面老头老太太的儿子也出来了说,“我们就是敲下门,我母亲是机关单位退休的,所以肯定不会敲门很大声的,你老婆觉得吓到了是你们人生观价值观有问题”,听到这话我差点笑出来,连着两个可笑至极的脑残逻辑,无语他妈给无语开门,无语到家了。对门家我们之前有个印象就是因为我们都是顶楼,这边老小区以前都是把前后阳台包进来的,然后社区就来咨询大家的意见是不是统一把包进来的违建拆掉,还特地上来六楼跟他们说,结果对面的老头就说,“我要去住建局投诉你们”,本来这个事情是违法的,但是社区的意思也是征求各位业主的意见,结果感觉是社区上门强拆了一样,为老不尊,坏人变老了的典范了。

    ]]>
    生活 生活 - 大扫除
    @@ -14504,4 +14491,17 @@ server.6=192.168< 生活 + + 难得的大扫除 + /2022/04/10/%E9%9A%BE%E5%BE%97%E7%9A%84%E5%A4%A7%E6%89%AB%E9%99%A4/ + 因为房东要来续签合同,记得之前她说要来看看,后来一直都没来成,一方面我们没打扫过也不想被看到,小房子东西从搬进来以后越来越多,虽然不是脏乱差,但也觉得有点不满意干净状态,这里不得不感叹房东家的有钱程度,买了房子自己都没进房子看过,买来只是为了个学籍,去年前房东把房子卖给新房东后,我们还是比较担心会要换房子了,这里其实是个我们在乎的优点略大于缺点的小房子,面积比较小,但是交通便利以及上下班通勤,周边配套也还不错,有个比较大的菜市场,虽然不常去,因为不太会挑不会还价,还是主要去附近一公里左右的超市,可以安静地挑菜,但是说实在的菜场的菜还是比超市新鲜一些。
    大扫除说实在的住在这边以后就没有一次真正意义上的大扫除,因为平时也有在正常打扫,只有偶尔的厨房煤气灶和厕所专门清理下,平时扫地拖地都有做,但是因为说实在的这房子也比较老了,地板什么的都有明显的老化,表面上的油漆都已经被磨损掉了,一些污渍很难拖干净,而且包括厨房和厕所的瓷砖都是纹路特别多,加上磨损,基本是污渍很多,特别是厨房的,又有油渍,我们搬进来的时候厨房的地就已经不太干净了,还有一点就是虽然不是在乡下的房子,但是旁边有两条主干道,一般只要开着窗没几天就灰尘积起来了,公司的电脑在家两天不到就一层灰,而且有些灰在地上时间久一点就会变成那种棉絮状的,看起来就会觉得更脏,并且地板我们平时就是扫一下,然后拖一下没明显的脏东西跟大灰尘就好了,有一些脏的就很难拖干净。
    这次的算是整体的大扫除,把柜子,桌子,茶几台,窗边的灰尘都要擦掉,有一些角落还是有蛮多灰尘,当然特别难受的就是电脑那些接口,线缆上的,都杂糅在一块,如果要全都解开了理顺了还是比较麻烦,并且得断电,所以还是尽力清理,但没有全弄开了(我承认我是在偷懒,这里得说下清理了键盘,键盘之前都是放着用,也没盖住,按键缝里就很容易积灰也很难清理,这次索性直接把键全拔了,但是里面的清理也还是挺麻烦,因为不是平板一块,而且还有小孔,有些缝隙也比较难擦进去,只能慢慢地用牙线棒裹着抹布还有棉签擦一下,然后把键帽用洗手液什么的都擦一下洗洗干净,最后晾干了装好感觉就是一把新键盘了,后面主要是拖地了,这次最神奇的就是这个拖地,本来我就跟 LD 吹牛说拖地我是专业的,从小拖到大,有些地板缝边上的污渍,我又是用力来回拖,再用脚踩着拖,还是能把一些原来以为拖不掉的污渍给拖干净了,但是后来的厨房就比较难,用洗洁精来回拖感觉一点都起不来,可能是污渍积了太久了,一开始都想要放弃了,就打算拖干就好了,后来突然看到旁边有个洗衣服的板刷,结果竟然能刷起来,这样就停不下来了,说累是真的非常累,感觉刷一块瓷砖就要休息一会,但是整体刷完之后就是焕然一新的赶脚,简直太有成就感了。

    +]]>
    + + 生活 + + + 生活 + 大扫除 + +
    diff --git a/sitemap.xml b/sitemap.xml index 878f453163..7fda7ecc4b 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1667,77 +1667,77 @@ - https://nicksxs.me/tags/c/ + https://nicksxs.me/tags/%E6%8A%80%E6%9C%AF/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%B9%B6%E5%8F%91/ + https://nicksxs.me/tags/%E8%AF%BB%E4%B9%A6/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/j-u-c/ + https://nicksxs.me/tags/c/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/aqs/ + https://nicksxs.me/tags/%E5%B9%B6%E5%8F%91/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/condition/ + https://nicksxs.me/tags/j-u-c/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/await/ + https://nicksxs.me/tags/aqs/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/signal/ + https://nicksxs.me/tags/condition/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/lock/ + https://nicksxs.me/tags/await/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/unlock/ + https://nicksxs.me/tags/signal/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E6%8A%80%E6%9C%AF/ + https://nicksxs.me/tags/lock/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E8%AF%BB%E4%B9%A6/ + https://nicksxs.me/tags/unlock/ 2022-06-11 weekly 0.2 @@ -1806,6 +1806,13 @@ 0.2 + + https://nicksxs.me/tags/Disruptor/ + 2022-06-11 + weekly + 0.2 + + https://nicksxs.me/tags/Dubbo/ 2022-06-11 @@ -1898,42 +1905,42 @@ - https://nicksxs.me/tags/Disruptor/ + https://nicksxs.me/tags/%E9%80%92%E5%BD%92/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E9%80%92%E5%BD%92/ + https://nicksxs.me/tags/Preorder-Traversal/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/Preorder-Traversal/ + https://nicksxs.me/tags/Inorder-Traversal/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/Inorder-Traversal/ + https://nicksxs.me/tags/%E5%89%8D%E5%BA%8F/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%89%8D%E5%BA%8F/ + https://nicksxs.me/tags/%E4%B8%AD%E5%BA%8F/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E4%B8%AD%E5%BA%8F/ + https://nicksxs.me/tags/Print-FooBar-Alternately/ 2022-06-11 weekly 0.2 @@ -1988,6 +1995,13 @@ 0.2 + + https://nicksxs.me/tags/string/ + 2022-06-11 + weekly + 0.2 + + https://nicksxs.me/tags/Intersection-of-Two-Arrays/ 2022-06-11 @@ -1996,7 +2010,7 @@ - https://nicksxs.me/tags/string/ + https://nicksxs.me/tags/Median-of-Two-Sorted-Arrays/ 2022-06-11 weekly 0.2 @@ -2052,35 +2066,35 @@ - https://nicksxs.me/tags/linux/ + https://nicksxs.me/tags/Remove-Duplicates-from-Sorted-List/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/grep/ + https://nicksxs.me/tags/linux/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E8%BD%AC%E4%B9%89/ + https://nicksxs.me/tags/grep/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/mfc/ + https://nicksxs.me/tags/%E8%BD%AC%E4%B9%89/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/Median-of-Two-Sorted-Arrays/ + https://nicksxs.me/tags/mfc/ 2022-06-11 weekly 0.2 @@ -2121,13 +2135,6 @@ 0.2 - - https://nicksxs.me/tags/Print-FooBar-Alternately/ - 2022-06-11 - weekly - 0.2 - - https://nicksxs.me/tags/hadoop/ 2022-06-11 @@ -2177,13 +2184,6 @@ 0.2 - - https://nicksxs.me/tags/Remove-Duplicates-from-Sorted-List/ - 2022-06-11 - weekly - 0.2 - - https://nicksxs.me/tags/Dockerfile/ 2022-06-11 @@ -2262,14 +2262,21 @@ - https://nicksxs.me/tags/openresty/ + https://nicksxs.me/tags/nginx/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/nginx/ + https://nicksxs.me/tags/%E6%97%A5%E5%BF%97/ + 2022-06-11 + weekly + 0.2 + + + + https://nicksxs.me/tags/openresty/ 2022-06-11 weekly 0.2 @@ -2345,13 +2352,6 @@ 0.2 - - https://nicksxs.me/tags/%E6%97%A5%E5%BF%97/ - 2022-06-11 - weekly - 0.2 - - https://nicksxs.me/tags/Rust/ 2022-06-11 @@ -2437,42 +2437,42 @@ - https://nicksxs.me/tags/WordPress/ + https://nicksxs.me/tags/gc/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ + https://nicksxs.me/tags/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/gc/ + https://nicksxs.me/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86/ + https://nicksxs.me/tags/jvm/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/ + https://nicksxs.me/tags/WordPress/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/jvm/ + https://nicksxs.me/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ 2022-06-11 weekly 0.2 @@ -2654,42 +2654,42 @@ - https://nicksxs.me/tags/%E8%BF%90%E5%8A%A8/ + https://nicksxs.me/tags/git/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%87%8F%E8%82%A5/ + https://nicksxs.me/tags/scp/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E8%B7%91%E6%AD%A5/ + https://nicksxs.me/tags/%E8%BF%90%E5%8A%A8/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%B9%B2%E6%B4%BB/ + https://nicksxs.me/tags/%E5%87%8F%E8%82%A5/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/scp/ + https://nicksxs.me/tags/%E8%B7%91%E6%AD%A5/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/git/ + https://nicksxs.me/tags/%E5%B9%B2%E6%B4%BB/ 2022-06-11 weekly 0.2 @@ -3165,42 +3165,42 @@ - https://nicksxs.me/tags/%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D/ + https://nicksxs.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/AutoConfiguration/ + https://nicksxs.me/tags/Design-Patterns/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/ + https://nicksxs.me/tags/%E5%8D%95%E4%BE%8B/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/Design-Patterns/ + https://nicksxs.me/tags/Singleton/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E5%8D%95%E4%BE%8B/ + https://nicksxs.me/tags/%E8%87%AA%E5%8A%A8%E8%A3%85%E9%85%8D/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/Singleton/ + https://nicksxs.me/tags/AutoConfiguration/ 2022-06-11 weekly 0.2 @@ -3459,28 +3459,28 @@ - https://nicksxs.me/tags/%E7%9C%8B%E4%B9%A6/ + https://nicksxs.me/tags/stream/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/%E9%AB%98%E9%80%9F/ + https://nicksxs.me/tags/zookeeper/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/stream/ + https://nicksxs.me/tags/%E7%9C%8B%E4%B9%A6/ 2022-06-11 weekly 0.2 - https://nicksxs.me/tags/zookeeper/ + https://nicksxs.me/tags/%E9%AB%98%E9%80%9F/ 2022-06-11 weekly 0.2 @@ -3503,28 +3503,28 @@ - https://nicksxs.me/categories/Java/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + https://nicksxs.me/categories/Java/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/JVM/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/leetcode/ + https://nicksxs.me/categories/Java/JVM/ 2022-06-11 weekly 0.2 @@ -3538,28 +3538,28 @@ - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ + https://nicksxs.me/categories/Java/leetcode/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/GC/ + https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Binary-Tree/ + https://nicksxs.me/categories/Java/GC/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + https://nicksxs.me/categories/Binary-Tree/ 2022-06-11 weekly 0.2 @@ -3573,28 +3573,28 @@ - https://nicksxs.me/categories/Linked-List/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2019/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2019/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/ + https://nicksxs.me/categories/Linked-List/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/C/ + https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ 2022-06-11 weekly 0.2 @@ -3608,35 +3608,35 @@ - https://nicksxs.me/categories/leetcode/java/ + https://nicksxs.me/categories/C/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/2020/ + https://nicksxs.me/categories/java/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Apollo/ + https://nicksxs.me/categories/leetcode/java/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E6%9D%91%E4%B8%8A%E6%98%A5%E6%A0%91/ + https://nicksxs.me/categories/Java/Apollo/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/java/ + https://nicksxs.me/categories/%E8%AF%BB%E5%90%8E%E6%84%9F/%E6%9D%91%E4%B8%8A%E6%98%A5%E6%A0%91/ 2022-06-11 weekly 0.2 @@ -3650,21 +3650,21 @@ - https://nicksxs.me/categories/Java/Dubbo/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/2020/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Filter/ + https://nicksxs.me/categories/Java/Dubbo/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/2020/ + https://nicksxs.me/categories/Filter/ 2022-06-11 weekly 0.2 @@ -3678,49 +3678,49 @@ - https://nicksxs.me/categories/leetcode/java/Binary-Tree/ + https://nicksxs.me/categories/DP/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/DP/ + https://nicksxs.me/categories/stack/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/stack/ + https://nicksxs.me/categories/Java/leetcode/Lowest-Common-Ancestor-of-a-Binary-Tree/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/leetcode/Lowest-Common-Ancestor-of-a-Binary-Tree/ + https://nicksxs.me/categories/linked-list/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Apollo/value/ + https://nicksxs.me/categories/leetcode/java/Linked-List/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/linked-list/ + https://nicksxs.me/categories/leetcode/java/Binary-Tree/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/Linked-List/ + https://nicksxs.me/categories/%E5%AD%97%E7%AC%A6%E4%B8%B2-online/ 2022-06-11 weekly 0.2 @@ -3734,7 +3734,7 @@ - https://nicksxs.me/categories/%E5%AD%97%E7%AC%A6%E4%B8%B2-online/ + https://nicksxs.me/categories/Java/Apollo/value/ 2022-06-11 weekly 0.2 @@ -3797,35 +3797,35 @@ - https://nicksxs.me/categories/leetcode/java/Binary-Tree/DFS/ + https://nicksxs.me/categories/Java/Mybatis/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Mybatis/ + https://nicksxs.me/categories/nginx/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/nginx/ + https://nicksxs.me/categories/leetcode/java/DP/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/DP/ + https://nicksxs.me/categories/php/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/php/ + https://nicksxs.me/categories/leetcode/java/stack/ 2022-06-11 weekly 0.2 @@ -3839,14 +3839,14 @@ - https://nicksxs.me/categories/leetcode/java/stack/ + https://nicksxs.me/categories/leetcode/java/linked-list/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/linked-list/ + https://nicksxs.me/categories/leetcode/java/Binary-Tree/DFS/ 2022-06-11 weekly 0.2 @@ -3860,21 +3860,21 @@ - https://nicksxs.me/categories/Java/Spring/ + https://nicksxs.me/categories/leetcode/java/string/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/leetcode/java/string/ + https://nicksxs.me/categories/Java/Spring/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/ + https://nicksxs.me/categories/Java/gc/ 2022-06-11 weekly 0.2 @@ -3888,35 +3888,35 @@ - https://nicksxs.me/categories/Java/gc/ + https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/MQ/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/ssh/ + https://nicksxs.me/categories/MQ/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/ + https://nicksxs.me/categories/ssh/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%90%90%E6%A7%BD/ 2022-06-11 weekly 0.2 @@ -3944,35 +3944,35 @@ - https://nicksxs.me/categories/shell/ + https://nicksxs.me/categories/git/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Spring/ + https://nicksxs.me/categories/shell/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/git/ + https://nicksxs.me/categories/Spring/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Docker/%E4%BB%8B%E7%BB%8D/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BD%B1%E8%AF%84/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E5%BD%B1%E8%AF%84/ + https://nicksxs.me/categories/Docker/%E4%BB%8B%E7%BB%8D/ 2022-06-11 weekly 0.2 @@ -3993,14 +3993,14 @@ - https://nicksxs.me/categories/Mybatis/ + https://nicksxs.me/categories/Java/Dubbo/RPC/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Dubbo/RPC/ + https://nicksxs.me/categories/Mybatis/ 2022-06-11 weekly 0.2 @@ -4035,35 +4035,35 @@ - https://nicksxs.me/categories/Redis/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ + https://nicksxs.me/categories/Redis/%E5%BA%94%E7%94%A8/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Redis/%E5%BA%94%E7%94%A8/ + https://nicksxs.me/categories/Redis/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/SpringBoot/ + https://nicksxs.me/categories/Java/Design-Patterns/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Design-Patterns/ + https://nicksxs.me/categories/Mac/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Mac/ + https://nicksxs.me/categories/SpringBoot/ 2022-06-11 weekly 0.2 @@ -4105,14 +4105,14 @@ - https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/grep/ + https://nicksxs.me/categories/Java/gc/jvm/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/gc/jvm/ + https://nicksxs.me/categories/Linux/%E5%91%BD%E4%BB%A4/grep/ 2022-06-11 weekly 0.2 @@ -4154,7 +4154,7 @@ - https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/%E8%B7%91%E6%AD%A5/ + https://nicksxs.me/categories/git/%E5%B0%8F%E6%8A%80%E5%B7%A7/ 2022-06-11 weekly 0.2 @@ -4168,14 +4168,14 @@ - https://nicksxs.me/categories/Spring/Servlet/ + https://nicksxs.me/categories/%E7%94%9F%E6%B4%BB/%E8%BF%90%E5%8A%A8/%E8%B7%91%E6%AD%A5/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/git/%E5%B0%8F%E6%8A%80%E5%B7%A7/ + https://nicksxs.me/categories/Spring/Servlet/ 2022-06-11 weekly 0.2 @@ -4203,14 +4203,14 @@ - https://nicksxs.me/categories/Mybatis/%E7%BC%93%E5%AD%98/ + https://nicksxs.me/categories/Java/Dubbo/RPC/SPI/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Java/Dubbo/RPC/SPI/ + https://nicksxs.me/categories/Mybatis/%E7%BC%93%E5%AD%98/ 2022-06-11 weekly 0.2 @@ -4252,14 +4252,14 @@ - https://nicksxs.me/categories/Redis/%E6%BA%90%E7%A0%81/ + https://nicksxs.me/categories/Redis/%E7%BC%93%E5%AD%98/ 2022-06-11 weekly 0.2 - https://nicksxs.me/categories/Redis/%E7%BC%93%E5%AD%98/ + https://nicksxs.me/categories/Redis/%E6%BA%90%E7%A0%81/ 2022-06-11 weekly 0.2 @@ -4356,13 +4356,6 @@ 0.2 - - https://nicksxs.me/categories/Dubbo/SPI/ - 2022-06-11 - weekly - 0.2 - - https://nicksxs.me/categories/Dubbo/%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6/ 2022-06-11 @@ -4447,6 +4440,13 @@ 0.2 + + https://nicksxs.me/categories/Dubbo/SPI/ + 2022-06-11 + weekly + 0.2 + + https://nicksxs.me/categories/%E5%B0%8F%E6%8A%80%E5%B7%A7/top/%E6%8E%92%E5%BA%8F/ 2022-06-11