Java-布隆过滤器的实现

文章目录

  • 前言
  • 一、概述
  • 二、误差率
  • 三、hash 函数的选择
  • 四、手写布隆过滤器
  • 五、guava 中的布隆过滤器


前言

如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路,但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢 (O(n)O(logn))。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个 Hash 函数将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,我们只要看看这个点是不是 1 就可以知道集合中有没有它了。于是乎,布隆过滤器便应运而生了。

一、概述

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。

主要作用:用于检索一个元素是否在一个集合中。

优点 :

  • 时间复杂度低,增加及查询元素的时间复杂度都是 O(k)kHash 函数的个数
  • 占用存储空间小,布隆过滤器相对于其他数据结构(如SetMap)非常节省空间

缺点:

  • 存在误判,只能证明一个元素一定不存在或者可能存在,返回结果是概率性的,但是可以通过调整参数来降低误判比例
  • 删除困难,一个元素映射到 bit 数组上的 x 个位置为 1,删除的时候不能简单的直接置为 0,可能会影响到其他元素的判断

原理:

布隆过滤器由长度为 m 的位向量和 k个随机映射函数组成。

假设 bit array 的长度 m=10,哈希函数的个数 k=3,默认情况下 bit array 中每个坐标的值均为 0
在这里插入图片描述
当一个元素被加入集合时,通过多个 hash 函数计算得到多个 hash 值,以这些 hash 值作为数组的坐标,将这些坐标位置上的值设置为 1

在这里插入图片描述
当查询该元素是否存在于集合中时,以同样的方法通过多个 hash 函数计算出对应的 hash 值,再查看这些 hash 值所对应的坐标是否均为 1,如果均为 1 则表示已存在,否则不存在

在这里插入图片描述


二、误差率

布隆过滤器的误差率(false positive rate)是指由于 hash 冲突,导致原本不再布隆过滤器中的数据,查询时显示在里面

哈希冲突是指当两个或多个不同的输入产生相同的哈希值时,就会发生哈希冲突

比如说有三个元素经过 k=3hash 函数计算得到的哈希值分别是 [1,6,9][2,5,9][3,4,8]
在这里插入图片描述
由上图可见 element_1element_2 经过 hash_3 函数时得到的值均为 9,这就是 hash 冲突,虽然可能发生哈希冲突,但是由于每个元素是通过多个 hash 值来判断是否存在,所以在插入上述三个元素时均会判断出该元素不存在,此时 bit array 如下:

在这里插入图片描述

如果再插入第四个元素时,经过这 k=3hash 函数计算后的结果是 [5,6,8],而 5,6,8 这三个坐标在 bit array 中值均为 1,此时就会判定 element_4 已存在,实际上并没有存在,这就是布隆过滤器会存在误判的原因

在这里插入图片描述

随着数据量增加, bit array 中每个位置的值为 1 的比率也会增加,误判的可能性也会随之增加

那如何减少这种因为 hash 冲突而导致的误判呢?可以用公式进行推导

假设 hash 函数以等概率条件选择并设置 bit array 中的某一位,m 是该位数组的大小,khash 函数的个数,那么位数组中某一特定的位在进行元素插入时的 hash 操作中没有被置为 1 的概率是:
1 − 1 m 1-\frac{1}{m} 1m1
那么在所有 khash 操作后该位没有被置为 1 的概率是:
( 1 − 1 m ) k (1-\frac{1}{m})^{k} (1m1)k
如果插入了 n 个元素,那么某一位仍然是 0 的概率是:
( 1 − 1 m ) k n (1-\frac{1}{m})^{kn} (1m1)kn
因而该位为 1 的概率是:
1 − ( 1 − 1 m ) k n 1-(1-\frac{1}{m})^{kn} 1(1m1)kn
现在检测某一元素是否在该集合中,标明某个元素是否在集合中所需的 k 个位置都按照如上的方法设置为 1 ,但是该方法可能会使算法错误的认为某一原本不在集合中的元素却被检测为在该集合中(误差 False Positives),该概率由以下公式确定:
( 1 − ( 1 − 1 m ) k n ) k ≈ ( 1 − e − k n / m ) k (1-(1-\frac{1}{m})^{kn})^{k}\approx (1-e^{-kn/m})^{k} (1(1m1)kn)k(1ekn/m)k

推导过程: ( 1 − ( 1 − 1 m ) k n ) k = ( 1 − ( 1 + 1 − m ) − m − k n m ) k (1-(1-\frac{1}{m})^{kn})^{k}= (1-(1+\frac{1}{-m})^{-m\frac{-kn}{m}})^{k} (1(1m1)kn)k=(1(1+m1)mmkn)k
由自然常数定义: e = lim ⁡ n → ∞ ( 1 + 1 n ) n e= \lim_{n \to \infty} (1+\frac{1}{n})^{n} e=nlim(1+n1)n
可知,当 n 趋近于无穷大时: e = ( 1 + 1 n ) n e= (1+\frac{1}{n})^{n} e=(1+n1)n
所以: ( 1 − ( 1 + 1 − m ) − m − k n m ) k = ( 1 − e − k n / m ) k (1-(1+\frac{1}{-m})^{-m\frac{-kn}{m}})^{k}= (1-e^{-kn/m})^{k} (1(1+m1)mmkn)k=(1ekn/m)k
即: ( 1 − ( 1 − 1 m ) k n ) k ≈ ( 1 − e − k n / m ) k (1-(1-\frac{1}{m})^{kn})^{k}\approx (1-e^{-kn/m})^{k} (1(1m1)kn)k(1ekn/m)k

由此可见,随着 m(位数组大小)的增加,误差(False Positives)的概率会下降,同时随着插入元素个数 n 的增加,误差(False Positives)的概率又会上升

对于给定的 mn,如何选择 hash 函数个数 k?由以下公式确定:
k = m n l n 2 ≈ 0.7 m n k=\frac{m}{n}ln2\approx 0.7\frac{m}{n} k=nmln20.7nm

推导过程:


k 为何值时可以使得误判率最低?设误判率为 k 的函数:
f ( k ) = ( 1 − e − k n / m ) k f(k)= (1-e^{-kn/m})^{k} f(k)=(1ekn/m)k
令: b = e n m b=e^{\frac{n}{m}} b=emn
则: f ( k ) = ( 1 − b − k ) k f(k)=(1-b^{-k})^{k} f(k)=(1bk)k
两边取对数: l n f ( k ) = k ⋅ l n ( 1 − b − k ) lnf(k)=k\cdot ln(1-b^{-k}) lnf(k)=kln(1bk)
两边对 k 进行求导:
1 f ( k ) ⋅ f ′ ( k ) = l n ( 1 − b − k ) + k ⋅ 1 1 − b − k ⋅ ( − b − k ) ⋅ ( − 1 ) ⋅ l n b = l n ( 1 − b − k ) + k ⋅ b − k ⋅ l n b 1 − b − k \frac{1}{f(k)}\cdot f'(k)=ln(1-b^{-k})+k\cdot \frac{1}{1-b^{-k}}\cdot (-b^{-k})\cdot (-1)\cdot lnb =ln(1-b^{-k})+k\cdot \frac{b^{-k}\cdot lnb}{1-b^{-k}} f(k)1f(k)=ln(1bk)+k1bk1(bk)(1)lnb=ln(1bk)+k1bkbklnb
等式左边,常量的导数为 0,所以: l n ( 1 − b − k ) + k ⋅ b − k ⋅ l n b 1 − b − k = 0 ln(1-b^{-k})+k\cdot \frac{b^{-k}\cdot lnb}{1-b^{-k}}=0 ln(1bk)+k1bkbklnb=0
( 1 − b − k ) ⋅ l n ( 1 − b − k ) = − k ⋅ b − k ⋅ l n b (1-b^{-k})\cdot ln(1-b^{-k})=-k\cdot b^{-k}\cdot lnb (1bk)ln(1bk)=kbklnb
因为: − k ⋅ l n b = l n b − k -k\cdot lnb = lnb^{-k} klnb=lnbk
所以: ( 1 − b − k ) ⋅ l n ( 1 − b − k ) = b − k ⋅ l n b − k (1-b^{-k})\cdot ln(1-b^{-k})=b^{-k}\cdot lnb^{-k} (1bk)ln(1bk)=bklnbk
因为, b-k 恒小于 1,所以: l n ( 1 − b − k ) = l n b − k ln(1-b^{-k}) = lnb^{-k} ln(1bk)=lnbk
则等式化简为: 1 − b − k = b − k 1-b^{-k}=b^{-k} 1bk=bk
b − k = 1 2 b^{-k}=\frac{1}{2} bk=21
转化 b 得: e − k n m = 1 2 e^{-\frac{kn}{m}}=\frac{1}{2} emkn=21
− k n m = l n 2 -\frac{kn}{m}=ln2 mkn=ln2
则误判率最低时,得出 k 的值为: k = l n 2 ⋅ m n ≈ 0.7 m n k=ln2\cdot \frac{m}{n} \approx 0.7 \frac{m}{n} k=ln2nm0.7nm

此时误差(False Positives)的概率为:
2 − k ≈ 0.618 5 m n 2^{-k}\approx 0.6185^{\frac{m}{n}} 2k0.6185nm

推导过程:

由上述推导过程可知,当: e − k n m = 1 2 e^{-\frac{kn}{m}}=\frac{1}{2} emkn=21
时函数 f(k) (即误差率)的值最小,所以:
f ( e r r o r ) = ( 1 − e − k n / m ) k = ( 1 − 1 2 ) k = 2 − k = 2 − l n 2 ⋅ m n ≈ 0.618 5 m n f(error)=(1-e^{-kn/m})^{k}=(1-\frac{1}{2})^{k}=2^{-k}=2^{-ln2\cdot \frac{m}{n}}\approx 0.6185^{\frac{m}{n}} f(error)=(1ekn/m)k=(121)k=2k=2ln2nm0.6185nm

而对于给定的误差(False Positives)概率 p,如何选择最优的数组大小 m 呢?
m = − n l n p ( l n 2 ) 2 m= -\frac{nlnp}{(ln2)^{2}} m=(ln2)2nlnp

推导过程:


由上述推导过程误差率得: p = 2 − l n 2 ⋅ m n p=2^{-ln2\cdot \frac{m}{n}} p=2ln2nm
两边取对数: l n p = l n 2 ⋅ ( − l n 2 ) ⋅ m n lnp=ln2\cdot (-ln2)\cdot \frac{m}{n} lnp=ln2(ln2)nm
则: m = − n ⋅ l n p ( l n 2 ) 2 m=-\frac{n\cdot lnp}{(ln2)^{2}} m=(ln2)2nlnp


三、hash 函数的选择

前面提到 hash 冲突是导致布隆过滤产生误差的主要原因,所以选择一个合适的 hash 函数是非常重要的,哈希函数的选择会影响到布隆过滤器的性能和准确性。

以下是选择哈希函数时需要考虑的一些因素:

  • 均匀分布:一个好的哈希函数应该能够将输入数据均匀地分布到布隆过滤器的位数组中,以最大限度地减少碰撞的可能性
  • 可扩展性:随着数据集的增加,布隆过滤器的大小也需要相应地扩展。因此,选择的哈希函数应该易于扩展,以便在增加数据集时有效地调整布隆过滤器的大小
  • 稳定性:在某些情况下,如果哈希函数对输入数据的变化过于敏感,可能会导致布隆过滤器中的大量位被频繁地置为 10,这会影响布隆过滤器的性能和准确性,因此,选择一个相对稳定的哈希函数可能更为合适
  • 易于实现:在选择哈希函数时,还需要考虑其实施的难易程度和可移植性。易于实现和移植的哈希函数可以减少布隆过滤器的实现和维护成本

常见的 hash 函数:

  • MD5:MD5(Message Digest Algorithm 5)是一种广泛使用的密码哈希函数,它将任意长度的 “字节串” 映射为一个 128 位的大数。尽管 MD5 在许多安全应用中已经不再被视为安全的哈希函数,但在某些情况下,它仍然可以用于布隆过滤器
  • SHA-1:SHA-1(Secure Hash Algorithm 1)是美国国家安全局设计,并由美国国家标准和技术研究所(NIST)发布的一系列密码散列函数。SHA-1 可以生成一个被称为消息摘要的 160 位( 20 字节)哈希值。尽管 SHA-1 的安全性也受到一定质疑,但在某些场景下仍可用于布隆过滤器
  • SHA-256:SHA-256 是 SHA-2 家族中的一种哈希函数,它生成一个 256 位(32字节)的哈希值。SHA-256 提供了比 SHA-1 更高的安全性,并且在实际应用中被广泛使用
  • MurmurHash:MurmurHash 是一种非加密型哈希函数,适用于一般数据检索应用。它能够提供较好的分布性和性能,因此在布隆过滤器中也被考虑使用

四、手写布隆过滤器

目前 MurmurHash 函数作为布隆过滤器的 hash 函数是使用得比较多的,所以以下内容也会采用这种函数

Google Guava 库:Guava 是一个广泛使用的 Java 库,其中包含了 MurmurHash 的实现,可以使用其中的 Hashing.murmur3_128() 方法来创建 MurmurHash 实例。

<!-- guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
</dependency>

通过以上内容,就可以手写布隆过滤器了,代码如下:

import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import com.mike.common.core.utils.ArithmeticUtils;

import java.util.BitSet;

/**
 * 布隆过滤器
 */
public class MyBloomFilter {

    // 位数组,使用BitSet实现
    private final BitSet bits;
    // 位数组大小
    private final int bitSize;
    // 哈希函数的个数
    private final int hashFunctionCount;

    // 预期要存放的数据量
    private final int dataCount;
    // 期望的误判率
    private final double falsePositiveRate;

    /**
     * 构造方法私有
     */
    private MyBloomFilter(int dataCount, double falsePositiveRate) {
        // 设置预期要存放的数据量
        this.dataCount = dataCount;
        // 实则期望的误判率(如果期望的误判率为 0,则取 double 的最小值)
        this.falsePositiveRate = falsePositiveRate == 0? Double.MIN_VALUE: falsePositiveRate;
        // 计算所需的数组大小
        this.bitSize = getBitSize();
        // 计算所需 hash 函数的个数
        this.hashFunctionCount = getHashFunctionCount();
        // 创建位数组
        this.bits = new BitSet(this.bitSize);
    }

    /**
     * 创建布隆过滤器
     * @param dataCount 预期要存放的数据量
     * @param falsePositiveRate 期望的误判率
     * @return 自定义布隆过滤器
     */
    public static MyBloomFilter create(int dataCount, double falsePositiveRate) {
        return new MyBloomFilter(dataCount, falsePositiveRate);
    }

    /**
     * 获取 bit 数组的大小
     */
    private int getBitSize() {
        /*
         * 计算公式:
         *          m = -1 * (n*ln(p))/(ln(2)*ln(2))
         *
         *          m:数组大小
         *          n:预估要存的数据量
         *          p:误判率
         */
        return (int) (-this.dataCount * Math.log(this.falsePositiveRate) / (Math.log(2) * Math.log(2)));
        // 注意:Math.log(n) 就是求以自然数 e 为底 n 的对数
    }

    /**
     * 获取 hash 函数的数量
     */
    private int getHashFunctionCount() {
        /*
         * 计算公式:
         *          k = ln(2)*(m/n)
         *
         *          k:hash 函数的个数
         *          m:bitSize 数组的大小
         *          n:预估要存的数据量
         */
        return Math.max(1, (int) Math.round((double) this.bitSize / this.dataCount * Math.log(2)));
    }

    /**
     * 往布隆过滤器中添加数据
     * @param data 数据
     * @return 结果:true 表示插入成功,false 表示插入失败
     */
    public boolean add(String data) {
        // 先假设插入失败
        boolean insert = false;
        // 计算 hash 值
        long murmurHash = Hashing.murmur3_128().hashString(data, Charsets.UTF_8).asLong();
        int hash1 = (int)murmurHash;
        int hash2 = (int)(murmurHash >>> 32);
        for (int i = 1; i <= hashFunctionCount; i++) {
            // 通过 murmurHash 与哈希函数个数的序号得到一个新的 hash 值
            int hash = hash1 + i * hash2;
            if (hash < 0) {
                // 如果 hash 小于 0,则进行取反操作
                hash =~hash;
            }
            // 获取更新为 1 的数组坐标位置(取 % 可以让得到的数不超过数组的大小)
            int index = hash % bitSize;
            // 通过该坐标值判定该位置是否已被设置为 1
            boolean exist = bits.get(index);
            if (!exist) {
                // 未已设置过则进行更新
                bits.set(index, true);
                // 设置已插入
                insert = true;
            }
        }
        return insert;
    }

    /**
     * 检查数据是否存在
     */
    public boolean exist(String data) {
    	// 逻辑和 add 方法类似
        long murmurHash = Hashing.murmur3_128().hashString(data, Charsets.UTF_8).asLong();
        int hash1 = (int)murmurHash;
        int hash2 = (int)(murmurHash >>> 32);
        for (int i = 1; i <= this.hashFunctionCount; i++) {
            int hash = hash1 + i * hash2;
            if (hash < 0) {
                hash =~hash;
            }
            int index = hash % bitSize;
            if (!bits.get(index)) {
            	// 有一个数组位上没有被设置过,则表示不存在
                return false;
            }
        }
        return true;
    }
}

测试代码:

public class BloomFilterDemo {

    public static void main(String[] args) {
        // 预期存放十万个数据
        int dataCount = 1000000;
        // 预期误差率为 0.01
        double falsePositiveRate = 0.01;
        // 创建布隆过滤器
        MyBloomFilter bloomFilter = MyBloomFilter.create(dataCount, falsePositiveRate);

        // 添加数据
        for (int i = 0; i < dataCount; i++) {
            String data = String.valueOf(i);
            bloomFilter.add(data);
        }

        // 添加从 dataCount 起后 100000 个数据
        int falsePositiveCount = 0;
        for (int i = dataCount; i < dataCount+100000; i++) {
            String data = String.valueOf(i);
            if (bloomFilter.exist(data)) {
                // 如果判断存在,则表示误判了,统计
                falsePositiveCount++;
            }
        }

        System.out.println("误差个数:" + falsePositiveCount);
    }
}

运行 main() 方法,控制台信息如下:

在这里插入图片描述

可以看到误差率也是接近前面预设的 0.01%


五、guava 中的布隆过滤器

上面内容我们通过布隆过滤器的原理和一些推导公式,实现了布隆过滤器,不过一般也不会自己去手写布隆过滤器,因为有些包中已经实现了布隆过滤器,比如: guava

在前面布隆过滤器的实现中,有些代码我也是参考了 guava 中的 BloomFilter 去实现的

那如何使用 guava 给的布隆过滤器呢?

导入 guava 依赖(就是第四部分导入的依赖):

<!-- guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
</dependency>

我们可以使用 BloomFilter 中的 create() 方法来创造布隆过滤器

在这里插入图片描述

比如:创建一个针对存储字符串类型的布隆过滤器

        // 创建布隆过滤器
        BloomFilter<String> bloomFilter = BloomFilter.create(
                // 过滤器只存储字符串类型的数据,字符集为 uft-8
                Funnels.stringFunnel(Charsets.UTF_8),
                // 预期存放数据量
                dataCount, 
                // 预期误差率
                falsePositiveRate);

使用 put() 方法添加元素,mightContain() 方法判断元素是否存在

上述测试代码用 guava 的布隆过滤器可改写为:

package com.mike.spider;

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class BloomFilterDemo {

    public static void main(String[] args) {
        // 预期存放十万个数据
        int dataCount = 1000000;
        // 预期误差率为 0.01
        double falsePositiveRate = 0.01;
        // 创建布隆过滤器
        BloomFilter<String> bloomFilter = BloomFilter.create(
                // 过滤器只存储字符串类型的数据,字符集为 uft-8
                Funnels.stringFunnel(Charsets.UTF_8),
                // 预期存放数据量
                dataCount,
                // 预期误差率
                falsePositiveRate);
        // 添加数据
        for (int i = 0; i < dataCount; i++) {
            String data = String.valueOf(i);
            bloomFilter.put(data);
        }

        // 添加从 dataCount 起后 100000 个数据
        int falsePositiveCount = 0;
        for (int i = dataCount; i < dataCount+100000; i++) {
            String data = String.valueOf(i);
            if (bloomFilter.mightContain(data)) {
                // 如果判断存在,则表示误判了,统计
                falsePositiveCount++;
            }
        }

        System.out.println("误差个数:" + falsePositiveCount);
    }
}

参考文章:

Java实现布隆过滤器的几种方式:https://blog.csdn.net/weixin_43888891/article/details/131407465

布隆过滤器(一):https://hardcore.feishu.cn/docs/doccntUpTrWmCkbfK1cITbpy5qc

布隆过滤器(Bloom Filter)- 原理、实现和推导:https://blog.csdn.net/hlzgood/article/details/109847282

布隆过滤器讲解及基于Guava BloomFilter案例:https://blog.csdn.net/weixin_42675423/article/details/130025590

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/308922.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 145. 二叉树的后序遍历

145. 二叉树的后序遍历 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&…

Vue3:vue-cli项目创建及vue.config.js配置

一、node.js检测或安装&#xff1a; node -v node.js官方 二、vue-cli安装&#xff1a; npm install -g vue/cli # OR yarn global add vue/cli/*如果安装的时候报错&#xff0c;可以尝试一下方法 删除C:\Users**\AppData\Roaming下的npm和npm-cache文件夹 删除项目下的node…

C语言基础语法跟练 day2

题源&#xff1a;牛客网 16、BoBo写了一个十六进制整数ABCDEF&#xff0c;他问KiKi对应的十进制整数是多少。 #include <stdio.h>int main() { //创建变量char arr[] "ABCDEF";int i;int sum0,c; //依次转换十六进制为十进制for(i0; arr[i]!\0; i){char b …

每日学习更新(LQR+iLQR)

一直想更新一下根据cost to go来推导LQR&#xff0c;之前的话可能会直接套问题&#xff0c;但是对于理论有些困惑&#xff0c;正好最近在学习ilqr轨迹生成/优化&#xff0c;因此来推一下公式&#xff0c;以下参考B站Dr_CAN&#xff0c;链接如下&#xff1a; 【最优控制】5_线性…

记录汇川:H5U与Fctory IO测试6

主程序&#xff1a; 子程序: IO映射 子程序&#xff1a; 辅助上料 子程序&#xff1a; 自动程序 Fctory IO配置&#xff1a; 实际动作如下&#xff1a; Fctory IO测试6

软件测试工具Robot Framework如何安装

安装文件准备 表1 安装文件准备 Robot框架结构 为了更好的了解环境安装&#xff0c;我们先看下框架结构&#xff1a; 图1 Robot Framework Architecture Robot Framework 通过导入不同的库&#xff0c;就可以使用库中所提供的关键字&#xff0c;从而时行相关的测试。有几个标…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷②

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷2 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷2 模块一 …

大众汽车宣布将ChatGPT,批量集成在多种汽车中!

1月9日&#xff0c;大众汽车在官网宣布&#xff0c;将ChatGPT批量集成到电动、内燃机汽车中。 大众表示&#xff0c;将ChatGPT与其IDA语音助手相结合&#xff0c;用户通过自然语言就能与ChatGPT进行互动&#xff0c;例如&#xff0c;帮我看看最近的三星米其林饭店在哪里&#…

Redis系列-15.Redis的IO多路复用原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

Java顺序表(1)

&#x1f435;本篇文章将对顺序表中的方法进行模拟实现 一、线性表 线性表是指在逻辑结构上呈连续的线性结构&#xff0c;而在物理结构上不一定是连续的结构&#xff0c;常见的线性表有&#xff1a;顺序表、链表、栈、队列等 二、顺序表 顺序表一般采用数组来存储数据&#x…

ELF文件格式解析二

使用objdump命令查看elf文件 objdump -x 查看elf文件所有头部的信息 所有的elf文件。 程序头部&#xff08;Program Header&#xff09;中&#xff0c;都以 PT_PHDR和PT_INTERP先开始。这两个段必须在所有可加载段项目的前面。 从上图中的INTERP段中&#xff0c;可以看到改段…

Android App打包加固后的APK无法安装问题

最近开发的一个应用要上架&#xff0c;正常流程打完包后去加固&#xff0c;由于以前一直用的是360的加固助手&#xff0c;这里开始也是选择用它。 使用360加固&#xff1a; 问题一、开始出现的问题是说应用未签名无法加固&#xff0c;我明明是签名后打的包&#xff0c;怎么会…

十分钟部署清华 ChatGLM-6B,实测效果超预期(Linux版)

前段时间&#xff0c;清华公布了中英双语对话模型 ChatGLM-6B&#xff0c;具有60亿的参数&#xff0c;初具问答和对话功能。 最&#xff01;最&#xff01;最重要的是它能够支持私有化部署&#xff0c;大部分实验室的服务器基本上都能跑起来。因为条件特殊&#xff0c;实验室网…

介绍几种常见的质数筛选法

质数筛选法 1.暴力筛选法 :smirk:2.普通优化 :rofl:3.埃氏筛法:cold_sweat:4.线性筛选法:scream: 质数&#xff1a;除了1和他本身没有其它因数的正整数就是质数。1不是质数&#xff0c;2是质数。 1.暴力筛选法 &#x1f60f; 原理 求x的质数&#xff0c;令y从2到 x \sqrt[]{x…

资源素材网站源码,功能齐备,界面干净整洁,附带安装教程

搭建教程 简单安装说明&#xff1a; 1、整站程序上传后台 2、然后导入数据库文件到数据库&#xff0c; 3、修改conf里面的conf的数据库名字及密码 4、配置伪静态 规则&#xff1a; location ~* \.(htm)$ { rewrite "^(.*)/(.?).htm(.*?)$" $1/index.php?$2…

Docker安装Jenkins,配置Maven和Java

前言 这是一个java的springboot项目&#xff0c;使用maven构建 安装准备 需要将maven和jdk安装在服务器上&#xff0c;Jenkins需要用到&#xff0c;还有创建一个jenkins的目录&#xff0c;安装命令如下&#xff1a; docker run -d -uroot -p 9095:8080 -p 50000:50000 --n…

20240109适配selinux让移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通

20240109适配selinux让移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通 2024/1/9 10:46 缘起&#xff1a;使用友善之臂的Android11可以让EC20上网&#xff0c;但是同样的修改步骤&#xff0c;Toybrick的Android11不能让EC20上网。 最后确认是selinux的问题&#…

UGUI Image图像控件替换图片

代码为探索而来&#xff0c;不是最优代码&#xff0c;请按需使用。 Unity3d引擎版本&#xff1a;Uinty3d 20233.2.3f1 补充一下图片如何改成Texture2D&#xff1a; 1、将图片导入unity。 2、选择图片&#xff0c;按下图操作&#xff0c;点击应用即可。 脚本代码&#xff1a…

【ECShop电子商务系统__软件测试作业】ECSHOP系统搭建文档+接口测试用例+接口文档+接口测试脚本

一、选题题目可选《ECShop电子商务系统》、《EPShop电子商城系统》或者自选其它的开源系统(至少有十个以上的功能模块的系统&#xff0c;不得选功能少、简单的系统)。 软件测试作业 说明:接口测试相关资料 二、具体要求 1、搭建测试系统并写出搭建被测系统的全过程。 2、根…

Spark---RDD序列化

文章目录 1 什么是序列化2.RDD中的闭包检查3.Kryo 序列化框架 1 什么是序列化 序列化是指 将对象的状态信息转换为可以存储或传输的形式的过程。 在序列化期间&#xff0c;对象将其当前状态写入到临时或持久性存储区。以后&#xff0c;可以通过从存储区中读取或反序列化对象的…