检查哈希表大小
现代Lisp:哈希表的大小与容量
Section titled “现代Lisp:哈希表的大小与容量”哈希表(Hash Table)是 Common Lisp 中一种基础且强大的数据结构,用于创建高效的键值映射。在使用它们时,理解条目数量与底层容量之间的区别至关重要。
计数与容量:核心区别
Section titled “计数与容量:核心区别”- 计数 (
hash-table-count):这表示哈希表中当前存储的键值对数量。这是你最常使用的值。 - 大小/容量 (
hash-table-size):这表示哈希表的内部容量——在需要重新分配大小之前,它能容纳多少条目。这主要是一个用于性能调优的工具。
可以把哈希表想象成一个停车场。hash-table-count 是当前停放的车辆数量,而 hash-table-size 则是可用的停车位总数。
获取条目数量:hash-table-count
Section titled “获取条目数量:hash-table-count”此函数返回表中键值对的数量。
;; 创建一个空哈希表(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获取容量:hash-table-size
Section titled “获取容量:hash-table-size”此函数揭示哈希表的内部已分配大小。确切值取决于具体实现,但它将是一个至少和你请求的初始 :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 进行性能调优
Section titled “使用 make-hash-table 进行性能调优”通过向 make-hash-table 提供关键字,你可以影响哈希表的性能和内存使用。
- :test:最重要的参数。它指定了键的比较函数。默认值是
eql。选择错误的测试函数是常见的错误来源。 -
- `#'eq` (最快):用于比较符号或完全相同的对象。
-
- `#'eql` (默认):用于符号和数字。
-
- `#'equal`:用于字符串、列表和其他结构上相似的数据结构。
-
- `#'equalp` (最慢):类似于 `equal`,但对字符串和字符不区分大小写。
- :size:给实现的一个提示,说明你预期存储的初始条目数量。设置此参数可以避免过早的重新分配大小。
- :rehash-size 与 :rehash-threshold:控制哈希表何时以及如何增长。阈值决定了哈希表在重新分配大小之前能有多“满”。默认值通常就很好。
常见陷阱:键测试不匹配
Section titled “常见陷阱:键测试不匹配”一个常见错误是尝试使用在表的 :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,如果你对最终条目数量有很好的估计。这是一个简单而有效的优化。