第7页


  • 场景

    大家如果是做后端开发的,想必都实现过列表查询的接口,当然有的查询条件很简单,一条 SQL 就搞定了,但有的查询条件极其复杂,再加上库表中设计的各种不合理,导致查询接口特别难写,然后加班什么的就不用说了(不知各位有没有这种感受呢~)。

    下面以一个例子开始,这是某购物网站的搜索条件,如果让你实现这样的一个搜索接口,你会如何实现?(当然你说借助搜索引擎,像 Elasticsearch 之类的,你完全可以实现。但我这里想说的是,如果要你自己实现呢?

    我用Redis实现了一个轻量级的搜索引擎!

    我用Redis实现了一个轻量级的搜索引擎!

    来源:github.com/jasonGeng88/blog/blob/master/201706/redis-search.md场景大家如果是做后端开发的,想必都实现过列表查询的接口,当然有的查询条件很简单,一条 SQL 就搞定了,但有的查询条件极其复杂,再加上库表中设计的各种不合理,导致查询接口特别难写...

    Java知识 2021-09-24 45 0
  • Native

    可以发现 stringstream 表现的非常差。当然,这并不是一个公平的比较,但从测评结果来看,使用 stringstream 来实现数值转换相比 baseline 慢了 391 倍。相比之下, <charconv>boost::spirit 表现的更好。

    既然我们已经知道了目标字符串包含了要解析的数字,而且不需要做任何的数值校验,基于这些前提,我们可以思考下,还有更快的方案吗?

    推荐下自己做的 Spring Cloud 的实战项目:

    https://github.com/YunaiV/onemall

    Naive 方案

    我们可以通过一个再简单不过的循环方案,一个个地解析字符。

    inline std::uint64_t parse_naive(std::string_view s) noexcept
    {
      std::uint64_t result = 0;
      for(char digit : s)
      {
        result *= 10;
        result += digit - '0';
      }
      return result;
    }
    如何更快地将string转换成int/long

    如何更快地将string转换成int/long

    你好鸭,Kirito 今天又来分享性能优化的骚操作了。在很多追求性能的程序挑战赛中,经常会遇到一个操作:将 String 转换成 Integer/Long。如果你没有开发过高并发的系统,或者没有参加过任何性能挑战赛,可能会有这样的疑问:这有啥好讲究的,Integer.valueOf/Long.valueOf...

    Java知识 2021-09-23 73 0
  • spring 永远的王者...

    看到这条消息,我笑了因为我刚参加工作那会和他的想法一样搬砖两年我才明白源码非常重要,十场九面问源码这么和你说吧,不懂源码你就不了解其底层逻辑和实现原理这也意味着你只是一个代码的搬运工在求职市场就没有议价能力想要高薪几乎没有可能性要么就是面试失败要么就是被疯狂压价这代码怎么跳来跳去的为什么那么多的接口和接口继...

    Java知识 2021-09-23 68 0

  • 面向对象

    面向对象是一种对世界 理解和抽象的方法。那么对象 是什么呢?

    对象是对世界的理解和抽象,世界又代称为万物 。理解世界是比较复杂的,但是世界又是由事物 组成的。

    正是这样的一种关系,认识事物是极其重要的。那什么是事物 呢?

    事物:由 两个方面组成。事即事情,物即物体,那什么是事情?什么是物体呢?

    由于对象 是对事物 的理解和抽象,所以对象就是对一个事物的属性和行为的理解和抽象。正是这样的一种关系,面向对象 就是对一个事物的属性和行为 的理解和抽象的方法。

    理解对象以及抽象“对象”就是在理解和抽象事物的属性和行为。

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    属性和操作

    面向对象的核心是对象 ,对象是由属性方法 组合而成的。在使用面向对象进行分析、设计、编码的时候,你首先 应该想到的是属性方法 组合形成的对象。在需要组合的时候就不应该出现只包含属性的对象或者只包含方法的对象。

    事物由事情和物体组成。事情是行为,物体是属性。

    推荐下自己做的 Spring Cloud 的实战项目:

    https://github.com/YunaiV/onemall

    对象建模

    在数据库系统中,它们关心的是事物中的物体 ,所以在抽象事物时它们只抽象了事物中的属性。在应用系统中,它们关心的是表达事物的三种方式(属性和方法的组合、只包含属性、只包含方法),所以在抽象事物时需要思考你需要那种方式。

    只要需要抽象事物(事情和物体)中的属性,也就是物体的这部分,那有可能是需要持久化的。只要需要持久化,通常是保存到关系型数据库中,在关系型数据库中的表(Table)基本上是与面向对象中的对象(Object)的属性是一一对应的。

    由于数据库中的表只抽象了事物中的属性,所以它有可能是不完整的。就抽象事物的属性来说依然有两种:只抽象事物的属性、抽象事物的属性和方法的组合。

    正是数据库中 的这种抽象形成了数据模型,它对比对象模型是不完整,所以在面向对象分析(OOA)时一定要采用对象(事物)抽象而不是数据(属性、物体)抽象。

    举个例子:

    简单金融账户(Account)

    属性有:账号(id)、余额(balance)、状态(status)

    操作有:开户(open)、注销(close)、存钱(credit)、取钱(debit)。

    数据模型的只需要设计字段(fields)和关联关系,所以下面的 SQL 基本已完成。

    create table account
    (
        id      integer,
        balance integer,
        status  integer
    )
    ;

    如果把上述 SQL 转换成 Java 的对象的话,得到将是一个用面向对象设计的数据模型,而不是完整的对象模型。这种模型在 Java 开发中非常普遍,这是数据模型思维所导致的结果。

    @Getter
    @Setter
    public class Account {
        private int id;
        private int balance;
        private AccountStatus status;
    }

    如果使用对象模型的思维来设计模型,从接口上来看,他应该是这样的:

    public interface Account {

        int getId();

        int getBalance();

        AccountStatus getStatus();

        void open();

        void close();

        void credit(int amount);

        void debit(int amount);
    }

    如果 Account 接口符合金融账户的设计,那么 Account 最简单地实现应该如下:

    @Getter
    public class Account {
        private int id;
        private int balance;
        private AccountStatus status;

        public void open() {
            this.status = AccountStatus.OPENED;
        }

        public void close() {
            this.status = AccountStatus.CLOSED;
        }

        public void credit(int amount) {
            this.balance += amount;
        }

        public void debit(int amount) {
            this.balance -= amount;
        }
    }

    这是从两个建模的角度来对比对象模型和数据模型的不同,下面我们还要从完整地执行流程来对比。

    Account Credit

    首先是使用数据模型所设计的时序图,因为数据模型下的 Account 不包含业务逻辑,所有的业务逻辑都在 AccountService 中,所以通常称为业务逻辑服务(层)或者事务脚本。如图下:

    领域驱动设计(DDD):面向对象思想

    领域驱动设计(DDD):面向对象思想

    来源:juejin.cn/user/4441682708804142面向对象面向对象是一种对世界 理解和抽象的方法。那么对象 是什么呢?对象是对世界的理解和抽象,世界又代称为万物 。理解世界是比较复杂的,但是世界又是由事物 组成的。正是这样的一种关系,认识事物是极其重要的。那什么是事物 呢?事物:由事 和物...

    Java知识 2021-09-23 63 0

  • 为解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。

    论文《Cuckoo Filter:Better Than Bloom》作者将布谷鸟过滤器和布隆过滤器进行了深入的对比。

    相比布谷鸟过滤器而言,布隆过滤器有以下不足:

    查询性能弱 是因为布隆过滤器需要使用多个 hash 函数探测位图中多个不同的位点,这些位点在内存上跨度很大,会导致 CPU 缓存行命中率低。

    空间效率低 是因为在相同的误判率下,布谷鸟过滤器的空间利用率要明显高于布隆,空间上大概能节省 40% 多。

    不过布隆过滤器并没有要求位图的长度必须是 2 的指数,而布谷鸟过滤器必须有这个要求。从这一点出发,似乎布隆过滤器的空间伸缩性更强一些。

    不支持反向删除操作 这个问题着实是击中了布隆过滤器的软肋。

    在一个动态的系统里面元素总是不断的来也是不断的走,布隆过滤器就好比是印迹,来过了就会有痕迹,就算走了也无法清理干净。

    比如你的系统里本来只留下 1kw 个元素,但是整体上来过了上亿的流水元素。

    那布隆过滤器很无奈,它会将这些流失的元素的印迹也会永远存放在那里。

    随着时间的流失,这个过滤器会越来越拥挤,直到有一天你发现它的误判率太高了,不得不进行重建。

    布谷鸟过滤器在论文里声称自己解决了这个问题,它可以有效支持反向删除操作。而且将它作为一个重要的卖点,诱惑你们放弃布隆过滤器改用布谷鸟过滤器。

    但是经过我一段时间的调查研究发现,布谷鸟过滤器并没有它声称的那么美好。它支持的反向删除操作非常鸡肋,以至于你根本没办法使用这个功能。

    在向读者具体说明这个问题之前,还是先给读者仔细讲解一下布谷鸟过滤器的原理。

    布谷鸟哈希

    布谷鸟过滤器源于布谷鸟哈希算法,布谷鸟哈希算法源于生活 —— 那个热爱「鸠占鹊巢」的布谷鸟。

    布谷鸟喜欢滥交(自由),从来不自己筑巢。它将自己的蛋产在别人的巢里,让别人来帮忙孵化。

    待小布谷鸟破壳而出之后,因为布谷鸟的体型相对较大,它又将养母的其它孩子(还是蛋)从巢里挤走 —— 从高空摔下夭折了。

    最简单的布谷鸟哈希结构是一维数组结构,会有两个 hash 算法将新来的元素映射到数组的两个位置。

    如果两个位置中有一个位置为空,那么就可以将元素直接放进去。但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。

    p1 = hash1(x) % l
    p2 = hash2(x) % l

    不同于布谷鸟的是,布谷鸟哈希算法会帮这些受害者(被挤走的蛋)寻找其它的窝。

    因为每一个元素都可以放在两个位置,只要任意一个有空位置,就可以塞进去。

    所以这个伤心的被挤走的蛋会看看自己的另一个位置有没有空,如果空了,自己挪过去也就皆大欢喜了。

    但是如果这个位置也被别人占了呢?

    好,那么它会再来一次「鸠占鹊巢」,将受害者的角色转嫁给别人。然后这个新的受害者还会重复这个过程直到所有的蛋都找到了自己的巢为止。

    正如鲁迅的那句名言「占自己的巢,让别人滚蛋去吧!

    但是会遇到一个问题,那就是如果数组太拥挤了,连续踢来踢去几百次还没有停下来,这时候会严重影响插入效率。

    这时候布谷鸟哈希会设置一个阈值,当连续占巢行为超出了某个阈值,就认为这个数组已经几乎满了。

    这时候就需要对它进行扩容,重新放置所有元素。

    还会有另一个问题,那就是可能会存在挤兑循环。

    比如两个不同的元素,hash 之后的两个位置正好相同,这时候它们一人一个位置没有问题。

    但是这时候来了第三个元素,它 hash 之后的位置也和它们一样,很明显,这时候会出现挤兑的循环。

    不过让三个不同的元素经过两次 hash 后位置还一样,这样的概率并不是很高,除非你的 hash 算法太挫了。

    布谷鸟哈希算法对待这种挤兑循环的态度就是认为数组太拥挤了,需要扩容(实际上并不是这样)

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    优化

    上面的布谷鸟哈希算法的平均空间利用率并不高,大概只有 50%。到了这个百分比,就会很快出现连续挤兑次数超出阈值。

    这样的哈希算法价值并不明显,所以需要对它进行改良。

    改良的方案之一 是增加 hash 函数,让每个元素不止有两个巢,而是三个巢、四个巢。这样可以大大降低碰撞的概率,将空间利用率提高到 95%左右。

    另一个改良方案 是在数组的每个位置上挂上多个座位。这样即使两个元素被 hash 在了同一个位置,也不必立即「鸠占鹊巢」。

    因为这里有多个座位,你可以随意坐一个。除非这多个座位都被占了,才需要进行挤兑。很明显这也会显著降低挤兑次数。

    这种方案的空间利用率只有 85%左右,但是查询效率会很高 ,同一个位置上的多个座位在内存空间上是连续的,可以有效利用 CPU 高速缓存。

    所以更加高效的方案 是将上面的两个改良方案融合起来,比如使用 4 个 hash 函数,每个位置上放 2 个座位。

    这样既可以得到时间效率,又可以得到空间效率。这样的组合甚至可以将空间利用率提到高 99%,这是非常了不起的空间效率。

    推荐下自己做的 Spring Cloud 的实战项目:

    https://github.com/YunaiV/onemall

    布谷鸟过滤器

    布谷鸟过滤器和布谷鸟哈希结构一样,它也是一维数组,但是不同于布谷鸟哈希的是,布谷鸟哈希会存储整个元素,而布谷鸟过滤器中只会存储元素的指纹信息(几个bit,类似于布隆过滤器)。

    这里过滤器牺牲了数据的精确性换取了空间效率。正是因为存储的是元素的指纹信息,所以会存在误判率,这点和布隆过滤器如出一辙。

    首先布谷鸟过滤器还是只会选用两个 hash 函数,但是每个位置可以放置多个座位。

    这两个 hash 函数选择的比较特殊,因为过滤器中只能存储指纹信息。当这个位置上的指纹被挤兑之后,它需要计算出另一个对偶位置。

    而计算这个对偶位置是需要元素本身的,我们来回忆一下前面的哈希位置计算公式。

    fp = fingerprint(x)
    p1 = hash1(x) % l
    p2 = hash2(x) % l

    我们知道了 p1 和 x 的指纹,是没办法直接计算出 p2 的。

    特殊的 hash 函数

    布谷鸟过滤器巧妙的地方就在于设计了一个独特的 hash 函数,使得可以根据 p1 和 元素指纹 直接计算出 p2,而不需要完整的 x 元素。

    fp = fingerprint(x)
    p1 = hash(x)
    p2 = p1 ^ hash(fp)  // 异或

    从上面的公式中可以看出,当我们知道 fp 和 p1,就可以直接算出 p2。同样如果我们知道 p2 和 fp,也可以直接算出 p1 —— 对偶性。

    p1 = p2 ^ hash(fp)

    所以我们根本不需要知道当前的位置是 p1 还是 p2,只需要将当前的位置和 hash(fp) 进行异或计算就可以得到对偶位置。

    而且只需要确保 hash(fp) != 0 就可以确保 p1 != p2,如此就不会出现自己踢自己导致死循环的问题。

    也许你会问为什么这里的 hash 函数不需要对数组的长度取模呢?

    实际上是需要的,但是布谷鸟过滤器强制数组的长度必须是 2 的指数,所以对数组的长度取模等价于取 hash 值的最后 n 位。

    在进行异或运算时,忽略掉低 n 位 之外的其它位就行。将计算出来的位置 p 保留低 n 位就是最终的对偶位置。

    // l = power(2, 8)
    p_ = p & 0xff

    ** **

    数据结构

    简单起见,我们假定指纹占用一个字节,每个位置有 4 个 座位。

    type bucket [4]byte  // 一个桶,4个座位
    type cuckoo_filter struct {
      buckets [size]bucket // 一维数组
      nums int  // 容纳的元素的个数
      kick_max  // 最大挤兑次数
    }

    插入算法

    插入需要考虑到最坏的情况,那就是挤兑循环。所以需要设置一个最大的挤兑上限

    def insert(x):
      fp 
    = fingerprint(x)
      p1 = hash(x)
      p2 = p1 ^ hash(fp)
      // 尝试加入第一个位置
      if !buckets[p1].full():
        buckets[p1].add(fp)
        nums++
        return true
      // 尝试加入第二个位置
      if !buckets[p2].full():
        buckets[p2].add(fp)
        nums++
        return true
      // 随机挤兑一个位置
      p = rand(p1, p2)
      c = 0
      while c < kick_max:
        // 挤兑
        old_fp = buckets[p].replace_with(fp)
        fp = old_fp
        // 计算对偶位置
        p = p ^ hash(fp)
        // 尝试加入对偶位置
        if !buckets[p].full():
          buckets[p].add(fp)
          nums++
          return true
        c++
      return false

    查找算法

    查找非常简单,在两个 hash 位置的桶里找一找有没有自己的指纹就 ok 了。

    def contains(x):
      fp 
    = fingerprint(x)
      p1 = hash(x)
      p2 = p1 ^ hash(fp)
      return buckets[p1].contains(fp) |" alt="大名鼎鼎的布隆过滤器过时了?被一只鸟取而代之。。。">

    大名鼎鼎的布隆过滤器过时了?被一只鸟取而代之。。。

    来源:码洞为解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。论文《Cuckoo Filter:Better Than Bloom》作者将布谷鸟过滤器和布隆过滤器进行了深入的对比。相比布谷鸟过滤器而言,布隆过滤器有以下不足:查询性能弱 是因为布隆过滤器需要使用多个 hash 函数探测位图中多个不同的位...

    Java知识 2021-09-22 51 0

  • 今天我们再来盘一盘 ThreadLocal ,这篇力求对 ThreadLocal 一网打尽,彻底弄懂 ThreadLocal 的机制。

    有了这篇基础之后,下篇再来盘一盘 ThreadLocal 的进阶版,等我哈。

    话不多说,本文要解决的问题如下:

    好了,开车!

    为什么需要 ThreadLocal

    最近不是开放三胎政策嘛,假设你有三个孩子。

    现在你带着三个孩子出去逛街,路过了玩具店,三个孩子都看中了一款变形金刚。

    所以你买了一个变形金刚,打算让三个孩子轮着玩。

    回到家你发现,孩子因为这个玩具吵架了,三个都争着要玩,谁也不让着谁。

    这时候怎么办呢?你可以去拉架,去讲道理,说服孩子轮流玩,但这很累。

    所以一个简单的办法就是出去再买两个变形金刚,这样三个孩子都有各自的变形金刚,世界就暂时得到了安宁。

    映射到我们今天的主题,变形金刚就是共享变量,孩子就是程序运行的线程。

    有多个线程(孩子),争抢同一个共享变量(玩具),就会产生冲突,而程序的解决办法是加锁(父母说服,讲道理,轮流玩),但加锁就意味着性能的消耗(父母比较累)。

    所以有一种解决办法就是避免共享(让每个孩子都各自拥有一个变形金刚),这样线程之间就不需要竞争共享变量(孩子之间就不会争抢)。

    所以为什么需要 ThreadLocal?

    就是为了通过本地化资源来避免共享,避免了多线程竞争导致的锁等消耗。

    这里需要强调一下,不是说任何东西都能直接通过避免共享来解决,因为有些时候就必须共享。

    举个例子:当利用多线程同时累加一个变量的时候,此时就必须共享,因为一个线程的对变量的修改需要影响要另个线程,不然累加的结果就不对了。

    再举个不需要共享的例子:比如现在每个线程需要判断当前请求的用户来进行权限判断,那这个用户信息其实就不需要共享,因为每个线程只需要管自己当前执行操作的用户信息,跟别的用户不需要有交集。

    好了,道理很简单,这下子想必你已经清晰了 ThreadLocal 出现的缘由了。

    再来看一下 ThreadLocal 使用的小 demo。

    public class YesThreadLocal {

        private static final ThreadLocal<String> threadLocalName = ThreadLocal.withInitial(() -> Thread.currentThread().getName());

        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    System.out.println("threadName: " + threadLocalName.get());
                }, "yes-thread-" + i).start();
            }
        }
    }

    输出结果如下:

    面试中 ThreadLocal 能问的,都在这了

    面试中 ThreadLocal 能问的,都在这了

    今天我们再来盘一盘 ThreadLocal ,这篇力求对 ThreadLocal 一网打尽,彻底弄懂 ThreadLocal 的机制。有了这篇基础之后,下篇再来盘一盘 ThreadLocal 的进阶版,等我哈。话不多说,本文要解决的问题如下:好了,开车!为什么需要 ThreadLocal最近不是开放三胎政策...

    Java知识 2021-09-22 57 0

  • 前言

    前后端分离的开发方式,我们以接口为标准来进行推动,定义好接口,各自开发自己的功能,最后进行联调整合。无论是开发原生的APP还是webapp还是PC端的软件,只要是前后端分离的模式,就避免不了调用后端提供的接口来进行业务交互。

    网页或者app,只要抓下包就可以清楚的知道这个请求获取到的数据,也可以伪造请求去获取或攻击服务器;也对爬虫工程师来说是一种福音,要抓你的数据简直轻而易举。那我们怎么去解决这些问题呢?

    接口签名

    我们先考虑一下接口数据被伪造,以及接口被重复调用的问题,要解决这个问题我们就要用到接口签名的方案,

    推荐下自己做的 Spring Boot 的实战项目:

    https://github.com/YunaiV/ruoyi-vue-pro

    签名流程

    阿里一面:如何保证API接口数据安全?

    阿里一面:如何保证API接口数据安全?

    来源:老顾聊技术 前言 前后端分离的开发方式,我们以接口为标准来进行推动,定义好接口,各自开发自己的功能,最后进行联调整合。无论是开发原生的APP还是webapp还是PC端的软件,只要是前后端分离的模式,就避免不了调用后端提供的接口来进行业务交互。网页或者app,只要抓下包就可以清楚的知道这个请求获取到的数...

    Java知识 2021-09-21 71 0
  • 别再用 BeanUtils 了,这款 PO VO DTO 转换神器不香么?

    别再用 BeanUtils 了,这款 PO VO DTO 转换神器不香么?

    来源:toutiao.com/i6891531055631696395/...

    Java知识 2021-09-21 68 0
  • 微服务已过时!DDD领域建模与架构设计才是未来!

    微服务已过时!DDD领域建模与架构设计才是未来!

    战略设计从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。战术设计从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。重要提醒:一次报名,全程赠送讲师答疑服...

    Java知识 2021-09-20 71 0
  • 用 传统关系型数据库和 ES 实现会有什么差别?

    如果用像 MySQL 这样的 RDBMS 来存储古诗的话,我们应该会去使用这样的 SQL 去查询

    select name from poems where content like "%前%";

    这种我们称为顺序扫描法,需要遍历所有的记录进行匹配。

    不但效率低,而且不符合我们搜索时的期望,比如我们在搜索“ABCD"这样的关键词时,通常还希望看到"A","AB","CD",“ABC”的搜索结果。

    于是乎就有了专业的搜索引擎,比如我们今天的主角 -- ES。

    搜索引擎原理

    搜索引擎的搜索原理简单概括的话可以分为这么几步,

    干掉SQL中的like,我用es后运营小姐姐们都说好快!

    干掉SQL中的like,我用es后运营小姐姐们都说好快!

    来源:juejin.cn/post/6889020742366920712"All problems in computer science can be solved by another level of indirection.”– David J. Wheeler“计算机世界就是 trade-off...

    Java知识 2021-09-20 61 0
识海教程

识海教程

每天学习技术知识,每天进步多一点点。

  • 228990阅读数
  • 4评论数

关于识海

网站将会逐步收集越来越多的技术文章,当然将来也会开启投稿功能,各位朋友们有好的文章可以分享给大家