未验证 提交 09d83464 编写于 作者: M Maryam Ariyan 提交者: GitHub

Makes GetChildKeys more efficient (#67186)

上级 018b85a3
...@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Configuration ...@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Configuration
/// </summary> /// </summary>
public class ConfigurationKeyComparer : IComparer<string> public class ConfigurationKeyComparer : IComparer<string>
{ {
private static readonly string[] _keyDelimiterArray = new[] { ConfigurationPath.KeyDelimiter }; private const char KeyDelimiter = ':';
/// <summary> /// <summary>
/// The default instance. /// The default instance.
...@@ -29,29 +29,61 @@ public class ConfigurationKeyComparer : IComparer<string> ...@@ -29,29 +29,61 @@ public class ConfigurationKeyComparer : IComparer<string>
/// <returns>Less than 0 if x is less than y, 0 if x is equal to y and greater than 0 if x is greater than y.</returns> /// <returns>Less than 0 if x is less than y, 0 if x is equal to y and greater than 0 if x is greater than y.</returns>
public int Compare(string? x, string? y) public int Compare(string? x, string? y)
{ {
string[] xParts = x?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); ReadOnlySpan<char> xSpan = x.AsSpan();
string[] yParts = y?.Split(_keyDelimiterArray, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); ReadOnlySpan<char> ySpan = y.AsSpan();
xSpan = SkipAheadOnDelimiter(xSpan);
ySpan = SkipAheadOnDelimiter(ySpan);
// Compare each part until we get two parts that are not equal // Compare each part until we get two parts that are not equal
for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++) while (!xSpan.IsEmpty && !ySpan.IsEmpty)
{ {
x = xParts[i]; int xDelimiterIndex = xSpan.IndexOf(KeyDelimiter);
y = yParts[i]; int yDelimiterIndex = ySpan.IndexOf(KeyDelimiter);
int compareResult = Compare(
xDelimiterIndex == -1 ? xSpan : xSpan.Slice(0, xDelimiterIndex),
yDelimiterIndex == -1 ? ySpan : ySpan.Slice(0, yDelimiterIndex));
if (compareResult != 0)
{
return compareResult;
}
int value1 = 0; xSpan = xDelimiterIndex == -1 ? default :
int value2 = 0; SkipAheadOnDelimiter(xSpan.Slice(xDelimiterIndex + 1));
ySpan = yDelimiterIndex == -1 ? default :
SkipAheadOnDelimiter(ySpan.Slice(yDelimiterIndex + 1));
}
bool xIsInt = x != null && int.TryParse(x, out value1); return xSpan.IsEmpty ? (ySpan.IsEmpty ? 0 : -1) : 1;
bool yIsInt = y != null && int.TryParse(y, out value2);
static ReadOnlySpan<char> SkipAheadOnDelimiter(ReadOnlySpan<char> a)
{
while (!a.IsEmpty && a[0] == KeyDelimiter)
{
a = a.Slice(1);
}
return a;
}
static int Compare(ReadOnlySpan<char> a, ReadOnlySpan<char> b)
{
#if NETCOREAPP
bool aIsInt = int.TryParse(a, out int value1);
bool bIsInt = int.TryParse(b, out int value2);
#else
bool aIsInt = int.TryParse(a.ToString(), out int value1);
bool bIsInt = int.TryParse(b.ToString(), out int value2);
#endif
int result; int result;
if (!xIsInt && !yIsInt) if (!aIsInt && !bIsInt)
{ {
// Both are strings // Both are strings
result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase); result = a.CompareTo(b, StringComparison.OrdinalIgnoreCase);
} }
else if (xIsInt && yIsInt) else if (aIsInt && bIsInt)
{ {
// Both are int // Both are int
result = value1 - value2; result = value1 - value2;
...@@ -59,19 +91,11 @@ public int Compare(string? x, string? y) ...@@ -59,19 +91,11 @@ public int Compare(string? x, string? y)
else else
{ {
// Only one of them is int // Only one of them is int
result = xIsInt ? -1 : 1; result = aIsInt ? -1 : 1;
} }
if (result != 0) return result;
{
// One of them is different
return result;
}
} }
// If we get here, the common parts are equal.
// If they are of the same length, then they are totally identical
return xParts.Length - yParts.Length;
} }
} }
} }
...@@ -14,6 +14,8 @@ public void CompareWithNull() ...@@ -14,6 +14,8 @@ public void CompareWithNull()
ComparerTest(null, null, 0); ComparerTest(null, null, 0);
ComparerTest(null, "a", -1); ComparerTest(null, "a", -1);
ComparerTest("b", null, 1); ComparerTest("b", null, 1);
ComparerTest(null, "a:b", -1);
ComparerTest(null, "a:b:c", -1);
} }
[Fact] [Fact]
...@@ -32,6 +34,20 @@ public void CompareWithDifferentLengths() ...@@ -32,6 +34,20 @@ public void CompareWithDifferentLengths()
ComparerTest("aa", "a", 1); ComparerTest("aa", "a", 1);
} }
[Fact]
public void CompareWithEmpty()
{
ComparerTest(":", "", 0);
ComparerTest(":", "::", 0);
ComparerTest(null, "", 0);
ComparerTest(":", null, 0);
ComparerTest("::", null, 0);
ComparerTest(" : : ", null, 1);
ComparerTest("b: :a", "b::a", -1);
ComparerTest("b:\t:a", "b::a", -1);
ComparerTest("b::a: ", "b::a:", 1);
}
[Fact] [Fact]
public void CompareWithLetters() public void CompareWithLetters()
{ {
......
...@@ -59,6 +59,64 @@ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders() ...@@ -59,6 +59,64 @@ public void LoadAndCombineKeyValuePairsFromDifferentConfigurationProviders()
Assert.Null(config["NotExist"]); Assert.Null(config["NotExist"]);
} }
[Fact]
private void GetChildKeys_CanChainEmptyKeys()
{
var input = new Dictionary<string, string>() { };
for (int i = 0; i < 1000; i++)
{
input.Add(new string(' ', i), string.Empty);
}
IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource
{
InitialData = input
})
.Build();
var chainedConfigurationSource = new ChainedConfigurationSource
{
Configuration = configurationRoot,
ShouldDisposeConfiguration = false,
};
var chainedConfiguration = new ChainedConfigurationProvider(chainedConfigurationSource);
IEnumerable<string> childKeys = chainedConfiguration.GetChildKeys(new string[0], null);
Assert.Equal(1000, childKeys.Count());
Assert.Equal(string.Empty, childKeys.First());
Assert.Equal(999, childKeys.Last().Length);
}
[Fact]
private void GetChildKeys_CanChainKeyWithNoDelimiter()
{
var input = new Dictionary<string, string>() { };
for (int i = 1000; i < 2000; i++)
{
input.Add(i.ToString(), string.Empty);
}
IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource
{
InitialData = input
})
.Build();
var chainedConfigurationSource = new ChainedConfigurationSource
{
Configuration = configurationRoot,
ShouldDisposeConfiguration = false,
};
var chainedConfiguration = new ChainedConfigurationProvider(chainedConfigurationSource);
IEnumerable<string> childKeys = chainedConfiguration.GetChildKeys(new string[0], null);
Assert.Equal(1000, childKeys.Count());
Assert.Equal("1000", childKeys.First());
Assert.Equal("1999", childKeys.Last());
}
[Fact] [Fact]
public void CanChainConfiguration() public void CanChainConfiguration()
{ {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册