用户画像clickhouse位图函数实践总结(代码片段)

扫地增 扫地增     2022-12-05     284

关键词:

1 位图概念

说到位图我们就不得不从位算开始,虽然大部分语言都有提供位运算,但是,并没有提供一种类似于位数组的类型,要使用这些位运算,我们只能通过数字类型来实现,比如Java中的int/long等类型。而这些数字类型的数组,我们一般可以称之为“位图”(BitMap)。
位图bitmap 是一种非常常用的结构,在索引,数据压缩等方面有广泛应用。所谓的 bitmap 就是用一个 bit 位来标记某个元素对应的 value, 而 key 即是该元素。由于采用了 bit 为单位来存储数据,因此在存储空间方面,可以大大节省。

2 位图函数

2.1 位图函数作用

位图函数用于对两个位图对象进行计算,对于任何一个 位图函数,它都将返回一个 位图对象,例如and,or,xor,not等等。比如:x y就是位图对象,f就是位图函数,f(x,y)就是位图对象。
在这里插入图片描述

2.2 位图函数构造方法

位图对象有两种构造方法。一个是由聚合函数groupBitmapState构造的,另一个是由Array Object构造的。同时还可以将位图对象转化为数组对象。

我们使用RoaringBitmap实际存储位图对象,当基数小于或等于32时,它使用Set保存。当基数大于32时,它使用RoaringBitmap保存。这也是为什么低基数集的存储更快的原因。

2.3 位图函数的基本分类

在这里插入图片描述

2.4 位图函数基本使用

2.4.1 数据准备

CREATE TABLE test.bit_map
(
    `user_id` UInt64
)
ENGINE = MergeTree
ORDER BY user_id
SETTINGS index_granularity = 8192

数据如下:

┌─user_id─┐
│       1 │
│       2 │
│       3 │
│       4 │
│       5 │
│       6 │
│       7 │
│       8 │
│       9 │
│      10 │
│      11 │
└─────────┘

2.4.2 构造位图

2.4.2.1 groupBitmapState

  • 参数类型: 函数的参数必须为UInt64
  • 返回值类型 : AggregateFunction(groupBitmap,UInt64)
  • 使用实例:
SELECT groupBitmapState(toUInt64(user_id)) as a,
       toTypeName(a)
from test.bit_map;

在这里插入图片描述

2.4.2.2 bitmapBuild

  • 参数类型: 无符号整数数组array
  • 返回值类型: 位图对象AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))) AS user_bit_map,
       toTypeName(user_bit_map)
FROM test.bit_map

在这里插入图片描述

2.4.3 位图对象转化为数组对象

2.4.3.1 bitmapToArray(bitmap)

  • 作用: 将位图转换为整数数组。
  • 参数类型: bitmap-位图对象 AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 整数数组
  • 使用实例:
SELECT
    bitmapToArray(groupBitmapState(toUInt64(user_id))) AS user_bit_map,
    toTypeName(user_bit_map)
FROM test.bit_map

在这里插入图片描述

2.4.4 位图对象的属性

2.4.4.1 bitmapContains

  • 语法:
bitmapContains(haystack, needle)
  • 作用: 检查位图是否包含指定元素
  • 参数类型: haystack - 位图对象, AggregateFunction(groupBitmap, UInt64)needle - 元素,类型UInt32
  • 返回值: 返回值为1,0。包含为1,反之为0。本质在这里可以理解为boolean类型,或者枚举类型。
  • 使用实例:
SELECT bitmapContains(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(9))AS user_bit_map,
       toTypeName(user_bit_map)
FROM test.bit_map

在这里插入图片描述

2.4.4.2 bitmapCardinality

  • 语法:
bitmapCardinality(bitmap)
  • 作用: 返回一个UInt64类型的数值,表示位图对象的基数。
  • 参数类型: bitmap – 位图对象, AggregateFunction(groupBitmap, UInt64)
  • 返回值: UInt64,可以理解为位图对象数组中元素的个数。
  • 使用实例:
SELECT bitmapCardinality(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id))))) AS user_bit_map,
       toTypeName(user_bit_map)
FROM test.bit_map

在这里插入图片描述

2.4.4.3 bitmapMin

  • 语法:
bitmapMin(bitmap)
  • 作用: 返回位图中的最小值。
  • 参数类型: bitmap – 位图对象, AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个UInt64类型的数值,表示位图中的最小值。如果位图为空则返回UINT32_MAX
  • 使用实例:
SELECT bitmapMin(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id))))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type,
       bitmapMin(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(nan))))) AS user_bit_map_1,
       toTypeName(user_bit_map) AS user_bit_map_1_type
FROM test.bit_map

在这里插入图片描述

2.4.4.4 bitmapMax

  • 作用: 返回位图中的最大值。
  • 语法:
bitmapMax(bitmap)
  • 参数类型: bitmap – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个UInt64类型的数值,表示位图中的最大值。如果位图为空则返回0
  • 使用实例:
SELECT bitmapMax(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id))))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type,
       bitmapMax(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(nan))))) AS user_bit_map_1,
       toTypeName(user_bit_map) AS user_bit_map_1_type
FROM test.bit_map

在这里插入图片描述

2.4.5 位图转换为新位图

2.4.5.1 bitmapSubsetInRange

  • 语法:
bitmapSubsetInRange(bitmap, range_start, range_end)
  • 作用: 返回一个新的子位图。
  • 参数类型: bitmap – 位图对象, AggregateFunction(groupBitmap, UInt64)range_start – 范围起始点(含),类型为UInt32range_end – 范围结束点(不含),类型UInt32
  • 返回值类型: 返回一个新的子位图。
  • 使用实例:
SELECT bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.5.2 bitmapSubsetLimit

  • 语法:
bitmapSubsetLimit(bitmap, range_start, limit)
  • 作用: 将位图指定范围(起始点和数目上限)转换为另一个位图
  • 参数: bitmap – 位图对象,AggregateFunction(groupBitmap, UInt64)range_start – 范围起始点(含),类型为UInt32limit – 子位图基数上限,类型为UInt32
  • 返回值类型: 返回一个新的子位图,AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapSubsetLimit(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6 位图运算

2.4.6.1 bitmapHasAny

  • 作用:hasAny(array,array)类似,判断两个位图对象是包含相同的元素。
  • 语法:
bitmapHasAny(bitmap,bitmap)
  • 参数类型: bitmap – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 如果位图有任何公共元素则返回1,否则返回0。
    对于空位图,返回0。
  • 使用实例:
SELECT bitmapHasAny(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(1),toUInt32(7))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.2 bitmapHasAll

  • 作用:hasAll(array,array)类似,判断第一个位图是否包含第二个位图的所有元素。
  • 语法:
bitmapHasAll(bitmap,bitmap)
  • 参数类型: bitmap – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 如果第一个位图包含第二个位图的所有元素,则返回1,否则返回0。如果第二个参数是空位图,则返回1。
  • 使用实例:
SELECT bitmapHasAll(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.3 bitmapAnd 与

  • 作用: 为两个位图对象进行与操作,返回一个新的位图对象。
  • 语法:
bitmapAnd(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个新的位图对象。类型为AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapAnd(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.4 bitmapOr 或

  • 作用: 为两个位图对象进行或操作,返回一个新的位图对象
  • 语法:
bitmapOr(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个新的位图对象。类型为AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapOr(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.5 bitmapXor 异或

  • 作用: 为两个位图对象进行异或操作,返回一个新的位图对象。
  • 语法:
bitmapXor(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个新的位图对象。类型为AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapXor(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.6 bitmapAndnot

  • 作用: 计算两个位图的差异,返回一个新的位图对象。
  • 语法:
bitmapAndnot(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回一个新的位图对象。类型为AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapAndnot(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.7 bitmapAndCardinality

  • 作用: 为两个位图对象进行与操作,返回结果位图的基数。
  • 语法:
bitmapAndCardinality(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回结果位图的基数。类型为UInt64
  • 使用实例:
SELECT bitmapAndCardinality(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.8 bitmapOrCardinality

  • 作用: 为两个位图进行或运算,返回结果位图的基数。
  • 语法:
bitmapOrCardinality(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回结果位图的基数。类型为UInt64
  • 使用实例:
SELECT bitmapOrCardinality(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

2.4.6.9 bitmapXorCardinality

  • 作用: 为两个位图进行异或运算,返回结果位图的基数。
  • 语法:
bitmapXorCardinality(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回结果位图的基数。类型为UInt64
  • 使用实例:
SELECT bitmapXorCardinality(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.10 bitmapAndnotCardinality

  • 作用: 计算两个位图的差异,返回结果位图的基数。
  • 语法:
bitmapAndnotCardinality(bitmap1,bitmap2)
  • 参数类型: bitmap1 – 位图对象,AggregateFunction(groupBitmap, UInt64)bitmap2 – 位图对象,AggregateFunction(groupBitmap, UInt64)
  • 返回值类型: 返回结果位图的基数。类型为UInt64
  • 使用实例:
SELECT bitmapAndnotCardinality(bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(6),toUInt32(10)),bitmapSubsetInRange(bitmapBuild(bitmapToArray(groupBitmapState(toUInt64(user_id)))),toUInt32(7),toUInt32(9))) AS user_bit_map,
       toTypeName(user_bit_map) AS user_bit_map_type
FROM test.bit_map

在这里插入图片描述

2.4.6.11 bitmapTransform

  • 作用: 将位图中的值数组转换为另一个值数组,结果是一个新的位图。
  • 语法:
bitmapTransform(bitmap, from_array, to_array)
  • 参数介绍:
  1. bitmap – 位图对象,类型为AggregateFunction(groupBitmap, UInt64)
  2. from_array – 类型为Array(UInt32)。对于范围[0,from_array.size()]中的idx,如果bitmap包含from_array[idx],则将其替换为to_array[idx]。注意,如果from_arrayto_array之间有公共元素,则结果取决于数组排序。
  3. to_array – 类型为Array(UInt32), 它的大小应该与from_array相同。
  • 返回值类型:
    返回结果位图对象。类型为AggregateFunction(groupBitmap, UInt64)
  • 使用实例:
SELECT bitmapToArray(bitmapTransform(bitmapBuild([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), cast([5,999,2] as Array(UInt32)), cast([2,888,20] as Array(UInt32)))) AS res

在这里插入图片描述

位图总结:

通过以上我们了解到位图对象有两种构造方法。其一是由聚合函数groupBitmapState构造的,其二是由Array Object构造的。同时还可以将位图对象转化为数组对象。对于任何一个位图函数,计算结果都将返回一个位图对象。带有Has的判断函数返回的数值是逻辑值0或者1;其中带有Cardinality后缀的函数返回的数值是运算后的结果位图的基数;其他的位图运算返回的是结果位图,例如And,Or,Xor,Andnot等。到此我们关于clickhouse位图的讲解就结束了,希望可以帮到大家。

转转用户画像平台实践(代码片段)

目录:背景什么是用户画像标签画像的应用场景转转用户画像平台的实践系统结构图标签画像的构建原则标签类型和规则标签的生产加工标签的存储设计用户洞察用户分群计算ID-MAPPING未来规划总结1.背景转转作为二手电商交... 查看详情

转转用户画像平台实践(代码片段)

目录:背景什么是用户画像标签画像的应用场景转转用户画像平台的实践系统结构图标签画像的构建原则标签类型和规则标签的生产加工标签的存储设计用户洞察用户分群计算ID-MAPPING未来规划总结1.背景转转作为二手电商交... 查看详情

基于clickhouse的用户行为(路径)分析实践(代码片段)

前言ClickHouse为用户提供了丰富的多参聚合函数(parametricaggregatefunction)和基于数组+Lambda表达式的高阶函数(higher-orderfunction),将它们灵活使用可以达到魔法般的效果。在我们的体系中,ClickHouse定位点... 查看详情

《clickhouse企业级应用:入门进阶与实战》8基于clickhousebitmap实现dmp用户画像标签圈人

《ClickHouse企业级应用:入门、进阶与实战》全面了解ClickHouse快速入门ClickHouse基础数据类型ClickHouse高级数据类型ClickHouse函数ClickHouseSQL基础基于SpringBoot开发ClickHouse SQL查询工具基于ClickHouseBitmap实现DMP用户画像标签圈人根据Dig... 查看详情

《clickhouse企业级应用:入门进阶与实战》8基于clickhousebitmap实现dmp用户画像标签圈人

《ClickHouse企业级应用:入门、进阶与实战》全面了解ClickHouse快速入门ClickHouse基础数据类型ClickHouse高级数据类型ClickHouse函数ClickHouseSQL基础基于SpringBoot开发ClickHouse SQL查询工具基于ClickHouseBitmap实现DMP用户画像标签圈人根据Dig... 查看详情

我用mrs-clickhouse构建的用户画像系统,让老板拍手称赞(代码片段)

摘要:在移动互联网时代,用户数量庞大,标签数量众多,用户标签的数据量巨大。用户画像系统中,对于标签的存储和查询,不同的企业有不同的实现方案。当前主流的实现方案采用ElasticSearch方案。但... 查看详情

spark+es+clickhouse构建dmp用户画像(代码片段)

环境搭建环境搭建环境搭建一、数据上传到hdfs二、创建临时内部分区表三、创建外部压缩表四、数据插入到临时表五、数据从临时表插入到外部压缩表六、删除临时表七、hive创建hbase表的映射关系八、hbase表创建九、hive插入数... 查看详情

clickhouse实践关于clickhouse对空值的处理总结(代码片段)

1背景在工作中,我们在使用sparkdataset向clickhouse向表中批量插入数据时,经常遇到某个字段为NULL导致导数任务失败。而我们在clickhouse按照正常方式建表时,我们并不能保证每条插入的数据的每个字段都是非NULL值。基... 查看详情

基于clickhouse的用户行为(路径)分析实践(代码片段)

前言ClickHouse为用户提供了丰富的多参聚合函数(parametricaggregatefunction)和基于数组+Lambda表达式的高阶函数(higher-orderfunction),将它们灵活使用可以达到魔法般的效果。在我们的体系中,ClickHouse定位点... 查看详情

个推用户画像的实践与应用

“以用户为核心”的概念在互联网时代深入人心,然而要真正了解用户懂得用户,就不得不提到“用户画像”。随着大数据技术的深入研究与应用,借助用户画像,企业或APP可以深入挖掘用户需求,从而实现精细化运营以及为精... 查看详情

clickhouse场景和未来的一些发展方向

一、ClickHouse现在有哪些最新场景应用呢?ClickHouse过去最常见的场景有三个:用户行为分析:在采集用户行为日志之后,进行PV、UV、留存、转化漏斗等操作,例如头条、快手、喜马拉雅等。用户画像圈选:... 查看详情

知乎用户画像与实时数据架构实践

...,我是云祁!今天和大家分享知乎侯容老师关于用户画像和实时数据架构实践的干货。侯容:知乎数据赋能组Leader,主要负责实时数据、用户理解方向。一、前言‍‍‍‍‍‍‍‍知乎业务中,随着各业务线... 查看详情

flink规则引擎实践分享(代码片段)

...则、条件查询封装**3.1规则封装3.2查询规则封装四、基于ClickHouse实现用户行为明细查询服务支持4.1ClickHouse从Kafka摄取数据4.2ClickHouse查询服务中的sql设计**4.3ClickHouse查询时间跨度问题与解决**4.4查询路由模块**五、缓存模块 查看详情

产品方法论总结——用户画像&用户场景

...sp;作为产品人,在逛专业网站看前辈分析产品时都会分析用户画像,一般都是分析用户的年龄、性别、地域分布等特点。今天想分享一下梁宁老师的两个用户画像分析模式,一个是:第一只羊,头羊,狼;另一个是:大明,笨笨... 查看详情

实时分析之客户画像项目实践

客户画像的背景描写叙述原来的互联网,以解决用户需求为目的。衍生出众多的网联网产品,以及产生呈数量级递增的海量数据。当用户需求基本得到满足的时候,须要分析这些海量的数据。得以达到最高效的需求实现,最智能... 查看详情

个推用户画像产品(个像)android集成实践

我们团队之前一直是个推推送的忠实用户,近期个推新推出了产品“个像·用户画像”,刚好非常契合我们的业务需求,于是我们也试用了一下。总的来说效果还不错,这篇文章就为大家介绍一下如何从零开始快速集成个像Android... 查看详情

应用实践|知乎用户画像与实时数据的架构与实践

数据的及时性是指能否在需要的时候获到数据,数据的及时性与企业的数据处理速度及效率有直接的关系,是影响业务处理和管理效率的关键指标。02    解决方案(1)全流程的数据链路和各级质量保证方法 &nb... 查看详情

clickhouse实践clickhouse中如何实现row_number()over(partitionby‘xxx‘orderby‘xxx‘desc/asc)(代码片段)

...对数据集生成顺序编号或者进行数据去重的操作。然而在Clickhouse中没有提供该功能的函数,那么在clickhouse我们要想实现类似的功能我们应该如何实现呢?今天我们就来用实例说明 查看详情