PostgreSQL GIN 索引揭秘

文章目录

  • 什么是GIN Index?
  • 示例场景
  • GIN Index的原理
  • GIN Index结构
    • Metapage
    • Entries
    • Leaf Pages
    • Entry page 和 Leaf page 的关系
    • Posting list 和posting tree
    • 待处理列表(Pending List)
  • 进阶解读GIN index索引结构
  • 总结

什么是GIN Index?

GIN (Generalized Inverted Index) 索引常用于为 array、jsonb 和 tsvector(用于 fulltext search)类型建立索引。
在 array 的场景下,可以用来验证一个 array 是否包含另一个 array 或元素(比如 <@ operator)。
在早前的postgresql-一文读懂index中的operator,你可以看到完整的 operator 列表。

但我在这篇文章里真正想回答的问题是:
“为什么我们要在这些数据类型和 operator 上使用 GIN 索引?”
在 PostgreSQL 中,GIN(Generalized Inverted Index,广义倒排索引)之所以被称为“倒排”,是因为它的数据结构和查询方式与传统索引(如 B-tree)的工作原理相反。倒排索引的核心思想是将数据的存储和查询从“正向”转向“反向”。
具体来说:

  • 传统索引(如 B-tree) 是“正向”索引,基于键值(key)直接映射到对应的记录位置。查询时,从键值出发,找到相关记录。
  • 倒排索引 则是将数据的属性或值作为索引的起点,记录哪些文档或记录包含这些值。例如,对于全文搜索,GIN 会为每个词条(term)维护一个列表,列出包含该词条的所有记录的标识。这种“值到记录”的映射方式被称为“倒排”,因为它反过来存储了“记录到值”的关系。

在 GIN 的上下文中,这种设计特别适合处理多值属性(如数组、JSON 或全文搜索),因为它能够高效地查找包含特定值的记录集合。PostgreSQL 中的 GIN 实现了这种广义倒排索引,支持多种数据类型和操作符类(如数组操作符或全文搜索操作符),因此得名“广义倒排索引”。
总结来说,“倒排”反映了 GIN 从值反向映射到记录的独特索引机制,这使其在特定场景下(如复杂查询或全文检索)表现出色。

示例场景

创建一个表 articles,并使用 GIN 索引来支持全文搜索:

-- 创建表
CREATE TABLE articles (id SERIAL PRIMARY KEY,content TEXT
);-- 插入一些示例数据
INSERT INTO articles (content) VALUES
('The quick brown fox jumps over the lazy dog'),
('A quick jump over the brown fence'),
('The lazy dog sleeps');-- 创建 GIN 索引,用于全文搜索
CREATE INDEX idx_gin_content ON articles USING GIN (to_tsvector('english', content));

我们利用to_tsvector函数,查看上面GIN Index将会生成的8个items如下:

demo=# SELECT DISTINCT word
FROM ts_stat('SELECT to_tsvector(''english'', content) FROM articles');word
-------fencdogsleepjumpfoxbrownquicklazi
(8 rows)

GIN Index的原理

当我们对 content 列创建 GIN 索引时,PostgreSQL 会使用 to_tsvector 函数将文本分解为词条(terms),并为每个词条生成一个倒排列表。
例如,to_tsvector(‘english’, ‘The quick brown fox jumps over the lazy dog’) 会分解为词条:brown, dog, fox, jump, lazy, over, quick, the。
GIN 索引会记录每个词条及其出现在哪些 id 中的信息,例如:

brown -> [id: 1, id: 2]
dog -> [id: 1, id: 3]
quick -> [id: 1, id: 2]
......

这种“词条到记录 ID”的映射就是“倒排”的体现,与传统索引从记录 ID 查找值的正向方式相反。

GIN Index结构

GIN 索引的结构和 BTree 索引非常接近。接下来我们将探讨其中的一些差异。

Metapage

像 BTree Index 一样,GIN index 的第一页是 metapage,其中包含关于索引的信息。不同之处在于,这些信息会稍有不同。例如,在 GIN index 中你不会找到btree中的 fast root结构。
我们可以通过 postgres extension pageinspect 来查看 metapage 的信息。

demo=# select * from gin_metapage_info(get_raw_page('idx_gin_content',0));
-[ RECORD 1 ]----+-----------
pending_head     | 4294967295
pending_tail     | 4294967295
tail_free_size   | 0
n_pending_pages  | 0
n_pending_tuples | 0
n_total_pages    | 2
n_entry_pages    | 1
n_data_pages     | 0
n_entries        | 8
version          | 2

以上输出字段说明如下:

  1. Pending list 相关字段
  • pending_head = 4294967295
  • pending_tail = 4294967295
    4294967295是一个特殊值 (InvalidBlockNumber),表示当前 没有 pending list。
  • tail_free_size = 0
    如果 pending list 存在,这里会表示尾页剩余的可用空间。现在为 0,说明没有 pending list。
  • n_pending_pages = 0
    表示待处理页面(pending pages)的数量。值为 0 表明当前没有待处理的页面,这与 pending_head 和 pending_tail 的值一致,说明索引未处于批量更新模式
  • n_pending_tuples = 0
    没有等待合并到 entry tree 的 tuple。
    说明:索引里的数据已经都合并进 entry pages 了,没有暂存的东西。
  1. Page 统计
  • n_total_pages = 2
    整个索引文件占用 2 个 page:
  • page 0:metapage
  • page 1:entry page
  • n_entry_pages = 1
    有 1 个 entry page(page 1),entry page 存放的是 key → posting list/tree 的入口。
  • n_data_pages = 0
    没有 data page。
    data page 存 posting tree 的叶子节点。因为目前 posting list 很小,直接存放在 entry page 里,不需要单独开 data page。
  1. 索引条目
  • n_entries = 8
    这个的索引里总共有 8 个不同的 key(比如 array 元素 / tsvector token / jsonb key)。
  1. 版本号
  • version = 2
    GIN 索引的格式版本,目前 Postgres 的 GIN 是 version 2(相比 v1 支持更紧凑的存储方式,posting list 压缩等)。

在一个 GIN index 中存在两种类型的 page:entry pages 和 data pages。

  • Data pages 是位于 posting tree 内部的 page。
  • Entry pages 是包含索引中 value 的 page。

这两类 page 都带有 opaque data,其中包含:

  • 一个 flag 用于定义类型(leaf、data、compressed、meta)
  • right sibling
  • maxoff

Entries

在一个 GIN index 中,keys(entries) 存储在 entry pages 中,以 binary tree 的形式组织。这一点和 BTree index 非常接近。实际上,索引的第一页是 metapage,然后这些 keys 会被存储到一个 binary tree 中。
GIN 的 entry pages 以二叉树形式组织,看上去和 BTree 很像, 但是,这里还是存在一些主要的差异……
首先,如果你在 BTree index 中对一个 array 建立索引,那么存储的值会直接是这个 array。

  • BTree:key → 指向 tuple
  • GIN:key → posting list(或 posting tree) → tuple
    例如,如果被索引的字段的值是一个阵列:array{1,6,12}
    BTree:整个数组{1,6,12}被当作一个 key:
Key (array)          → Tuple(s)
-------------------------------
{1,6,12}(0,1)

GIN:数组会被拆开,存成多个 entry:1 、 6、12,各自维护 posting list:

Key (entry)          → Posting List (heap pointers)
-------------------------------
1(0,1)
6(0,1)
12(0,1)

这正是 GIN 擅长全文检索的原因。
在我们的例子中,虽然不是数组,但 to_tsvector 把句子拆成了多个词,每个词单独建 entry。

GIN Entry Page
+---------+-------------------+
| "dog"   | -> (0,1), (0,3)   |
| "brown" | -> (0,1), (0,2)   |
| "sleep" | -> (0,3)          |
| "quick" | -> (0,1), (0,2)   |
| ......  | -> ......         |
+---------+-------------------+

第二个区别是:在 GIN index 中,values 是唯一的。
在 BTree 中,同一个 value 可以对应多个 items, tuple (value, pointer) 来保证 index entry 的唯一性。
而在 GIN index 中,values 的唯一性使得它非常适合用于同一个 value 出现在许多不同 rows 的情况。

Leaf Pages

在BTree index中,在leaf level上,items的数量与rows的数量相等。因此同一个值可能会重复出现多次:

BTree Leaf Page"brown" → Row1"brown" → Row2"dog"   → Row1"dog"   → Row3"fox"   → Row1"jumps" → Row1"lazy"  → Row1"lazy"  → Row3"over"  → Row1"over"  → Row2"quick" → Row1"quick" → Row2"the"   → Row1"the"   → Row3

正如我之前所说,在GIN index中entries是唯一的。因此leaf levels包含指向rows的pointers的list或tree,即post list或post tree。

GIN Entry Tree"brown"[Row1, Row2]"dog"[Row1, Row3]"fox"[Row1]"jumps"[Row1]"lazy"[Row1, Row3]"over"[Row1, Row2]"quick"[Row1, Row2]"the"[Row1, Row3]

Entry page 和 Leaf page 的关系

在 entry tree 里,和 BTree 类似:

  1. Entry pages (internal pages)
  • 存放的是 entries 的范围信息(keys):
  • 每个 entry 对应一个 posting list 或 posting tree 的指针
    这类似于 BTree 的 internal page。
  1. Leaf pages
    存放具体的 entry (value)

entry 对应的是posting list还是posting tree的指针取决于索引的大小:

  1. 小索引(entry page = root = leaf,posting list 内联)
Entry Tree└── Entry Page (root & leaf)├── entry = "dog"   → posting list [ctid(0,1), ctid(0,3)]├── entry = "quick" → posting list [ctid(0,1), ctid(0,2)]├── entry = "the"   → posting list [ctid(0,1), ctid(0,3)]└── ...

这里只有 metapage + 一个 entry page,entry page 既是 root 也是 leaf,posting list 全部内联。
2. 大索引(entry page 内存指针,posting list 太大 → posting tree)

Entry Tree├── Entry Page (internal)│       key range + child pointers│└── Leaf Page├── entry = "dog" → posting list [ctid(0,1), ctid(0,3)]├── entry = "quick" → posting list [ctid(0,1), ctid(0,2)]├── entry = "the" → pointer to posting tree└── ...

在这种情况下:

  • entry page 作为 internal page(只做导航);
  • leaf page 存 entry,但如果某个 entry(如 “the”)太大,就存一个指针指向 posting tree。

Posting list 和posting tree

在一个 leaf page 中,entries 包含一个称为 posting list 的 item pointers 列表,这个列表是以压缩格式存储的。

如果列表变得太大,以至于该 item 无法再放入 index page,那么 posting list 会被拆分到不同的页面中,这些页面以 BTree 组织。这就是所谓的 posting tree。在 leaf item 中,会存储指向这个树的指针,而不是 posting list。
在 posting list 中指向 heap 的指针是按物理顺序存储的。而在 posting tree 中,这些指针则作为 keys。
所以,现在我们讲完了 leaf,这里展示一下 GIN 索引的各个层级结构
在 GIN 的 entry tree 里,每个 entry 是唯一的。
比如 “dog”:

Leaf Page (GIN)
Entry: "dog"Posting list of item pointers:→ (Row1, ctid=(0,1))(Row3, ctid=(0,3))

这里 posting list 很小,能直接放在 leaf page 里。

当 posting list 太大时 → Posting tree
假设 “the” 出现在 100 万行文章中,posting list 太大,无法塞进一个 index page。
这时,GIN 会把 posting list 拆分成多个 page,用 BTree 组织,形成 posting tree:

Entry Tree (Entry Page / Leaf Page)
Entry: "the"→ pointer to Posting Tree

Posting Tree 结构:

Posting Tree Root Page (BTree internal)├── → Data Page 1 [Row1, Row3, Row8, Row20, ...]├── → Data Page 2 [Row101, Row102, Row110, ...]└── → Data Page 3 [Row999, Row1000, ...]

这里:

  • leaf item 里不再存 posting list,而是存一个 指针,指向 posting tree root。
  • posting tree 内部的指针就是 keys,用来导航到具体的 data page。

待处理列表(Pending List)

在 GIN index 中插入新行是相当慢的,因为 values 的唯一性,插入操作比在普通 BTree 中插入更慢——这是因为必须更新 posting list 或 posting tree。

为了优化插入,我们将新的 entries 存储在一个 pending list 中,它是一个简单的线性 pages 列表。当 pending list 达到限制,或者发生 VACUUM 时,这些 entries 会被移动到 BTree 中,使用的是 bulk insert,这种方式经过优化,尤其是在每个 value 对应多行的情况下。

pending list 的大小限制可以逐个索引设置:

CREATE INDEX ... WITH (gin_pending_list_limit=...)
ALTER INDEX ... SET (gin_pending_list_limit=...)

或者通过全局配置参数 gin_pending_list_limit 来设置。

gin_pending_list_limit = '64MB';

pending list 的缺点是:在 GIN index 中进行搜索时,必须同时扫描 BTree 和 pending list。
如果你的场景中数据很少发生变化,并且你不在乎更新操作很慢,那么可以通过在创建索引时,或者使用 ALTER INDEX 将 fastupdate 设置为 false 来禁用 pending list。

需要注意的是,如果你使用 ALTER INDEX 禁用了 pending list,那么已有的 pending list 并不会自动被刷新,因此你可能需要在表上执行 VACUUM,以确保所有数据都被移动到 BTree 中。

进阶解读GIN index索引结构

首先,我们透过pageinspace扩展去查看metapage所包含的内容

 pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version
--------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+---------4294967295 |   4294967295 |              0 |               0 |                0 |             2 |             1 |            0 |         8 |       2
(1 row)

从输出中的n_total_pages来看,这个index共有2个page,其中metapage占用1个page,n_entry_page=1代表有1个entry page.
由于pageinspect并没有提供直接查看entry page的函数,我们只能从侧面来证明这个entry page的内容:

demo=# SELECT DISTINCT word
FROM ts_stat('SELECT to_tsvector(''english'', content) FROM articles');word
-------fencdogsleepjumpfoxbrownquicklazi
(8 rows)

共产生8个词条,这与n_entries=8是一致的。

另外,我们也可以透过pg_filedump来dump gin index的内部结构

 pg_filedump -i -f  -R 1 /var/lib/postgresql/16/main/base/16448/24863

这里-R 1,意指dump page 1(page 0是metapage,page 1是entry page)
输出如下:


*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility
*
* File: /var/lib/postgresql/16/main/base/16448/24863
* Options used: -i -f -R 1
*******************************************************************Block    1 ********************************************************
<Header> -----Block Offset: 0x00002000         Offsets: Lower      56 (0x0038)Block: Size 8192  Version    4            Upper    7952 (0x1f10)LSN:  logid      0 recoff 0x12b23040      Special  8184 (0x1ff8)Items:    8                      Free Space: 7896Checksum: 0x0000  Prune XID: 0x00000000  Flags: 0x0000 ()Length (including item array): 560000: 00000000 4030b212 00000000 3800101f  ....@0......8...0010: f81f0420 00000000 d89f4000 b89f4000  ... ......@...@.0020: a09f3000 889f3000 689f4000 489f4000  ..0...0.h.@.H.@.0030: 289f4000 109f3000                    (.@...0.<Data> -----Item   1 -- Length:   32  Offset: 8152 (0x1fd8)  Flags: NORMALBlock Id: 2147483664  linp Index: 2  Size: 32Has Nulls: 0  Has Varwidths: 11fd8: 00801000 02002040 0d62726f 776e0000  ...... @.brown..1fe8: 00000000 01000100 01000000 00000000  ................Item   2 -- Length:   32  Offset: 8120 (0x1fb8)  Flags: NORMALBlock Id: 2147483664  linp Index: 2  Size: 32Has Nulls: 0  Has Varwidths: 11fb8: 00801000 02002040 09646f67 00000000  ...... @.dog....1fc8: 00000000 01000100 02000000 00000000  ................Item   3 -- Length:   24  Offset: 8096 (0x1fa0)  Flags: NORMALBlock Id: 2147483664  linp Index: 1  Size: 24Has Nulls: 0  Has Varwidths: 11fa0: 00801000 01001840 0b66656e 63000000  .......@.fenc...1fb0: 00000000 02000000                    ........Item   4 -- Length:   24  Offset: 8072 (0x1f88)  Flags: NORMALBlock Id: 2147483664  linp Index: 1  Size: 24Has Nulls: 0  Has Varwidths: 11f88: 00801000 01001840 09666f78 00000000  .......@.fox....1f98: 00000000 01000000                    ........Item   5 -- Length:   32  Offset: 8040 (0x1f68)  Flags: NORMALBlock Id: 2147483664  linp Index: 2  Size: 32Has Nulls: 0  Has Varwidths: 11f68: 00801000 02002040 0b6a756d 70000000  ...... @.jump...1f78: 00000000 01000100 01000000 00000000  ................Item   6 -- Length:   32  Offset: 8008 (0x1f48)  Flags: NORMALBlock Id: 2147483664  linp Index: 2  Size: 32Has Nulls: 0  Has Varwidths: 11f48: 00801000 02002040 0b6c617a 69000000  ...... @.lazi...1f58: 00000000 01000100 02000000 00000000  ................Item   7 -- Length:   32  Offset: 7976 (0x1f28)  Flags: NORMALBlock Id: 2147483664  linp Index: 2  Size: 32Has Nulls: 0  Has Varwidths: 11f28: 00801000 02002040 0d717569 636b0000  ...... @.quick..1f38: 00000000 01000100 01000000 00000000  ................Item   8 -- Length:   24  Offset: 7952 (0x1f10)  Flags: NORMALBlock Id: 2147483664  linp Index: 1  Size: 24Has Nulls: 0  Has Varwidths: 11f10: 00801000 01001840 0d736c65 65700000  .......@.sleep..1f20: 00000000 03000000                    ........<Special Section> -----GIN Index Section:Flags: 0x00000002 (LEAF)  Maxoff: 0Blocks: RightLink (-1)1ff8: ffffffff 00000200                    ........*** End of Requested Range Encountered. Last Block Read: 1 ***

输出中,从item 1…item8,这个entrypage一共包含8个item,这与我们上面的查询一致的,并且每个item都包含这样的结构:

  1fb8: 00801000 02002040 09646f67 00000000  ...... @.dog....1fc8: 00000000 01000100 02000000 00000000  ................
  1. 第一行中( 1fb8: 00801000 02002040 09646f67 00000000 … @.dog…
    ):
  • 00 80 10 00 02 00 20 40
    这前 8 字节是元信息 / tuple header(Postgres 的 tuple header / GIN internal header),包含诸如 t_infomask、t_hoff、gin-item 的元字段等。此处不是我们关心的 posting-list 内容,因此不展开逐位解释
    这块包含 lexeme(词条)本身:
  • 09 64 6f 67 00 00 00 00
    09:这是 varlena/文本的头部字节(包含长度/标志等),常见于 Postgres 存储的短文本格式。
    我们不必在此把 varlena 的头位 bit-by-bit 拆开 —— 重点是后面实际的字符字节。
    64 6f 67 是 ASCII “dog”,这是索引的 lexeme(词条)。
demo=# demo=# select chr(x'64'::int),chr(x'6F'::int),chr(x'67'::int);chr | chr | chr
-----+-----+-----d   | o   | g
  • 后面 00 00 00 00 是对齐/填充,使后面 posting-list 从对齐位置开始。
  1. 第二行中:
    在 GIN 数据页里,紧跟词条后面就是 posting list 或 posting tree 的指针。
    在进一步解读前,我们需要先了解posting list/posting tree指针的源码结构:
typedef struct ItemPointerData
{BlockIdData ip_blkid;   /* 4 字节,块号 */OffsetNumber ip_posid;  /* 2 字节,行号(slot) */
} ItemPointerData;
  • BlockIdData = 4 字节,存储 heap 表的 block number
  • OffsetNumber = 2 字节,存储该 block 上的 行号 (line pointer index)
    注意:实际存储是 小端字节序(Postgres 在磁盘上是 little-endian)
    一个 ItemPointerData 占 6 字节,但在实际存储时会补齐到 4 字节对齐,所以通常会看到 8 字节一组。

第二行中的 16 字节是:

00 00 00 00   01 00 01 00   02 00 00 00   00 00 00 00

我们拆开:
00 00 00 00 —— 对齐 / padding(跳过)。
01 00 01 00 —— 关键的第一段,按 16-bit 分成两部分(小端):

  • 前两个字节 01 00(小端) = 0x0001 = 十进制 1,这是block number(块号),代表指向heap table的第1个块号,而在heap table中第一个块号是0
  • 后两个字节 01 00(小端) = 0x0001 = 十进制 1,即第一个 offset(offset1)= 1。
    到这里,我们获得post list中第一个tid (block, offset1) = (0, 1)。
    02 00 00 00 —— 接下来是一个 32-bit 小端整数:0x00000002 = 十进制 2,这是一个 offset delta(后续 offset 相对前一 offset 的增量)。
    于是,我们获得同一块的第二个offset(1+2=3),因此第二个tid (block, offset1) = (0, 3)。
    00 00 00 00 —— 结束 / 填充(通常用 0 作为终结标记)。

最终结论(映射回 heap table的tid)
所以这段 posting-list 对应的两个 heap tuple 是:
(0,1) —— 表里 id = 1,内容 “The quick brown fox jumps over the lazy dog”(含 “dog”)
(0,3) —— 表里 id = 3,内容 “The lazy dog sleeps”(含 “dog”)
也就是说 “dog” 在行 (0,1) 和 (0,3),与表里的文本一致。

demo=# select ctid,* from articles;ctid  | id |                   content
-------+----+---------------------------------------------(0,1) |  1 | The quick brown fox jumps over the lazy dog(0,2) |  2 | A quick jump over the brown fence(0,3) |  3 | The lazy dog sleeps
(3 rows)

总结

一个 GIN 索引包含:

  • 一个 metapage
  • 一个 BTree of key entries(键条目的 B 树)
  • 叶子页 (leaves) 要么包含指向 posting tree 的指针,要么包含一个 posting list of heap pointers(堆指针的 posting 列表)
  • 这些指针在物理内存中是有序的;在 posting tree 中,使用 tid 作为键来构建树
  • 还没有被索引的行存放在 pending list 中

GIN 索引具有非常独特的结构。最重要的部分是理解:
被索引的 values 会被拆分以生成 keys
这也是它在 fulltext search、arrays 和 jsonb 上非常高效的原因。

不过,GIN 也有一些针对 integers 的扩展。例如,如果想索引一个不同值(different values)不多的列,这可能会很有价值,因为 BTree 会被优化。但在搜索方面,我发现它不一定比 BTree 更好,可能是因为需要访问 posting lists 和 posting trees。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源多模态OpenFlamingo横空出世,基于Flamingo架构实现图像文本自由对话,重塑人机交互未来

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《GPT多模态大模型与AI Agent智能体》&#xff08;跟我一起学人工智能&#xff09;【陈敬雷编著】【清华大学出版社】 清华《GPT多模态大模型与AI Agent智能体》书籍配套视频课程【陈敬雷…

电子衍射模拟:基于GPU加速的MATLAB/Julia实现

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;注册即送-H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;电子衍射模拟的重要性与计算挑战 电子…

easyExcel动态应用案例

代码链接&#xff1a;https://download.csdn.net/download/ly1h1/919402991.案例说明&#xff1a;1.1.导入功能导入数据实现转换成 List<List<String>> headers和 List<List<String>> datas&#xff0c;后续补充可以与数据模型注解结合&#xff0c;形…

【数据结构入门】排序算法(5):计数排序

目录 1. 比较排序和非比较排序 2. 计数排序的原理 2.1 计数排序的弊端 3.代码复现 3.1 代码分析 3.2 排序核心 3.3 时间、空间复杂度 1. 比较排序和非比较排序 比较排序是根据排序元素的具体数值比较来进行排序&#xff1b;非比较排序则相反&#xff0c;非比较排序例如&…

输入3.8V~32V 输出2A 的DCDC降压芯片SCT9320

同志们&#xff0c;今天来个降压芯片SCT9320。输入3.8V~32V&#xff0c;输出最高可以达到2A。0.8V的参考电压。500k的开关频率。一共八个引脚&#xff0c;两个NC&#xff08;为什么不做成六个引脚呢&#xff1f;&#xff09;。EN引脚悬空或者接到VIN都可以直接启动&#xff0c;…

C++类和对象详解(2);初识类的默认成员函数

1.类的默认成员函数默认成员函数就是用户没有显示实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类我们不写的情况下编译器会默认生成以下的6个默认成员函数。&#xff08;1&#xff09;构造函数&#xff1a;主要完成初始化的工作&#xff08;2&#xff09…

PLC通信 Tpc客户端Socket

1.PLC通信 namespace _2.PLC通信 {public partial class Form1 : Form{public Form1(){InitializeComponent();}//连接//1.型号: 跟PLC沟通 使用哪个型号的PLC//2.IP 同上//3.机台号:同上//4.插槽号:同上Plc plc new Plc(CpuType.S71200, "192.168.25.80", 0, 1);pr…

Android 开发实战:从零到一集成 espeak-ng 实现中文离线 TTS(无需账号开箱即用)

简介 在移动应用开发中,语音合成(TTS)技术是提升用户体验的重要工具。然而,许多开发者在集成 TTS 时面临依赖网络、需注册账号、功能受限等问题。本文将带你从零开始,通过开源项目 espeak-ng,实现无需账号、开箱即用的中文离线语音播报。 文章将覆盖以下核心内容: esp…

直播APP集成美颜SDK详解:智能美妆功能的开发实战

在这个“颜值即正义”的时代&#xff0c;用户对直播APP的第一印象&#xff0c;往往来自主播的画面质量。高清的视频固然重要&#xff0c;但如果缺少自然美颜和智能美妆功能&#xff0c;观众体验就会大打折扣。于是&#xff0c;美颜SDK成了直播行业的“标配”。今天&#xff0c;…

C++内存管理:new与delete的深层解析

1. 引言在C的世界里&#xff0c;动态内存管理是一个核心话题。对于从C语言过渡到C的开发者来说&#xff0c;一个常见的困惑是&#xff1a;既然C语言的malloc和free依然可以在C中使用&#xff0c;为什么C还要引入new和delete这两个操作符&#xff1f;本文将深入探讨这两对内存管…

【AI开发】【前后端全栈】[特殊字符] AI 时代的快速开发思维

&#x1f680; AI 时代的快速开发思维 —— 以 Django Vue3 为例的前后端分离快捷开发流程 一、AI 时代的开发新思路 在 AI 的加持下&#xff0c;软件开发不再是“纯体力活”&#xff0c;而是 思维工具自动化 的协作。 过去&#xff1a;需求 → 设计 → 开发 → 测试 → 上…

Day24_【深度学习(3)—PyTorch使用—张量的创建和类型转换】

一、创建张量1.张量基本创建方式torch.tensor 根据指定数据创建张量 &#xff08;最重要&#xff09;torch.Tensor 根据形状创建张量, 其也可用来创建指定数据的张量torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量1.1 torch.tensor# 方式一&…

3-12〔OSCP ◈ 研记〕❘ WEB应用攻击▸利用XSS提权

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

AI 大模型赋能智慧矿山:从政策到落地的全栈解决方案

矿山行业作为能源与工业原料的核心供给端&#xff0c;长期面临 “安全生产压力大、人工效率低、技术落地难” 等痛点。随着 AI 大模型与工业互联网技术的深度融合&#xff0c;智慧矿山已从 “政策引导” 迈入 “规模化落地” 阶段。本文基于 AI 大模型智慧矿山行业解决方案&…

Node.js 项目依赖包管理

h5打开以查看 一、核心理念&#xff1a;从“能用就行”到“精细化管理” 一个规范的依赖管理体系的目标是&#xff1a; 可复现&#xff1a;在任何机器、任何时间都能安装完全一致的依赖&#xff0c;保证构建结果一致。 清晰可控&#xff1a;明确知道每个依赖为何存在&#x…

洛谷P1835素数密度 详解

题目如下&#xff1a;这里面有部分代码比较有意思&#xff1a;1&#xff0c;为何开始先遍历&#xff0c;最终值小于50000&#xff1f;因为题目要求的右边与左边差小于 10^6 &#xff0c;所以最多有10^3个素数&#xff0c;所以保存里面的素数数量大于1000&#xff0c;而50000的化…

突破限制:FileCodeBox远程文件分享新体验

文章目录【视频教程】1.Docker部署2.简单使用演示3. 安装cpolar内网穿透4. 配置公网地址5. 配置固定公网地址在隐私日益重要的今天&#xff0c;FileCodeBox与cpolar的协同为文件传输提供了安全高效的解决方案。通过消除公网IP限制和隐私顾虑&#xff0c;让每个人都能掌控自己的…

以太网链路聚合实验

一、实验目的掌握使用手动模式配置链路聚合的方法掌握使用静态 LACP 模式配置链路聚合的方法掌握控制静态 LACP 模式下活动链路的方法掌握静态 LACP 的部分特性的配置二、实验环境安装有eNSP模拟器的PC一台&#xff0c;要求PC能联网。三、实验拓扑LSW1与LSW2均为S3700交换机。L…

autMan安装教程

一、安装命令 如果你系统没安装docker&#xff0c;请看往期教程 以下为通用命令 docker run -d --name autman --restart always -p 8080:8080 -p 8081:8081 -v /root/autman:/autMan --log-opt max-size10m --log-opt max-file3 hdbjlizhe/autman:latest解释一下以上命令&…

【无人机】自检arming参数调整选项

检查项目 (英文名)中文含义检查内容四旋翼建议 (新手 → 老手)理由说明All所有检查启用下面所有的检查项目。✅ 强烈建议勾选这是最安全的设置&#xff0c;确保所有关键系统正常。Barometer气压计检查气压计是否健康、数据是否稳定。✅ 必须勾选用于定高模式&#xff0c;数据异…