forked from StackExchange/StackExchange.Redis
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEncodingExtensions.cs
More file actions
97 lines (89 loc) · 4.21 KB
/
Copy pathEncodingExtensions.cs
File metadata and controls
97 lines (89 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
using System;
using System.Buffers;
using System.Text;
namespace StackExchange.Redis;
internal static class EncodingExtensions
{
// Above this length we stream through a Decoder; at or below it we linearize onto the stack and use the
// contiguous span overloads, avoiding the Decoder heap allocation for the common (small) case.
private const int MaxStackLinearizeBytes = 128;
// Note: there is no BCL Encoding.GetCharCount(in ReadOnlySequence<byte>) on any TFM (unlike GetChars,
// which the BCL provides on modern runtimes), so we supply it for all targets.
public static int GetCharCount(this Encoding encoding, in ReadOnlySequence<byte> seq)
{
// common case: a single segment can be measured directly, with no decoder state to track
if (seq.IsSingleSegment) return encoding.GetCharCount(seq.FirstSpan);
// small payloads: linearize onto the stack and measure contiguously - no Decoder allocation, and no
// glyph-straddles-a-boundary problem once the bytes are contiguous
long length = seq.Length;
if (length <= MaxStackLinearizeBytes)
{
Span<byte> linear = stackalloc byte[(int)length];
seq.CopyTo(linear);
return encoding.GetCharCount(linear);
}
// larger multi-segment: a multi-byte glyph can straddle a segment boundary, so we *must* decode with
// a stateful decoder rather than summing per-segment counts (which would over-count split glyphs).
// Note we cannot use Decoder.GetCharCount: unlike GetChars/Convert it does not carry partial-glyph
// state between calls, so it too over-counts. Decoder.Convert reports the chars produced without us
// having to keep them, so we decode into a small scratch buffer and discard the output, flushing on
// the final segment.
var decoder = encoding.GetDecoder();
Span<char> scratch = stackalloc char[128];
int count = 0;
var position = seq.Start;
bool have = seq.TryGet(ref position, out var current);
while (have)
{
var nextPosition = position;
bool haveNext = seq.TryGet(ref nextPosition, out var next);
var bytes = current.Span;
bool flush = !haveNext;
bool completed;
do
{
decoder.Convert(bytes, scratch, flush, out int bytesUsed, out int charsUsed, out completed);
count += charsUsed;
bytes = bytes.Slice(bytesUsed);
}
while (!bytes.IsEmpty || (flush && !completed));
current = next;
position = nextPosition;
have = haveNext;
}
return count;
}
#if NET461 || NET472 || NETSTANDARD2_0
// modern runtimes have a BCL Encoding.GetChars(in ReadOnlySequence<byte>, Span<char>); we only need to
// supply it for the older targets. The flush-on-final-segment logic mirrors GetCharCount above, which
// guarantees the two agree on the char count - so a buffer sized via GetCharCount cannot overflow here.
public static int GetChars(this Encoding encoding, in ReadOnlySequence<byte> seq, Span<char> chars)
{
if (seq.IsSingleSegment) return encoding.GetChars(seq.FirstSpan, chars);
// small payloads: linearize onto the stack and decode contiguously - no Decoder allocation
long length = seq.Length;
if (length <= MaxStackLinearizeBytes)
{
Span<byte> linear = stackalloc byte[(int)length];
seq.CopyTo(linear);
return encoding.GetChars(linear, chars);
}
var decoder = encoding.GetDecoder();
int total = 0;
var position = seq.Start;
bool have = seq.TryGet(ref position, out var current);
while (have)
{
var nextPosition = position;
bool haveNext = seq.TryGet(ref nextPosition, out var next);
int written = decoder.GetChars(current.Span, chars, flush: !haveNext);
chars = chars.Slice(written); // advance by chars *written*, not by bytes consumed
total += written;
current = next;
position = nextPosition;
have = haveNext;
}
return total;
}
#endif
}