@@ -12,50 +12,78 @@ namespace Microsoft.AspNetCore.SignalR.Internal;
12
12
/// </summary>
13
13
internal sealed class Utf8HashLookup
14
14
{
15
- private int [ ] buckets ;
16
- private Slot [ ] slots ;
17
- private int count ;
15
+ private int [ ] _buckets ;
16
+ private int [ ] _caseSensitiveBuckets ;
17
+ private Slot [ ] _slots ;
18
+ private int _count ;
18
19
19
20
private const int HashCodeMask = 0x7fffffff ;
20
21
21
22
internal Utf8HashLookup ( )
22
23
{
23
- buckets = new int [ 7 ] ;
24
- slots = new Slot [ 7 ] ;
24
+ _buckets = new int [ 7 ] ;
25
+ _caseSensitiveBuckets = new int [ 7 ] ;
26
+ _slots = new Slot [ 7 ] ;
25
27
}
26
28
27
29
internal void Add ( string value )
28
30
{
29
- var hashCode = GetKeyHashCode ( value . AsSpan ( ) ) ;
30
-
31
- if ( count == slots . Length )
31
+ if ( _count == _slots . Length )
32
32
{
33
33
Resize ( ) ;
34
34
}
35
35
36
- int index = count ;
37
- count ++ ;
36
+ int slotIndex = _count ;
37
+ _count ++ ;
38
+
39
+ var encodedValue = Encoding . UTF8 . GetBytes ( value ) ;
40
+ var hashCode = GetHashCode ( value . AsSpan ( ) ) ;
41
+ var caseSensitiveHashCode = GetCaseSensitiveHashCode ( encodedValue ) ;
42
+ int bucketIndex = hashCode % _buckets . Length ;
43
+ int caseSensitiveBucketIndex = caseSensitiveHashCode % _caseSensitiveBuckets . Length ;
44
+
45
+ _slots [ slotIndex ] . hashCode = hashCode ;
46
+ _slots [ slotIndex ] . caseSensitiveHashCode = caseSensitiveHashCode ;
47
+
48
+ _slots [ slotIndex ] . value = value ;
49
+ _slots [ slotIndex ] . encodedValue = encodedValue ;
50
+
51
+ _slots [ slotIndex ] . next = _buckets [ bucketIndex ] - 1 ;
52
+ _slots [ slotIndex ] . caseSensitiveNext = _caseSensitiveBuckets [ caseSensitiveBucketIndex ] - 1 ;
53
+
54
+ _buckets [ bucketIndex ] = slotIndex + 1 ;
55
+ _caseSensitiveBuckets [ caseSensitiveBucketIndex ] = slotIndex + 1 ;
56
+ }
57
+
58
+ internal bool TryGetValue ( ReadOnlySpan < byte > encodedValue , [ MaybeNullWhen ( false ) , AllowNull ] out string value )
59
+ {
60
+ var caseSensitiveHashCode = GetCaseSensitiveHashCode ( encodedValue ) ;
61
+
62
+ for ( var i = _caseSensitiveBuckets [ caseSensitiveHashCode % _caseSensitiveBuckets . Length ] - 1 ; i >= 0 ; i = _slots [ i ] . caseSensitiveNext )
63
+ {
64
+ if ( _slots [ i ] . caseSensitiveHashCode == caseSensitiveHashCode && encodedValue . SequenceEqual ( _slots [ i ] . encodedValue . AsSpan ( ) ) )
65
+ {
66
+ value = _slots [ i ] . value ;
67
+ return true ;
68
+ }
69
+ }
38
70
39
- int bucket = hashCode % buckets . Length ;
40
- slots [ index ] . hashCode = hashCode ;
41
- slots [ index ] . key = value ;
42
- slots [ index ] . value = value ;
43
- slots [ index ] . next = buckets [ bucket ] - 1 ;
44
- buckets [ bucket ] = index + 1 ;
71
+ // If we cannot find a case-sensitive match, we transcode the encodedValue to a stackalloced UTF16 string
72
+ // and do an OrdinalIgnoreCase comparison.
73
+ return TryGetValueSlow ( encodedValue , out value ) ;
45
74
}
46
75
47
- internal bool TryGetValue ( ReadOnlySpan < byte > utf8 , [ MaybeNullWhen ( false ) , AllowNull ] out string value )
76
+ private bool TryGetValueSlow ( ReadOnlySpan < byte > encodedValue , [ MaybeNullWhen ( false ) , AllowNull ] out string value )
48
77
{
49
78
const int StackAllocThreshold = 128 ;
50
79
51
- // Transcode to utf16 for comparison
52
80
char [ ] ? pooled = null ;
53
- var count = Encoding . UTF8 . GetCharCount ( utf8 ) ;
81
+ var count = Encoding . UTF8 . GetCharCount ( encodedValue ) ;
54
82
var chars = count <= StackAllocThreshold ?
55
83
stackalloc char [ StackAllocThreshold ] :
56
84
( pooled = ArrayPool < char > . Shared . Rent ( count ) ) ;
57
- var encoded = Encoding . UTF8 . GetChars ( utf8 , chars ) ;
58
- var hasValue = TryGetValue ( chars [ ..encoded ] , out value ) ;
85
+ var encoded = Encoding . UTF8 . GetChars ( encodedValue , chars ) ;
86
+ var hasValue = TryGetValueFromChars ( chars [ ..encoded ] , out value ) ;
59
87
if ( pooled is not null )
60
88
{
61
89
ArrayPool < char > . Shared . Return ( pooled ) ;
@@ -64,15 +92,15 @@ internal bool TryGetValue(ReadOnlySpan<byte> utf8, [MaybeNullWhen(false), AllowN
64
92
return hasValue ;
65
93
}
66
94
67
- private bool TryGetValue ( ReadOnlySpan < char > key , [ MaybeNullWhen ( false ) , AllowNull ] out string value )
95
+ private bool TryGetValueFromChars ( ReadOnlySpan < char > key , [ MaybeNullWhen ( false ) , AllowNull ] out string value )
68
96
{
69
- var hashCode = GetKeyHashCode ( key ) ;
97
+ var hashCode = GetHashCode ( key ) ;
70
98
71
- for ( var i = buckets [ hashCode % buckets . Length ] - 1 ; i >= 0 ; i = slots [ i ] . next )
99
+ for ( var i = _buckets [ hashCode % _buckets . Length ] - 1 ; i >= 0 ; i = _slots [ i ] . next )
72
100
{
73
- if ( slots [ i ] . hashCode == hashCode && key . Equals ( slots [ i ] . key , StringComparison . OrdinalIgnoreCase ) )
101
+ if ( _slots [ i ] . hashCode == hashCode && key . Equals ( _slots [ i ] . value , StringComparison . OrdinalIgnoreCase ) )
74
102
{
75
- value = slots [ i ] . value ;
103
+ value = _slots [ i ] . value ;
76
104
return true ;
77
105
}
78
106
}
@@ -81,32 +109,52 @@ private bool TryGetValue(ReadOnlySpan<char> key, [MaybeNullWhen(false), AllowNul
81
109
return false ;
82
110
}
83
111
84
- private static int GetKeyHashCode ( ReadOnlySpan < char > key )
112
+ private static int GetHashCode ( ReadOnlySpan < char > value ) =>
113
+ HashCodeMask & string . GetHashCode ( value , StringComparison . OrdinalIgnoreCase ) ;
114
+
115
+ private static int GetCaseSensitiveHashCode ( ReadOnlySpan < byte > encodedValue )
85
116
{
86
- return HashCodeMask & string . GetHashCode ( key , StringComparison . OrdinalIgnoreCase ) ;
117
+ var hashCode = new HashCode ( ) ;
118
+ hashCode . AddBytes ( encodedValue ) ;
119
+ return HashCodeMask & hashCode . ToHashCode ( ) ;
87
120
}
88
121
89
122
private void Resize ( )
90
123
{
91
- var newSize = checked ( count * 2 + 1 ) ;
92
- var newBuckets = new int [ newSize ] ;
124
+ var newSize = checked ( _count * 2 + 1 ) ;
93
125
var newSlots = new Slot [ newSize ] ;
94
- Array . Copy ( slots , newSlots , count ) ;
95
- for ( int i = 0 ; i < count ; i ++ )
126
+
127
+ var newBuckets = new int [ newSize ] ;
128
+ var newCaseSensitiveBuckets = new int [ newSize ] ;
129
+
130
+ Array . Copy ( _slots , newSlots , _count ) ;
131
+
132
+ for ( int i = 0 ; i < _count ; i ++ )
96
133
{
97
134
int bucket = newSlots [ i ] . hashCode % newSize ;
98
135
newSlots [ i ] . next = newBuckets [ bucket ] - 1 ;
99
136
newBuckets [ bucket ] = i + 1 ;
137
+
138
+ int caseSensitiveBucket = newSlots [ i ] . caseSensitiveHashCode % newSize ;
139
+ newSlots [ i ] . caseSensitiveNext = newCaseSensitiveBuckets [ caseSensitiveBucket ] - 1 ;
140
+ newCaseSensitiveBuckets [ caseSensitiveBucket ] = i + 1 ;
100
141
}
101
- buckets = newBuckets ;
102
- slots = newSlots ;
142
+
143
+ _slots = newSlots ;
144
+
145
+ _buckets = newBuckets ;
146
+ _caseSensitiveBuckets = newCaseSensitiveBuckets ;
103
147
}
104
148
105
- internal struct Slot
149
+ private struct Slot
106
150
{
107
151
internal int hashCode ;
108
- internal int next ;
109
- internal string key ;
152
+ internal int caseSensitiveHashCode ;
153
+
110
154
internal string value ;
155
+ internal byte [ ] encodedValue ;
156
+
157
+ internal int next ;
158
+ internal int caseSensitiveNext ;
111
159
}
112
160
}
0 commit comments