字符串 P39

Redis 的字符串是一个有字节组成的序列,可以存储以下 3 种类型的值:字节串(byte string)、整数、浮点数。

在需要的时候, Redis 会将整数转换成浮点数。整数的取值范围和系统的长整型(long)的相同,浮点数取值范围和精度与 IEEE 754 标准下的双精度浮点数(double)的相同。

Redis 中的自增命令和自减命令 P39

相关演示代码如下(mainhandleResult 定义见:01. Redis 数据结构简介.md):

// 执行字符串类型数字相关操作
func executeNumberOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "number")))// 获取值,输出 -> ERROR:  redigo: nil returnedhandleResult(redis.Int(conn.Do("GET", "number")))// 自增 1,返回自增后的值 -> 1handleResult(redis.Int(conn.Do("INCR", "number")))// 自增 2,返回自增后的值 -> 3handleResult(redis.Int(conn.Do("INCRBY", "number", "2")))// 自减 1,返回自减后的值 -> 2handleResult(redis.Int(conn.Do("DECR", "number")))// 自减 2,返回自减后的值 -> 0handleResult(redis.Int(conn.Do("DECRBY", "number", "2")))// 自增 1.5,返回自增后的值 -> 1.5handleResult(redis.Float64(conn.Do("INCRBYFLOAT", "number", "1.5")))// 自增 -1.3,返回自增后的值 -> 0.2handleResult(redis.Float64(conn.Do("INCRBYFLOAT", "number", "-1.3")))
}

供 Redis 处理子串和二进制位的命令 P40

相关演示代码如下:

// 执行字符串类型字符串相关操作
func executeStringOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "string")))// 追加串,返回当前字符串长度 -> 6,值变为 -> appendhandleResult(redis.Int(conn.Do("APPEND", "string", "append")))// 获取子串,返回 -> enhandleResult(redis.String(conn.Do("GETRANGE", "string", 3, 4)))// 设置子串,返回当前字符串长度 -> 6,值变为 -> appledhandleResult(redis.Int(conn.Do("SETRANGE", "string", 3, "le")))// 设置子串,返回当前字符串长度 -> 11,值变为 -> applicationhandleResult(redis.Int(conn.Do("SETRANGE", "string", 3, "lication")))// 获取二进制位,返回 -> 1// (获取第 7/8 个字符 a 在二进制下第 7%8 位上的二进制位,即 0110 0001 的第 7 位 1)handleResult(redis.Int(conn.Do("GETBIT", "string", 7)))// 设置二进制位,返回原来的二进制位 -> 0,值变为 -> cpplication// (设置第 6/8 个字符 a 在二进制下第 6%8 位上的二进制位为1,即 0110 0001 变为 0110 0011)handleResult(redis.Int(conn.Do("SETBIT", "string", 6, 1)))// 统计二进制位,返回 -> 7// (统计 [0, 1] 范围内子串 cp 在二进制下 0110 0011 0111 0000 二进制位为 1 的数量)handleResult(redis.Int(conn.Do("BITCOUNT", "string", 0, 1)))handleResult(redis.String(conn.Do("SET", "aKey", "aa")))handleResult(redis.String(conn.Do("SET", "bKey", "b")))// 对 aa(0110 0001 0110 0001) 和 b(0110 0010 0000 0000) 进行 按位或,结果存储到 cKey 中// 返回字符串长度 -> 2,值为 ca(0110 0011 0110 0001),handleResult(redis.Int(conn.Do("BITOP", "OR", "cKey", "aKey", "bKey")))
}

Redis 可以通过使用子串操作和二进制位操作,配合 WATCHMULTIEXEC 命令(后面会初步介绍,以后将深入讲解),构建任何想要的数据结构。

列表 P42

一些常用的列表命令 P42

相关演示代码如下:

// 执行列表类型相关操作
func executeListOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "list")))// 右端插入一次插入 a, b, c,返回当前列表长度 -> 3,列表变为 -> a b chandleResult(redis.Int(conn.Do("RPUSH", "list", "a", "b", "c")))// 左端插入一次插入 d, e, f,返回当前列表长度 -> 6,列表变为 -> f e d a b chandleResult(redis.Int(conn.Do("LPUSH", "list", "d", "e", "f")))// 弹出并返回列表最右端的值,返回 -> c,列表变为 -> f e d a bhandleResult(redis.String(conn.Do("RPOP", "list")))// 弹出并返回列表最左端的值,返回 -> f,列表变为 -> e d a bhandleResult(redis.String(conn.Do("LPOP", "list")))// 返回左端开始下标偏移量为 offset 的值,返回 -> dhandleResult(redis.String(conn.Do("LINDEX", "list", 1)))// 移除列表左端开始 [1, 2] 范围外的所有元素,列表变为 -> d ahandleResult(redis.String(conn.Do("LTRIM", "list", 1, 2)))
}

利用 LTRIM 命令可以原子地弹出多个元素。 P43

阻塞式的列表弹出命令以及在列表之间移动元素的命令 P43

相关演示代码如下:

// 执行列表类型阻塞相关操作
func executeListBlockOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "source", "destination")))// 从第一个非空列表中弹出并返回列表最左端的值,最多等待 1秒,输出 -> ERROR:  redigo: nil returnedhandleResult(redis.Strings(conn.Do("BLPOP", "source", "destination", 1)))// 初始化handleResult(redis.Int(conn.Do("RPUSH", "source", "a", "b", "c")))handleResult(redis.Int(conn.Do("RPUSH", "destination", "d", "e", "f")))// 从第一个非空列表中弹出并返回列表最左端的值,无限等待,返回 -> a,source 变为 -> b c,destination 变为 -> d e fhandleResult(redis.Strings(conn.Do("BLPOP", "source", "destination", 0)))// 从第一个非空列表中弹出并返回列表最右端的值,无限等待,返回 -> f,source 变为 -> b c,destination 变为 -> d ehandleResult(redis.Strings(conn.Do("BRPOP", "destination", "source", 0)))// 从 source 弹出最右端元素,然后推入到 destination 最左端,并返回这个元素// 返回 -> c,source 变为 -> b,destination 变为 -> c d ehandleResult(redis.String(conn.Do("RPOPLPUSH", "source", "destination")))// 从 source 弹出最右端元素,然后推入到 destination 最左端,并返回这个元素,无限等待// 返回 -> b,source 变为 -> <nil>,destination 变为 -> b c d ehandleResult(redis.String(conn.Do("BRPOPLPUSH", "source", "destination", 0)))// 从 source 弹出最右端元素,然后推入到 destination 最左端,并返回这个元素,最多等待 1秒// 输出 -> ERROR:  redigo: nil returned,source 变为 -> <nil>,destination 变为 -> b c d ehandleResult(redis.String(conn.Do("BRPOPLPUSH", "source", "destination", 1)))
}

对于阻塞弹出命令和弹出并推入命令,最常见的用例就是消息传递(messaging)和任务队列(task queue),将在以后对这两个主题进行介绍。 P44

练习题:通过列表来降低内存占用 P44

在上篇文章中,我们使用了有序集合来记录用户最近浏览过的商品,并把用户浏览这些商品时的时间戳设置为分值,从而使得程序可以在清理旧会话的过程中或是在执行完购买操作后,进行相应的数据分析。但由于保存时间戳需要占用相应的空间,所以如果分析操作并不需要用到时间戳的话,那么就没有必要使用有序集合来保存用户最近浏览过的商品了。为此,请在保证语义不变的情况下,将 UpdateToken 函数里面是用的有序集合替换成列表。

提示:如果在解答这个问题时遇上困难的话,可以到 6.1.1 节中找找灵感。

  • 由于列表是有序的,所有最新访问的一定在列表的左端,所以每次操作时先删除列表中这个访问记录,再推入列表左端,最后修剪列表为长度为 25 即可。由于每次需要遍历整个列表,所以时间复杂度较高,但是列表长度总共只有 25 ,时间上相差不大,但是空间可以节省很多。

go // 更新最近商品访问列表 func UpdateLatestViewedItem(conn redis.Conn, itemId int) { // 移除列表中所有值为 itemId 的元素 _ = conn.Send("LREM", "latestViewedItem", 0, itemId) // 将最近访问的商品推入列表最左端 _ = conn.Send("LPUSH", "latestViewedItem", itemId) // 修剪列表,保留最近访问的 25 个 _ = conn.Send("LTRIM", "latestViewedItem", 0, 24) // 执行上述命令 _ = conn.Flush() }

集合 P44

一些常用的集合命令 P45

相关演示代码如下:

// 执行集合类型相关操作
func executeSetOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "source", "destination")))// 集合中添加三个元素,输出 -> 6,source 变为 -> 1 2 3 4 5 6 7handleResult(redis.Int(conn.Do("SADD", "source", 1, 2, 3, 4, 5, 6, 7, 1)))// 从集合中删除两个元素: 1 2,输出 -> 2,source 变为 -> 3 4 5 6 7handleResult(redis.Int(conn.Do("SREM", "source", 1, 2)))// 判断集合是否含有元素 3,输出 -> 1handleResult(redis.Int(conn.Do("SISMEMBER", "source", 3)))// 返回集合的元素个数,输出 -> 5handleResult(redis.Int(conn.Do("SCARD", "source")))// 返回集合的所有元素,输出 -> [3 4 5 6 7]handleResult(redis.Ints(conn.Do("SMEMBERS", "source")))// 随机返回集合中不同的 3 个元素,输出 -> [6 5 3] (随机结果可能存在不同,以实际为准)handleResult(redis.Ints(conn.Do("SRANDMEMBER", "source", 3)))// 随机返回集合中可重复的 6 个元素,输出 -> [7 5 6 3 7 6] (随机结果可能存在不同,以实际为准)handleResult(redis.Ints(conn.Do("SRANDMEMBER", "source", -6)))// 随机删除集合中的 1 个元素,输出 -> 3 ,source 变为 -> 4 5 6 7(随机结果可能存在不同,以实际为准)handleResult(redis.Int(conn.Do("SPOP", "source")))// 移动 source 集合中的元素 7 到 destination 集合中(由于前面存在随机,结果可能存在不同,以实际为准)// 输出 -> 1 ,source 变为 -> 4 5 6 ,destination 变为 -> 7handleResult(redis.Int(conn.Do("SMOVE", "source", "destination", 7)))
}

用于组合和处理多个集合的命令 P45

相关演示代码如下:

// 执行集合类型多个集合相关操作
func executeSetMutiOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "source_1", "source_2", "source_3", "destination")))// 初始化handleResult(redis.Int(conn.Do("SADD", "source_1", 1, 2, 4, 8)))handleResult(redis.Int(conn.Do("SADD", "source_2", 2, 3, 4, 5)))handleResult(redis.Int(conn.Do("SADD", "source_3", 5, 6, 7, 8)))// 返回三个集合的差集,输出 -> [1]handleResult(redis.Ints(conn.Do("SDIFF", "source_1", "source_2", "source_3")))// 将三个集合的差集存储到 destination 中,输出 -> 1,destination 变为 -> 1handleResult(redis.Int(conn.Do("SDIFFSTORE", "destination", "source_1", "source_2", "source_3")))// 返回两个集合的交集,输出 -> [2 4]handleResult(redis.Ints(conn.Do("SINTER", "source_1", "source_2")))// 将两个集合的交集存储到 destination 中,输出 -> 2,destination 变为 -> 2 4handleResult(redis.Int(conn.Do("SINTERSTORE", "destination", "source_1", "source_2")))// 返回三个集合的并集,输出 -> [1 2 3 4 5 6 7 8]handleResult(redis.Ints(conn.Do("SUNION", "source_1", "source_2", "source_3")))// 将三个集合的并集存储到 destination 中,输出 -> 8,destination 变为 -> 1 2 3 4 5 6 7 8handleResult(redis.Int(conn.Do("SUNIONSTORE", "destination", "source_1", "source_2", "source_3")))
}

哈希表 P46

用于添加和删除键值对的散列操作 P47

相关演示代码如下:

// 执行哈希表类型相关操作
func executeHashOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "hash")))// 向哈希表中设置一个或多个 field 的值,输出 -> OK,hash 变为 -> {field_1: value_1, field_2: value_2, field_3: value_3}handleResult(redis.String(conn.Do("HMSET", "hash", "field_1", "value_1", "field_2", "value_2", "field_3", "value_3")))// 从哈希表中获取一个或多个 field 的值,输出 -> [value_1 value_3 value_2]handleResult(redis.Strings(conn.Do("HMGET", "hash", "field_1", "field_3", "field_2")))// 从哈希表中删除一个或多个 field 的值,输出 -> 2,hash 变为 -> field_2: value_2}handleResult(redis.Int(conn.Do("HDEL", "hash", "field_1", "field_3")))// 返回哈希表中包含的 field 的数量,输出 -> 1handleResult(redis.Int(conn.Do("HLEN", "hash")))
}

哈希表的更高级特性 P47

相关演示代码如下:

// 执行哈希表类型高级特性相关操作
func executeHashFeatureOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "hash")))// 初始化handleResult(redis.String(conn.Do("HMSET", "hash", "field_1", "value_1", "field_2", "value_2", "field_3", "3")))// 判断 field 是否存在于哈希表中,输出 -> 1handleResult(redis.Int(conn.Do("HEXISTS", "hash", "field_1")))// 返回哈希表中所有的 field,输出 -> [field_1 field_2 3]handleResult(redis.Strings(conn.Do("HKEYS", "hash")))// 返回哈希表中所有 field 的值,输出 -> [value_1 value_2 value_3]handleResult(redis.Strings(conn.Do("HVALS", "hash")))// 返回哈希表中所有的 field 及其值,输出 -> map[field_1:value_1 field_2:value_2 field_3:3]handleResult(redis.StringMap(conn.Do("HGETALL", "hash")))// 将哈希表中 field 的值增加 1,输出 -> 4,field_3 的值变为 -> 4handleResult(redis.Int(conn.Do("HINCRBY", "hash", "field_3", 1)))// 将哈希表中 field 的值增加 -1.5,输出 -> 2.5,field_3 的值变为 -> 2.5handleResult(redis.Float64(conn.Do("HINCRBYFLOAT", "hash", "field_3", -1.5)))
}

如果哈希表包含的值非常大,可以先使用 HKEYS 取出所有的 field,然后再使用 HGET 取出值,防止一次取出多个大体积的值而导致服务器阻塞。 P48

有序集合 P48

一些常用的有序集合命令 P49

相关演示代码如下:

// 执行有序集合相关操作
func executeZsetOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "zset")))// 有序集合中添加 5 个元素及其分值,输出 -> 5,zset 变为 -> ["a":1, "b":2, "c":3, "d":4, "e":5]handleResult(redis.Int(conn.Do("ZADD", "zset", 1, "a", 2, "b", 3, "c", 4, "d", 5, "e")))// 有序集合中删除 3 个元素及其分值,输出 -> 2,zset 变为 -> ["a":1, "b":2, "c":3]handleResult(redis.Int(conn.Do("ZREM", "zset", "d", "e", "f")))// 返回有序集合的元素个数,输出 -> 3handleResult(redis.Int(conn.Do("ZCARD", "zset")))// 给有序集合中的元素的分值增加 0.5,输出 -> 1.5,a 的值变为 -> 1.5handleResult(redis.Int(conn.Do("ZINCRBY", "zset", 1, "a")))// 给有序集合中的元素的分值增加 -1.5,输出 -> 0.5,a 的值变为 -> 0.5handleResult(redis.Float64(conn.Do("ZINCRBY", "zset", -1.5, "a")))// 返回分值在 [1, 3] 范围内的元素的数量,输出 -> 2handleResult(redis.Int(conn.Do("ZCOUNT", "zset", 1, 3)))// 返回元素的升序排名(升序,从 0 开始),输出 -> 0handleResult(redis.Int(conn.Do("ZRANK", "zset", "a")))// 返回元素的降序排名(降序,从 0 开始),输出 -> 2handleResult(redis.Int(conn.Do("ZREVRANK", "zset", "a")))// 返回元素的排名的分值,输出 -> 0.5handleResult(redis.Float64(conn.Do("ZSCORE", "zset", "a")))// 返回升序排名在 [1, 2] 范围内的元素,并且返回分值,输出 -> map[b:2 c:3]handleResult(redis.StringMap(conn.Do("ZRANGE", "zset", "1", "2", "WITHSCORES")))// 返回降序排名在 [1, 2] 范围内的元素,并且返回分值,输出 -> map[a:0.5 b:2]handleResult(redis.StringMap(conn.Do("ZREVRANGE", "zset", "1", "2", "WITHSCORES")))
}

有序集合的范围性命令及并交集命令 P50

相关演示代码如下:

// 执行有序集合范围及交并集相关操作
func executeZsetMutiOperation(conn redis.Conn) {// 删除原有值handleResult(redis.Int(conn.Do("DEL", "zset_1", "zset_2", "destination")))// 初始化handleResult(redis.Int(conn.Do("ZADD", "zset_1", 1, "a", 2, "b", 3, "c")))handleResult(redis.Int(conn.Do("ZADD", "zset_2", 2, "b", 3, "c", 4, "d")))// 返回升序分值在 [1, 2] 范围内的元素,并且返回分值,输出 -> map[a:1 b:2]handleResult(redis.StringMap(conn.Do("ZRANGEBYSCORE", "zset_1", "1", "2", "WITHSCORES")))// 返回降序分值在 [4, 3] 范围内的元素,并且返回分值,输出 -> map[c:3 d:4]handleResult(redis.StringMap(conn.Do("ZREVRANGEBYSCORE", "zset_2", "4", "3", "WITHSCORES")))// 移除升序排名在 [1, 1] 范围内的元素,输出 -> 1,zset_1 变为 -> ["b":2, "c":3]handleResult(redis.Int(conn.Do("ZREMRANGEBYRANK", "zset_1", "1", "1")))// 移除降序排名在 [2, 2] 范围内的元素,输出 -> 1,zset_2 变为 -> ["c":3, "d":4]handleResult(redis.Int(conn.Do("ZREMRANGEBYSCORE", "zset_2", "2", "2")))// 求 2 个有序集合的交集,权重分别为 2, 3,分值默认采用加法// 并存储到 destination 中,输出 -> 1,destination 变为 ->handleResult(redis.Int(conn.Do("ZINTERSTORE", "destination", 2, "zset_1", "zset_2", "WEIGHTS", 2, 3)))// 求 2 个有序集合的并集,权重分别为 2, 3,分值指定采用最大值// 并存储到 destination 中,输出 -> 3,destination 变为 -> ["a":2, "c":9, "d":12]handleResult(redis.Int(conn.Do("ZUNIONSTORE", "destination", 2, "zset_1", "zset_2", "WEIGHTS", 2, 3, "AGGREGATE", "MAX")))
}

所思所想

  • 这一章又是比较枯燥的命令介绍,不过还是坚持看下来了,发现还是挺有用的,有很多平常没接触的命令,也没想到 Redis 竟然这么强大。
  • 即使时比较精细地阅读,也不需要全部阅读,可以快速浏览已经知道的基础,重点还是要放在不知道的地方,带着思考去阅读,先想然后用实践验证。

本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/redis-in-action

redis 自减命令_Redis 实战 —— 04. Redis 数据结构常用命令简介相关推荐

  1. linux常用命令 打开文件,【Linux】常用命令 lsof查看打开的文件

    Linux系统把软硬件都抽象成文件,所以通过文件可以追踪到很多重要信息,如读取的配置文件.打开的端口等. 下面是常见的用法: 默认测试文件名为text.txt 1,显示打开text.txt的进程: l ...

  2. 统信UOS命令大全 麒麟系统命令大全 Linux常用命令操作大全(非常全非常详细) ubuntu命令大全常用操作命令大全

    [腾讯云]云服务器2核4G74元/年,222元/3年 Linux常用命令大全(非常全!!!)适用于 统信UOS 麒麟kylin 系统信息  arch 显示机器的处理器架构 uname -m 显示机器的 ...

  3. php基础命令,PHP命令行下的11个常用命令介绍

    本文整理了PHP命令行下的11个常用命令,下面介绍给大家,它们分别是php -m.php -v.php --ini.php -h.php --info.php --rf .php --rc .php ...

  4. Redis实战 - 04 Redis 分布式锁应用之抢购代金券

    文章目录 1. 数据库表结构 1. 代金券表 2. 抢购活动表 3. 订单表 2. 秒杀场景的解决方案 3. 创建秒杀服务 ms-seckill 4. 代金券抢购功能开发 - 关系型数据库实现 1. ...

  5. 第18章 Redis数据结构常用命令

    18-1 字符串的一些基本命令 18-1 :配置Spring关于Redis字符串的运行环境 <bean id="poolConfig" class="redis.c ...

  6. ubuntu16.04下postgresql常用命令,及图形化管理界面pgadmin3

    1.创建用户 create user jiraadmin with password '123456';     创建jiraadmin用户并设置密码为123456 alter role jiraad ...

  7. 汇编语言DEBUG命令详解||汇编命令||DEBUG的常用命令:A,U,R,T,D,E,Q 等等

    汇编命令 汇编语言Debug命令详解 DEBUG的常用命令:A,U,R,T,D,E,Q 等等 汇编语言DEBUG命令详解 文章目录 汇编语言DEBUG命令详解 R命令的使用 H命令的使用 D命令的使用 ...

  8. linux目录命令改目录名,Linux命令详解——文件和目录常用命令-linux修改文件名...

    文件和目录常用命令 查看目录内容 ls 切换目录 cd 创建和删除操作 touch rm mkdir 拷贝和移动文件 cp mv 查看文件内容 cat more grep 其他 echo 重定向 &g ...

  9. linux清屏命令_linux下的7个常用命令的基本使用

    02.Linux下的7个常用命令的基本使用 小技巧: 使用[ctrl + shift + = ]可以放大终端窗口的字体显示使用[ctrl + -]可以缩小终端窗口的字体显示 ls:查看当前目录下的文件 ...

最新文章

  1. 随机密码生成python_每日一课 | Python 中生成 0 到 9 之间的随机整数
  2. android yuv加水印_Android Camera添加预览水印
  3. [SHOI2015]自动刷题机
  4. criterion java_Java Criterion類代碼示例
  5. 宜信(刘志波)技术培训
  6. 软件工程概论 课堂练习 第3次作业3【画出“工资处理”子系统的结构图】
  7. cocos2d-x初探学习笔记(7)--CCProgressTimer
  8. leetcode 236. Lowest Common Ancestor of a Binary Tree | 236. 二叉树的最近公共祖先(Java)
  9. 梯度提升树python_梯度增强树回归— Spark和Python
  10. 非空验证方法(多值)和BindingResult提示验证信息
  11. 用户-第三方支付公司-银行 资金流动过程
  12. Ubuntu下搜狗输入法突然无法输入中文
  13. java 类型转换原理_9.java数据类型的转换
  14. 大数据_Hbase-API访问_Java操作Hbase_MR-数据迁移-开发代码---Hbase工作笔记0016
  15. 鸟哥的Linux私房菜第零章
  16. .p7b证书转成iis使用的.pfx证书
  17. 大学计算机—计算思维导论 中国大学mooc 哈尔滨工业大学 测验题目和答案
  18. 工具 文库免积分下载、其他工具
  19. 如何解决Mac电脑没声音了的问题
  20. curve BLS12-377/381 BN256 SageMath脚本

热门文章

  1. php中的几种跳转语句以及各自的特点,PHP中的跳转语句有且仅有break和continue两个语句。...
  2. 你的老师看你编码的样子
  3. Cache 工作原理、Cache 一致性,你想知道的都在这里
  4. 为什么阿里规定需要在事务注解 @Transactional 中指定 rollbackFor?
  5. 皮一皮:周一之后还是周一?
  6. 每日一皮:当你开始研究一个新项目时是这样的吗?
  7. Spring Boot 2.x基础教程:使用集中式缓存Redis
  8. 深入Java中的位操作
  9. git项目比对_Git实战之Git对比SVN
  10. linux lddbus设备,Linux那些事儿之我是Sysfs(4)举例一lddbus | 技术部落