3.10 散列表
散列表实现了键值映射,其中键和值都可以是任意 Racket 值, 访问和更新该表的操作通常是常量时间的。根据散列表以 make-hash、 make-hasheqv 还是 make-hasheq 来创建,其键分别用 equal?、eqv? 或 eq? 来比较。
> (define ht (make-hash)) > (hash-set! ht "apple" '(red round)) > (hash-set! ht "banana" '(yellow long)) > (hash-ref ht "apple") '(red round)
> (hash-ref ht "coconut") hash-ref: no value found for key
key: "coconut"
> (hash-ref ht "coconut" "not there") "not there"
函数 hash、hasheqv 和 hasheq 均会根据初始的键值集合来创建不可变的散列表。其中每个值都作为其键之后的参数提供。 不可变的散列表可通过 hash-set 来扩展, 它会以常量时间产生一个新的不可变散列表。
> (define ht (hash "apple" 'red "banana" 'yellow)) > (hash-ref ht "apple") 'red
> (define ht2 (hash-set ht "coconut" 'brown)) > (hash-ref ht "coconut") hash-ref: no value found for key
key: "coconut"
> (hash-ref ht2 "coconut") 'brown
不可变散列表的字面量可使用 #hash(对应基于 equal? 的表)、 #hasheqv(对应基于 eqv? 的表)或 #hasheq(对应基于 eq? 的表)写成表达式的形式。括号括住的序列必须紧跟 #hash、 #hasheq 或 #hasheqv 其中每个元素都是以点号分隔的键-值对。 #hash 等形式隐式地用 quote 引述了其键值的子形式。
The Racket Reference的Reading Hash Tables一节中阐述了散列表字面语法的要点。
可变和不可变散列表的打印结果都与不可变散列表相同。如果所有的键和值均可使用 quote 来表达,那么就使用引述的 #hash、#hasheqv 或 #hasheq 形式,否则使用 hash、hasheq 或 hasheqv 来表示:
> #hash(("apple" . red) ("banana" . yellow)) '#hash(("banana" . yellow) ("apple" . red))
> (hash 1 (srcloc "file.rkt" 1 0 1 (+ 4 4))) (hash 1 (srcloc "file.rkt" 1 0 1 8))
可变散列表能够可选地将其键保留为弱引用的,这样只要键在其它地方被保留, 该映射就会被保留。
> (define ht (make-weak-hasheq)) > (hash-set! ht (gensym) "can you see me?") > (collect-garbage) > (hash-count ht) 0
请注意,在弱引用散列表中,只要值对应的键可访问,那么该值也是强引用的。 当值又引用回其键时,会创建一个 catch-22 依赖,这样该映射就会永久保留。 要打破这种循环,需将键映射到一个 ephemeron(短命对象)来将值与其键配对 (散列表中二者的隐式配对除外)。
The Racket Reference的Ephemerons一节中阐述了使用 ephemeron的要点。
> (define ht (make-weak-hasheq))
> (let ([g (gensym)]) (hash-set! ht g (list g))) > (collect-garbage) > (hash-count ht) 1
> (define ht (make-weak-hasheq))
> (let ([g (gensym)]) (hash-set! ht g (make-ephemeron g (list g)))) > (collect-garbage) > (hash-count ht) 0
The Racket Reference的Hash Tables一节中提供了关于hash tables and hash-table procedures的更多信息。
The Racket Reference的Hash Tables一节中提供了关于散列表及其过程的更多信息。