toflar/fast-set
Composer 安装命令:
composer require toflar/fast-set
包简介
Provides the means to create memory-efficient precompiled sets with very fast fingerprint lookups
README 文档
README
FastSet is a tiny, read-only, memory-efficient membership set for PHP.
It answers exactly one question:
Does this string exist in my set?
…and it does so very fast and with very little memory, even for large sets.
Why FastSet exists
PHP’s native arrays are extremely fast for lookups, but they are also very memory-hungry.
Storing large dictionaries (100k+ strings) quickly becomes impractical, especially when you need multiple sets at once.
FastSet is designed for cases where:
- The dictionary is static (no inserts, deletes, or updates).
- You only need
has(string $entry): bool. - Memory usage matters.
- Lookup speed must be predictable and fast.
Typical use cases:
- Linguistic dictionaries (e.g. word decomposition)
- Large allow/deny lists
- Static vocabularies
- Read-only validation sets
Design overview
FastSet uses a sorted fingerprint blob instead of storing the original strings.
Build time
- Read to original dictionary built using the
SetBuilder - Hash each entry using the configured hash
- Sort all fingerprints.
- Write two files:
hashes_<hash_algorithm>.bin– concatenated fingerprints (without the 2-byte prefixes for maximum compression)index_<hash_algorithm>.bin– a 2-byte prefix index (65,537 offsets)
Lookup time
- Hash the input string with the same algorithm.
- Use the first 2 bytes of the hash to select a small bucket.
- Binary-search only inside that bucket.
Because hash prefixes are uniformly distributed, buckets are tiny.
Real world tests on the fixture file that contains ~134,000 entries in this repository (tests/Fixtures/terms_de.txt)
using the xxh3 hash algorithm:
- Used buckets:
57,112 (≈ 87.2%) - Average non-empty bucket size:
2.36entries - Worst case bucket size (biggest bucket):
11entries - Worst case comparisons:
log₂(11) = 4
Of course, the bigger your dictionary, the bigger the individual buckets.
All comparisons are fixed-width, not variable-length UTF-8 strings.
Properties
- ✅ Very low memory usage
- ✅ Predictable lookup cost
- ✅ No PHP hash tables
- ✅ No database
- ✅ No dependencies
- ⚠️ Read-only
- ⚠️ Hash collisions are theoretically possible
(acceptable for many linguistic and heuristic use cases)
Installation
composer require toflar/fast-set
Usage
1. Build the set (one-time)
Hashes are an excellent tool for evenly distributing entries, which is exactly what makes lookups in FastSet
extremely fast. However, hashes are not well suited for distribution:
- They are often larger than the original terms (especially for short words).
- They are effectively random, which means gzip compression performs very poorly.
Shipping prebuilt hash files would therefore often mean shipping more data than the original dictionary.
The solution: The SetBuilder
This library ships with a SetBuilder that is designed specifically for distribution size efficiency.
Instead of shipping hashes, you ship a compressed dictionary that:
- exploits shared prefixes between terms
- avoids repeating identical prefixes
- compresses extremely well with gzip
The hash-based data structures are then generated locally at build time.
$myOriginalSet = __DIR__ . '/dictionary.txt'; // one entry per line // Encode/Compress with the prefix algorithm: SetBuilder::buildSet($myOriginalSet, './compressed.txt'); // Encode/Compress with the prefix algorithm and gzip on top (the .gz suffix determines that): SetBuilder::buildSet($myOriginalSet, './compressed.gz'); // If your dictionary is not a line-feed separated file, but you have an array, you can also build it from an // array directly: SetBuilder::buildFromArray($myArray, './compressed.gz'); // You can use either xxh3 (default) or xxh128 // xxh128 will have a smaller probability for false-positives but require more memory $hashAlgorithm = 'xxh3'; // You then ship either "compressed.txt" or "compressed.gz" with your application. Instantiating // is then done as follows: $set = new FastSet(__DIR__ . '/dict', $hashAlgorithm); $set->build(__DIR__ . '/compressed.(txt|gz)'); // Must be a file built using the SetBuilder
Calling build creates the following files on-the-fly:
dict/
├── hashes_<hash_algorithm>.bin
└── index_<hash_algorithm>.bin
Important: Do not ship
hashes_<hash_algorithm>.binorindex_<hash_algorithm>.bin. Only ship the compressed dictionary created by theSetBuilder.
How to choose the right hashing algorithm
This library only supports the use of xxHash. Why? Because it's extremely fast.
It does, however, not make sense to allow all versions of xxHash. Hence, only xxh3 (64bit) and xxh128
(128bit)
are supported.
xxh3 will almost always be the right choice for you. That's why it's the default. But decide for yourself:
Collision probability by hash size for 100_000 terms:
| Hash | Bits | Probability of ≥1 collision | Interpretation |
|---|---|---|---|
| xxh32 | 32 | ~0.31 (31%) | ❌ Unacceptable |
| xxh3 | 64 | ~2.7 × 10⁻¹⁰ | ~1 in 3.7 billion |
| xxh128 | 128 | ~1.5 × 10⁻²⁹ | Effectively zero |
Collision probability by hash size for 500_000 terms:
| Hash | Bits | Probability of ≥1 collision | Interpretation |
|---|---|---|---|
| xxh32 | 32 | ~1.0 | ❌ Guaranteed collisions |
| xxh3 | 64 | ~6.8 × 10⁻⁹ | ~1 in 147 million |
| xxh128 | 128 | ~3.7 × 10⁻²⁸ | Effectively zero |
Which algorithm to choose depends on the risk of collisions you want to take but as long as you are < 1 million terms, 64-bit is astronomically safe. Maybe this is a good rule of thumb:
Use
xxh3by default. Switch toxxh128if your set contains millions of terms, or you need essentially zero collision risk.
2. Lookup
Once you have initialized/built your FastSet calling build() so that the required files have been built, you can
then use it as follows:
$set = new FastSet(__DIR__ . '/dict'); if ($set->has('look-me-up')) { // exists }
The hashes_<hash_algorithm>.bin and index_<hash_algorithm>.bin files are loaded lazily on first lookup, but you can also call initialize()
explicitly if you want to load them into memory at a specific point in time.
Caveats
- Not suitable if you need exact, collision-free guarantees
- Not suitable for mutable sets
- Input normalization (case-folding, Unicode normalization) is intentionally left to the caller
License
MIT
toflar/fast-set 适用场景与选型建议
toflar/fast-set 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1.24k 次下载、GitHub Stars 达 7, 最近一次更新时间为 2026 年 01 月 15 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「dictionary」 「hash」 「set」 「memory」 「binary」 「lookup」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 toflar/fast-set 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 toflar/fast-set 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 toflar/fast-set 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Simple, immutable data structures
Dictionary implementation for PHP
Baum is an implementation of the Nested Set pattern for Eloquent models.
Tresholds Governor, aims to facilitate the protection of authentication against brute force and dictionary attacks
Article CSS-ID Frontend Output Optimization
♺ Laravel Nova Hashids card. Convert your ids.
统计信息
- 总下载量: 1.24k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 7
- 点击次数: 42
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-01-15