// Copyright 2009-2022 Josh Close
// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0.
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0.
// https://github.com/JoshClose/CsvHelper
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
// https://blog.markvincze.com/back-to-basics-dictionary-part-2-net-implementation/
namespace CsvHelper
{
///
/// Caches fields.
/// Based on C#'s .
///
internal class FieldCache
{
private readonly int maxFieldSize;
private int size;
private int[] buckets;
private Entry[] entries;
private int count;
public FieldCache(int initialSize = 128, int maxFieldSize = 128)
{
this.maxFieldSize = maxFieldSize;
size = initialSize;
buckets = new int[size];
entries = new Entry[size];
}
public string GetField(char[] buffer, int start, int length)
{
if (length == 0)
{
return string.Empty;
}
if (length > maxFieldSize)
{
return new string(buffer, start, length);
}
var hashCode = GetHashCode(buffer, start, length);
ref var bucket = ref GetBucket(hashCode);
int i = bucket - 1;
while ((uint)i < (uint)entries.Length)
{
ref var entry = ref entries[i];
if (entry.HashCode == hashCode && entry.Value.AsSpan().SequenceEqual(new Span(buffer, start, length)))
{
return entry.Value;
}
i = entry.Next;
}
if (count == entries.Length)
{
Resize();
bucket = ref GetBucket(hashCode);
}
ref var reference = ref entries[count];
reference.HashCode = hashCode;
reference.Next = bucket - 1;
reference.Value = new string(buffer, start, length);
bucket = count + 1;
count++;
return reference.Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint GetHashCode(char[] buffer, int start, int length)
{
unchecked
{
uint hash = 17;
for (var i = start; i < start + length; i++)
{
hash = hash * 31 + buffer[i];
}
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref int GetBucket(uint hashCode)
{
return ref buckets[hashCode & buckets.Length - 1];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Resize()
{
size *= 2;
var tempEntries = new Entry[size];
Array.Copy(entries, tempEntries, count);
buckets = new int[size];
for (int i = 0; i < count; i++)
{
ref var tempEntry = ref tempEntries[i];
if (tempEntry.Next >= -1)
{
ref var bucket = ref GetBucket(tempEntry.HashCode);
tempEntry.Next = bucket - 1;
bucket = i + 1;
}
}
entries = tempEntries;
}
[DebuggerDisplay("HashCode = {HashCode}, Next = {Next}, Value = {Value}")]
private struct Entry
{
public uint HashCode;
public int Next;
public string Value;
}
}
}