|
120 | 120 | //! - Access patterns have temporal locality |
121 | 121 | //! |
122 | 122 | //! **Avoid NRU when:** |
123 | | -//! - You need strict LRU ordering (use LRU) |
124 | | -//! - You need O(1) eviction guarantees (use Clock with hand) |
125 | | -//! - You need scan resistance (use S3-FIFO, LRU-K) |
| 123 | +//! - You need strict LRU ordering (use [`ConcurrentLruCache`](super::lru::ConcurrentLruCache)) |
| 124 | +//! - You need O(1) eviction guarantees (use [`ClockCache`](super::clock::ClockCache)) |
| 125 | +//! - You need scan resistance (use [`S3FifoCache`](super::s3_fifo::S3FifoCache), |
| 126 | +//! [`LrukCache`](super::lru_k::LrukCache)) |
126 | 127 | //! |
127 | 128 | //! ## Example Usage |
128 | 129 | //! |
@@ -225,10 +226,7 @@ struct Entry<V> { |
225 | 226 | /// # Implementation |
226 | 227 | /// |
227 | 228 | /// Uses HashMap + Vec for O(1) access with swap-remove for eviction. |
228 | | -pub struct NruCache<K, V> |
229 | | -where |
230 | | - K: Clone + Eq + Hash, |
231 | | -{ |
| 229 | +pub struct NruCache<K, V> { |
232 | 230 | /// HashMap for O(1) key lookup |
233 | 231 | map: FxHashMap<K, Entry<V>>, |
234 | 232 | /// Dense array of keys for eviction scanning |
@@ -267,11 +265,74 @@ where |
267 | 265 | } |
268 | 266 |
|
269 | 267 | /// Returns `true` if the cache is empty. |
| 268 | + /// |
| 269 | + /// # Example |
| 270 | + /// |
| 271 | + /// ``` |
| 272 | + /// use cachekit::policy::nru::NruCache; |
| 273 | + /// use cachekit::traits::{CoreCache, ReadOnlyCache}; |
| 274 | + /// |
| 275 | + /// let mut cache = NruCache::<&str, i32>::new(10); |
| 276 | + /// assert!(cache.is_empty()); |
| 277 | + /// |
| 278 | + /// cache.insert("a", 1); |
| 279 | + /// assert!(!cache.is_empty()); |
| 280 | + /// ``` |
270 | 281 | #[inline] |
271 | 282 | pub fn is_empty(&self) -> bool { |
272 | 283 | self.map.is_empty() |
273 | 284 | } |
274 | 285 |
|
| 286 | + /// Returns an iterator over all key-value pairs in the cache. |
| 287 | + /// |
| 288 | + /// Iteration order is unspecified. |
| 289 | + /// |
| 290 | + /// # Example |
| 291 | + /// |
| 292 | + /// ``` |
| 293 | + /// use cachekit::policy::nru::NruCache; |
| 294 | + /// use cachekit::traits::CoreCache; |
| 295 | + /// |
| 296 | + /// let mut cache = NruCache::new(10); |
| 297 | + /// cache.insert("a", 1); |
| 298 | + /// cache.insert("b", 2); |
| 299 | + /// |
| 300 | + /// let pairs: Vec<_> = cache.iter().collect(); |
| 301 | + /// assert_eq!(pairs.len(), 2); |
| 302 | + /// ``` |
| 303 | + #[inline] |
| 304 | + pub fn iter(&self) -> Iter<'_, K, V> { |
| 305 | + Iter { |
| 306 | + inner: self.map.iter(), |
| 307 | + } |
| 308 | + } |
| 309 | + |
| 310 | + /// Returns a mutable iterator over all key-value pairs in the cache. |
| 311 | + /// |
| 312 | + /// Only values are mutable; keys and eviction metadata are not exposed. |
| 313 | + /// Iteration order is unspecified. |
| 314 | + /// |
| 315 | + /// # Example |
| 316 | + /// |
| 317 | + /// ``` |
| 318 | + /// use cachekit::policy::nru::NruCache; |
| 319 | + /// use cachekit::traits::CoreCache; |
| 320 | + /// |
| 321 | + /// let mut cache = NruCache::new(10); |
| 322 | + /// cache.insert("a", 1); |
| 323 | + /// cache.insert("b", 2); |
| 324 | + /// |
| 325 | + /// for (_key, value) in cache.iter_mut() { |
| 326 | + /// *value += 10; |
| 327 | + /// } |
| 328 | + /// ``` |
| 329 | + #[inline] |
| 330 | + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { |
| 331 | + IterMut { |
| 332 | + inner: self.map.iter_mut(), |
| 333 | + } |
| 334 | + } |
| 335 | + |
275 | 336 | /// Evicts an entry using NRU policy. |
276 | 337 | /// |
277 | 338 | /// First tries to find an unreferenced entry. If all entries are referenced, |
@@ -344,18 +405,54 @@ where |
344 | 405 | /// Returns `true` if the cache contains the key. |
345 | 406 | /// |
346 | 407 | /// Does not affect the reference bit. |
| 408 | + /// |
| 409 | + /// # Example |
| 410 | + /// |
| 411 | + /// ``` |
| 412 | + /// use cachekit::policy::nru::NruCache; |
| 413 | + /// use cachekit::traits::{CoreCache, ReadOnlyCache}; |
| 414 | + /// |
| 415 | + /// let mut cache = NruCache::new(10); |
| 416 | + /// cache.insert("key", 1); |
| 417 | + /// |
| 418 | + /// assert!(cache.contains(&"key")); |
| 419 | + /// assert!(!cache.contains(&"missing")); |
| 420 | + /// ``` |
347 | 421 | #[inline] |
348 | 422 | fn contains(&self, key: &K) -> bool { |
349 | 423 | self.map.contains_key(key) |
350 | 424 | } |
351 | 425 |
|
352 | 426 | /// Returns the number of entries in the cache. |
| 427 | + /// |
| 428 | + /// # Example |
| 429 | + /// |
| 430 | + /// ``` |
| 431 | + /// use cachekit::policy::nru::NruCache; |
| 432 | + /// use cachekit::traits::{CoreCache, ReadOnlyCache}; |
| 433 | + /// |
| 434 | + /// let mut cache = NruCache::new(10); |
| 435 | + /// assert_eq!(cache.len(), 0); |
| 436 | + /// |
| 437 | + /// cache.insert("a", 1); |
| 438 | + /// assert_eq!(cache.len(), 1); |
| 439 | + /// ``` |
353 | 440 | #[inline] |
354 | 441 | fn len(&self) -> usize { |
355 | 442 | self.map.len() |
356 | 443 | } |
357 | 444 |
|
358 | 445 | /// Returns the maximum capacity of the cache. |
| 446 | + /// |
| 447 | + /// # Example |
| 448 | + /// |
| 449 | + /// ``` |
| 450 | + /// use cachekit::policy::nru::NruCache; |
| 451 | + /// use cachekit::traits::ReadOnlyCache; |
| 452 | + /// |
| 453 | + /// let cache = NruCache::<String, i32>::new(50); |
| 454 | + /// assert_eq!(cache.capacity(), 50); |
| 455 | + /// ``` |
359 | 456 | #[inline] |
360 | 457 | fn capacity(&self) -> usize { |
361 | 458 | self.capacity |
@@ -474,6 +571,20 @@ where |
474 | 571 | } |
475 | 572 |
|
476 | 573 | /// Clears all entries from the cache. |
| 574 | + /// |
| 575 | + /// # Example |
| 576 | + /// |
| 577 | + /// ``` |
| 578 | + /// use cachekit::policy::nru::NruCache; |
| 579 | + /// use cachekit::traits::{CoreCache, ReadOnlyCache}; |
| 580 | + /// |
| 581 | + /// let mut cache = NruCache::new(10); |
| 582 | + /// cache.insert("a", 1); |
| 583 | + /// cache.insert("b", 2); |
| 584 | + /// |
| 585 | + /// cache.clear(); |
| 586 | + /// assert!(cache.is_empty()); |
| 587 | + /// ``` |
477 | 588 | fn clear(&mut self) { |
478 | 589 | self.map.clear(); |
479 | 590 | self.keys.clear(); |
@@ -530,6 +641,20 @@ where |
530 | 641 | K: Clone + Eq + Hash, |
531 | 642 | { |
532 | 643 | /// Returns a snapshot of cache metrics. |
| 644 | + /// |
| 645 | + /// # Example |
| 646 | + /// |
| 647 | + /// ``` |
| 648 | + /// use cachekit::policy::nru::NruCache; |
| 649 | + /// use cachekit::traits::CoreCache; |
| 650 | + /// |
| 651 | + /// let mut cache = NruCache::new(10); |
| 652 | + /// cache.insert("a", 1); |
| 653 | + /// let _ = cache.get(&"a"); |
| 654 | + /// |
| 655 | + /// let snap = cache.metrics_snapshot(); |
| 656 | + /// assert_eq!(snap.get_hits, 1); |
| 657 | + /// ``` |
533 | 658 | pub fn metrics_snapshot(&self) -> NruMetricsSnapshot { |
534 | 659 | NruMetricsSnapshot { |
535 | 660 | get_calls: self.metrics.get_calls, |
@@ -572,11 +697,174 @@ where |
572 | 697 | } |
573 | 698 | } |
574 | 699 |
|
| 700 | +impl<K, V> Clone for NruCache<K, V> |
| 701 | +where |
| 702 | + K: Clone + Eq + Hash, |
| 703 | + V: Clone, |
| 704 | +{ |
| 705 | + fn clone(&self) -> Self { |
| 706 | + Self { |
| 707 | + map: self.map.clone(), |
| 708 | + keys: self.keys.clone(), |
| 709 | + capacity: self.capacity, |
| 710 | + #[cfg(feature = "metrics")] |
| 711 | + metrics: self.metrics, |
| 712 | + } |
| 713 | + } |
| 714 | +} |
| 715 | + |
| 716 | +// --------------------------------------------------------------------------- |
| 717 | +// Iterators |
| 718 | +// --------------------------------------------------------------------------- |
| 719 | + |
| 720 | +/// Iterator over shared references to key-value pairs in an [`NruCache`]. |
| 721 | +/// |
| 722 | +/// Created by [`NruCache::iter`]. |
| 723 | +pub struct Iter<'a, K, V> { |
| 724 | + inner: std::collections::hash_map::Iter<'a, K, Entry<V>>, |
| 725 | +} |
| 726 | + |
| 727 | +impl<'a, K, V> Iterator for Iter<'a, K, V> { |
| 728 | + type Item = (&'a K, &'a V); |
| 729 | + |
| 730 | + #[inline] |
| 731 | + fn next(&mut self) -> Option<Self::Item> { |
| 732 | + self.inner.next().map(|(k, e)| (k, &e.value)) |
| 733 | + } |
| 734 | + |
| 735 | + #[inline] |
| 736 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 737 | + self.inner.size_hint() |
| 738 | + } |
| 739 | +} |
| 740 | + |
| 741 | +impl<K, V> ExactSizeIterator for Iter<'_, K, V> {} |
| 742 | + |
| 743 | +impl<K, V> std::iter::FusedIterator for Iter<'_, K, V> {} |
| 744 | + |
| 745 | +/// Iterator over mutable references to values (with shared key references) |
| 746 | +/// in an [`NruCache`]. |
| 747 | +/// |
| 748 | +/// Created by [`NruCache::iter_mut`]. |
| 749 | +pub struct IterMut<'a, K, V> { |
| 750 | + inner: std::collections::hash_map::IterMut<'a, K, Entry<V>>, |
| 751 | +} |
| 752 | + |
| 753 | +impl<'a, K, V> Iterator for IterMut<'a, K, V> { |
| 754 | + type Item = (&'a K, &'a mut V); |
| 755 | + |
| 756 | + #[inline] |
| 757 | + fn next(&mut self) -> Option<Self::Item> { |
| 758 | + self.inner.next().map(|(k, e)| (k, &mut e.value)) |
| 759 | + } |
| 760 | + |
| 761 | + #[inline] |
| 762 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 763 | + self.inner.size_hint() |
| 764 | + } |
| 765 | +} |
| 766 | + |
| 767 | +impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {} |
| 768 | + |
| 769 | +impl<K, V> std::iter::FusedIterator for IterMut<'_, K, V> {} |
| 770 | + |
| 771 | +/// Owning iterator over key-value pairs from an [`NruCache`]. |
| 772 | +/// |
| 773 | +/// Created by the `IntoIterator` implementation on `NruCache`. |
| 774 | +pub struct IntoIter<K, V> { |
| 775 | + inner: std::collections::hash_map::IntoIter<K, Entry<V>>, |
| 776 | +} |
| 777 | + |
| 778 | +impl<K, V> Iterator for IntoIter<K, V> { |
| 779 | + type Item = (K, V); |
| 780 | + |
| 781 | + #[inline] |
| 782 | + fn next(&mut self) -> Option<Self::Item> { |
| 783 | + self.inner.next().map(|(k, e)| (k, e.value)) |
| 784 | + } |
| 785 | + |
| 786 | + #[inline] |
| 787 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 788 | + self.inner.size_hint() |
| 789 | + } |
| 790 | +} |
| 791 | + |
| 792 | +impl<K, V> ExactSizeIterator for IntoIter<K, V> {} |
| 793 | + |
| 794 | +impl<K, V> std::iter::FusedIterator for IntoIter<K, V> {} |
| 795 | + |
| 796 | +// --------------------------------------------------------------------------- |
| 797 | +// IntoIterator |
| 798 | +// --------------------------------------------------------------------------- |
| 799 | + |
| 800 | +impl<K, V> IntoIterator for NruCache<K, V> |
| 801 | +where |
| 802 | + K: Clone + Eq + Hash, |
| 803 | +{ |
| 804 | + type Item = (K, V); |
| 805 | + type IntoIter = IntoIter<K, V>; |
| 806 | + |
| 807 | + fn into_iter(self) -> Self::IntoIter { |
| 808 | + IntoIter { |
| 809 | + inner: self.map.into_iter(), |
| 810 | + } |
| 811 | + } |
| 812 | +} |
| 813 | + |
| 814 | +impl<'a, K, V> IntoIterator for &'a NruCache<K, V> |
| 815 | +where |
| 816 | + K: Clone + Eq + Hash, |
| 817 | +{ |
| 818 | + type Item = (&'a K, &'a V); |
| 819 | + type IntoIter = Iter<'a, K, V>; |
| 820 | + |
| 821 | + fn into_iter(self) -> Self::IntoIter { |
| 822 | + self.iter() |
| 823 | + } |
| 824 | +} |
| 825 | + |
| 826 | +impl<'a, K, V> IntoIterator for &'a mut NruCache<K, V> |
| 827 | +where |
| 828 | + K: Clone + Eq + Hash, |
| 829 | +{ |
| 830 | + type Item = (&'a K, &'a mut V); |
| 831 | + type IntoIter = IterMut<'a, K, V>; |
| 832 | + |
| 833 | + fn into_iter(self) -> Self::IntoIter { |
| 834 | + self.iter_mut() |
| 835 | + } |
| 836 | +} |
| 837 | + |
| 838 | +// --------------------------------------------------------------------------- |
| 839 | +// Extend |
| 840 | +// --------------------------------------------------------------------------- |
| 841 | + |
| 842 | +impl<K, V> Extend<(K, V)> for NruCache<K, V> |
| 843 | +where |
| 844 | + K: Clone + Eq + Hash, |
| 845 | +{ |
| 846 | + fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) { |
| 847 | + for (k, v) in iter { |
| 848 | + self.insert(k, v); |
| 849 | + } |
| 850 | + } |
| 851 | +} |
| 852 | + |
575 | 853 | #[cfg(test)] |
576 | 854 | mod tests { |
577 | 855 | use super::*; |
578 | 856 | use crate::traits::{CoreCache, MutableCache}; |
579 | 857 |
|
| 858 | + #[allow(dead_code)] |
| 859 | + const _: () = { |
| 860 | + fn assert_send<T: Send>() {} |
| 861 | + fn assert_sync<T: Sync>() {} |
| 862 | + fn check() { |
| 863 | + assert_send::<NruCache<String, i32>>(); |
| 864 | + assert_sync::<NruCache<String, i32>>(); |
| 865 | + } |
| 866 | + }; |
| 867 | + |
580 | 868 | #[test] |
581 | 869 | fn test_new() { |
582 | 870 | let cache: NruCache<i32, i32> = NruCache::new(10); |
|
0 commit comments