# Nicksxs's Blog

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

0%

## 在老丈人家的小工记四

发表于 2020-09-26 分类于 生活 ， 运动 ， 跑步 ， 干活 阅读次数： Disqus：

## 小工记四

第四周去的时候让我们去了现在在住的房子里，去三楼整理东西了，蛮多的东西需要收拾整理，有些需要丢一下，以前往往是把不太要用的东西就放三楼了，但是后面就不会再去收拾整理，LD 跟丈母娘负责收拾，我不太知道哪些还要的，哪些不要了，而且本来也不擅长这种收拾🤦‍♂️，然后就变成她们收拾出来废纸箱，我负责拆掉，压平，这时候终于觉得体重还算是有点作用，总体来说这个事情我其实也不擅长，不擅长的主要是捆起来，可能我总是小题大做，因为纸箱大小不一，如果不做一下分类，然后把大的折小一些的话，直接绑起来，容易拎起来就散掉了，而且一些鞋盒子这种小件的纸盒会比较薄，冰箱这种大件的比较厚，厚的比较不容易变形，需要大力踩踏，而且扎的时候需要用体重压住捆实了之后那样子才是真的捆实的，不然待会又是松松垮垮容易滑出来散架，因为压住了捆好后，下来了之后箱子就会弹开了把绳子崩紧实，感觉又是掌握到生活小技巧了😃，我这里其实比较单调无聊，然后 LD 那可以说非常厉害了，一共理出来 11 把旧电扇，还有好多没用过全新的不锈钢脸盆大大小小的，感觉比店里在卖的还多，还有是有比较多小时候的东西，特别多小时候的衣服，其实这种对我来说最难了，可能需要读一下断舍离，蛮多东西都舍不得扔，但是其实是没啥用了，然后还占地方，这天应该算是比较轻松的一天了，上午主要是把收拾出来要的和不要的搬下楼，然后下午要去把纸板给卖掉。中午还是去小快餐店吃的，在住的家里理东西还有个好处就是中午吃完饭可以小憩一下，因为我个人是非常依赖午休的，不然下午完全没精神，而且心态也会比较烦躁，一方面是客观的的确比较疲惫，另一方面应该主观心理作用也有点影响，就像上班的时候也是觉得不午睡就会很难受，心理作用也有一点，不过总之能睡还是睡一会，真的没办法就心态好点，吃完午饭之后我们就推着小平板车去收废品的地方卖掉了上午我收拾捆起来的纸板，好像卖了一百多，都是直接过地磅了，不用一捆一捆地称，不过有个小插曲，那里另外一个大爷在倒他的三轮车的时候撞了我一下，还好车速慢，屁股上肉垫后，接下来就比较麻烦了，是LD 她们两姐妹从小到大的书，也要去卖掉，小平板车就载不下了，而且着实也不太好推，轮子不太平，导致推着很累，书有好多箱，本来是想去亲戚家借电动三轮车，因为不会开摩托的那种，摩托的那种 LD 邻居家就有，可是到了那发现那个也是很大，而且刹车是用脚踩的那种，俺爹不太放心，就说第二天周日他有空会帮忙去载了卖掉的，然后比较搞笑的来了，丈母娘看错了时间，以为已经快五点了，就让我们顺便在车里带点东西去在修的房子，放到那边三楼去，到了那还跟老丈人说已经这么迟了要赶紧去菜场买菜了，结果我们回来以后才发现看错了一个小时🤦‍♂️。
前面可以没提，前三周去的我们一般就周六去一天，然后周日因为要早点回杭州，而且可能想让我们周日能休息下，但是这周就因为周日的时候我爸要去帮忙载书，然后 LD 姐姐也会过来收拾东西，我们周日就又去整理收拾了，周日由于俺爹去的很早，我过去的时候书已经木有了，主要是去收拾东西了，把一些有用没用的继续整理，基本上三楼的就处理完毕了，舒了一大口气，毕竟让丈母娘一个人收拾实在是太累了，但是要扔掉的衣服比较棘手，附近知道的青蛙回收桶被推倒了，其他地方也不知道哪里有，我们就先载了一些东西去在修的房子那，然后去找青蛙桶，结果一个小区可以进，但是已经满了，另一个不让进，后来只能让 LD 姐姐带去她们小区扔了，塞了满满一车。因为要赶回杭州的车就没有等我爸一起回来，他还在那帮忙搞卫生间的墙缝。
虽然这两天不太热，活也不算很吃力，不过我这个体重和易出汗的体质，还是让短袖不知道湿透了多少次，灌了好多水和冰红茶（下午能提提神），回来周一早上称体重也比较喜人，差一点就达到阶段目标，可以想想去哪里吃之前想好的烤肉跟火锅了（估计吃完立马回到解放前）。

## Leetcode 3 Longest Substring Without Repeating Characters 题解分析

发表于 2020-09-20 分类于 Java ， leetcode ， java ， 字符串 - online ， string 阅读次数： Disqus：

又做了个题，看记录是以前用 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)两重循环，不过显然不太好，万一超时间，还有一种就是线性复杂度的了，这个就是需要搞定一个思路，比如字符串时 `a`bcdefg`a`qwrty，比如遍历到第二个`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;
        }
    }

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

## 在老丈人家的小工记三

发表于 2020-09-13 分类于 生活 ， 运动 ， 跑步 ， 干活 阅读次数： Disqus：

## 小工记三

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

## mybatis 的 $ 和 # 是有啥区别

发表于 2020-09-06 更新于 2022-06-11 分类于 Java ， Mybatis ， Spring ， Mysql ， Sql注入 ， Mybatis 阅读次数： Disqus：

这个问题也是面试中常被问到的，就抽空来了解下这个，跳过一大段前面初始化的逻辑，
对于一条`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;
        if (isDynamic) {
          sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
          sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
      }
    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
    protected MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            if (textSqlNode.isDynamic()) {
              contents.add(textSqlNode);
              isDynamic = true;
            } else {
              contents.add(new StaticTextSqlNode(data));
            }
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);
            isDynamic = true;
          }
        }
        return new MixedSqlNode(contents);
      }
    // org.apache.ibatis.scripting.xmltags.TextSqlNode#isDynamic
      public boolean isDynamic() {
        DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
        GenericTokenParser parser = createParser(checker);
        parser.parse(text);
        return checker.isDynamic();
      }
        private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
      }

可以看到其中一个条件就是是否有`${}`这种占位符，假如说上面的 sql 换成 `${}`，那么可以看到它会在这里创建一个 dynamicSqlSource,

    // org.apache.ibatis.scripting.xmltags.DynamicSqlSource
    public class DynamicSqlSource implements SqlSource {

      private final Configuration configuration;
      private final SqlNode rootSqlNode;

      public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
      }

      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
      }

    }

这里眼尖的同学可能就可以看出来了，RawSqlSource 在初始化的时候已经经过了 parse，把`#{}`替换成了`?`占位符，但是 DynamicSqlSource 并没有
再看这个图，我们发现在这的时候还没有进行替换
然后往里跟
好像是这里了
这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用，不同类型的标签会构成不同的 node，像这里就是一个 textSqlNode
可以发现到这我们的 sql 已经被替换了，而且是直接作为 string 类型替换的，所以可以明白了这个问题所在，就是注入，不过细心的同学发现其实这里是有个
理论上还是可以做过滤的，不过好像现在没用起来。
我们前面可以发现对于`#{}`是在启动扫描 mapper的 xml 文件就替换成了 `?`,然后是在什么时候变成实际的值的呢
发现到这的时候还是没有替换，其实说白了也就是 prepareStatement 那一套，
在这里进行替换，会拿到 org.apache.ibatis.mapping.ParameterMapping，然后进行替换，因为会带着类型信息，所以不用担心注入咯

## 这周末我又在老丈人家打了天小工

发表于 2020-08-30 分类于 生活 ， 运动 ， 跑步 ， 干活 阅读次数： Disqus：

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