Skip to content

Commit 8edafc8

Browse files
committed
Optimize writing text strings
1 parent 19ccf1f commit 8edafc8

2 files changed

Lines changed: 78 additions & 70 deletions

File tree

ValveKeyValue/ValveKeyValue/Serialization/KeyValues1/KV1TextSerializer.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Buffers;
12
using System.Globalization;
23
using System.Text;
34
using ValveKeyValue.Abstraction;
@@ -6,6 +7,8 @@ namespace ValveKeyValue.Serialization.KeyValues1
67
{
78
sealed class KV1TextSerializer : IVisitationListener, IDisposable
89
{
10+
static readonly SearchValues<char> CharsToEscape = SearchValues.Create("\"\\");
11+
912
public KV1TextSerializer(Stream stream, KVSerializerOptions options)
1013
{
1114
ArgumentNullException.ThrowIfNull(stream);
@@ -128,21 +131,28 @@ void WriteText(string text)
128131
{
129132
writer.Write('"');
130133

131-
foreach (var @char in text)
134+
if (!text.AsSpan().ContainsAny(CharsToEscape))
135+
{
136+
writer.Write(text);
137+
}
138+
else
132139
{
133-
switch (@char)
140+
foreach (var @char in text)
134141
{
135-
case '"':
136-
writer.Write("\\\"");
137-
break;
138-
139-
case '\\':
140-
writer.Write(options.HasEscapeSequences ? "\\\\" : "\\");
141-
break;
142-
143-
default:
144-
writer.Write(@char);
145-
break;
142+
switch (@char)
143+
{
144+
case '"':
145+
writer.Write("\\\"");
146+
break;
147+
148+
case '\\':
149+
writer.Write(options.HasEscapeSequences ? "\\\\" : "\\");
150+
break;
151+
152+
default:
153+
writer.Write(@char);
154+
break;
155+
}
146156
}
147157
}
148158

ValveKeyValue/ValveKeyValue/Serialization/KeyValues3/KV3TextSerializer.cs

Lines changed: 55 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Buffers;
12
using System.Globalization;
23
using System.Text;
34
using ValveKeyValue.Abstraction;
@@ -6,6 +7,8 @@ namespace ValveKeyValue.Serialization.KeyValues3
67
{
78
sealed class KV3TextSerializer : IVisitationListener, IDisposable
89
{
10+
static readonly SearchValues<char> CharsToEscape = SearchValues.Create("\n\t\\\"");
11+
912
public KV3TextSerializer(Stream stream, KVHeader header = null)
1013
{
1114
ArgumentNullException.ThrowIfNull(stream);
@@ -379,9 +382,7 @@ void WriteIndentation()
379382

380383
void WriteText(string text)
381384
{
382-
var isMultiline = text.Contains('\n', StringComparison.Ordinal);
383-
384-
if (isMultiline)
385+
if (text.Contains('\n', StringComparison.Ordinal))
385386
{
386387
text = text.Replace("\r\n", "\n", StringComparison.Ordinal);
387388
text = text.Replace("\"\"\"", "\\\"\"\"", StringComparison.Ordinal);
@@ -390,6 +391,12 @@ void WriteText(string text)
390391
writer.Write(text);
391392
writer.Write("\n\"\"\"");
392393
}
394+
else if (!text.AsSpan().ContainsAny(CharsToEscape))
395+
{
396+
writer.Write('"');
397+
writer.Write(text);
398+
writer.Write('"');
399+
}
393400
else
394401
{
395402
writer.Write('"');
@@ -398,10 +405,6 @@ void WriteText(string text)
398405
{
399406
switch (@char)
400407
{
401-
case '\n':
402-
writer.Write("\\n");
403-
break;
404-
405408
case '\t':
406409
writer.Write("\\t");
407410
break;
@@ -431,66 +434,61 @@ void WriteKey(string key)
431434
return;
432435
}
433436

434-
var escaped = key.Length == 0; // Quote empty strings
435-
var sb = new StringBuilder(key.Length + 2);
436-
sb.Append('"');
437-
438-
if (key.Length > 0 && key[0] >= '0' && key[0] <= '9')
437+
if (key.Length > 0 && !char.IsAsciiDigit(key[0]) && !NeedsQuoting(key))
439438
{
440-
// Quote when first character is a digit
441-
escaped = true;
439+
writer.Write(key);
442440
}
443-
444-
foreach (var @char in key)
441+
else
445442
{
446-
switch (@char)
443+
writer.Write('"');
444+
445+
foreach (var @char in key)
447446
{
448-
case '\t':
449-
escaped = true;
450-
sb.Append('\\');
451-
sb.Append('t');
452-
break;
453-
454-
case '\n':
455-
escaped = true;
456-
sb.Append('\\');
457-
sb.Append('n');
458-
break;
459-
460-
case '"':
461-
escaped = true;
462-
sb.Append('\\');
463-
sb.Append('"');
464-
break;
465-
466-
case '\\':
467-
escaped = true;
468-
sb.Append('\\');
469-
sb.Append('\\');
470-
break;
471-
472-
default:
473-
if (@char != '.' && @char != '_' && !char.IsAsciiLetterOrDigit(@char))
474-
{
475-
escaped = true;
476-
}
477-
478-
sb.Append(@char);
479-
break;
447+
switch (@char)
448+
{
449+
case '\t':
450+
writer.Write("\\t");
451+
break;
452+
453+
case '\n':
454+
writer.Write("\\n");
455+
break;
456+
457+
case '\'':
458+
writer.Write("\\'");
459+
break;
460+
461+
case '"':
462+
writer.Write("\\\"");
463+
break;
464+
465+
case '\\':
466+
writer.Write("\\\\");
467+
break;
468+
469+
default:
470+
writer.Write(@char);
471+
break;
472+
}
480473
}
481-
}
482474

483-
if (escaped)
484-
{
485-
sb.Append('"');
486-
writer.Write(sb.ToString());
475+
writer.Write('"');
487476
}
488-
else
477+
478+
writer.Write(" = ");
479+
}
480+
481+
static bool NeedsQuoting(string key)
482+
{
483+
foreach (var c in key)
489484
{
490-
writer.Write(key);
485+
if (c != '.' && c != '_' && !char.IsAsciiLetterOrDigit(c))
486+
{
487+
return true;
488+
}
491489
}
492490

493-
writer.Write(" = ");
491+
return false;
494492
}
495493

496494
void WriteFlag(KVFlag kvFlag)

0 commit comments

Comments
 (0)