Skip to content

检查哈希表大小

哈希表(Hash Table)是 Common Lisp 中一种基础且强大的数据结构,用于创建高效的键值映射。在使用它们时,理解条目数量与底层容量之间的区别至关重要。

  • 计数 (hash-table-count):这表示哈希表中当前存储的键值对数量。这是你最常使用的值。
  • 大小/容量 (hash-table-size):这表示哈希表的内部容量——在需要重新分配大小之前,它能容纳多少条目。这主要是一个用于性能调优的工具。

可以把哈希表想象成一个停车场。hash-table-count 是当前停放的车辆数量,而 hash-table-size 则是可用的停车位总数。

此函数返回表中键值对的数量。

;; 创建一个空哈希表
(defvar *user-cache* (make-hash-table))
(print (hash-table-count *user-cache*)) ;=> 0
;; 添加一些条目
(setf (gethash :user-1 *user-cache*) "Alice")
(setf (gethash :user-2 *user-cache*) "Bob")
(print (hash-table-count *user-cache*)) ;=> 2
;; 再添加一个
(setf (gethash :user-3 *user-cache*) "Charlie")
(print (hash-table-count *user-cache*)) ;=> 3

此函数揭示哈希表的内部已分配大小。确切值取决于具体实现,但它将是一个至少和你请求的初始 :size 参数一样大的数字。

;; 创建一个哈希表,建议一个初始大小
(defvar *my-ht* (make-hash-table :size 50))
;; 实际容量取决于具体实现,但应 >= 50
(print (hash-table-size *my-ht*)) ;=> e.g., 65 (in SBCL)
;; 计数仍为零
(print (hash-table-count *my-ht*)) ;=> 0
;; 添加一些条目
(setf (gethash :a *my-ht*) 1)
(setf (gethash :b *my-ht*) 2)
;; 容量保持不变,但计数已改变
(print (hash-table-size *my-ht*)) ;=> e.g., 65
(print (hash-table-count *my-ht*)) ;=> 2

通过向 make-hash-table 提供关键字,你可以影响哈希表的性能和内存使用。

  • :test:最重要的参数。它指定了键的比较函数。默认值是 eql。选择错误的测试函数是常见的错误来源。
  • - `#'eq` (最快):用于比较符号或完全相同的对象。
  • - `#'eql` (默认):用于符号和数字。
  • - `#'equal`:用于字符串、列表和其他结构上相似的数据结构。
  • - `#'equalp` (最慢):类似于 `equal`,但对字符串和字符不区分大小写。
  • :size:给实现的一个提示,说明你预期存储的初始条目数量。设置此参数可以避免过早的重新分配大小。
  • :rehash-size 与 :rehash-threshold:控制哈希表何时以及如何增长。阈值决定了哈希表在重新分配大小之前能有多“满”。默认值通常就很好。

一个常见错误是尝试使用在表的 :test 函数下不等效的键来检索值。

;; 使用默认测试函数 'eql' 创建一个表
(defvar *ht* (make-hash-table))
;; 添加一个以字符串为键的条目
(setf (gethash "my-key" *ht*) "my-value")
;; 使用一个 `equal` 字符串来检索。这之所以有效,是因为 'eql'
;; 对于字符串,如果它们不 `eq`,会退回到 'equal'。
;; 但这是一个微妙之处,`equal` 才是字符串的正确测试函数。
(gethash "my-key" *ht*) ;=> "my-value", T
;; --- 经典错误 ---
;; 使用 'eq' 测试函数创建一个表
(defvar *bad-ht* (make-hash-table :test #'eq))
;; 添加一个以字符串为键的条目
(setf (gethash "my-key" *bad-ht*) "my-value")
;; 尝试用一个内容相同但却是不同字符串对象来检索
(gethash "my-key" *bad-ht*) ;=> NIL, NIL (!!)
;; 为什么?因为两个 "my-key" 字符串并非 `eq`(它们在内存中不是同一个对象)。
;; 创建此表的正确方式是 `(make-hash-table :test #'equal)`
  • 使用 hash-table-count 来查看表中有多少项。
  • 选择正确的 :test 函数:这对正确性至关重要。对字符串使用 #'equal。
  • 提供一个初始 :size,如果你对最终条目数量有很好的估计。这是一个简单而有效的优化。