Почему я не могу предварительно выделить хэш-сет<T>
Почему я не могу предварительно выделить hashset<T>
?
Есть моменты, когда я могу добавить много элементов к нему, и я хочу устранить изменение размера.
5 ответов
Нет технической причины, по которой это не должно быть возможно - Microsoft просто не решила представить конструктор с начальной емкостью.
Если вы можете вызвать конструктор, который принимает IEnumerable<T>
и использовать реализацию ICollection<T>
Я считаю, что будет использовать размер коллекции в качестве начальной минимальной емкости. Обратите внимание, это деталь реализации. Емкость должна быть достаточно большой, чтобы хранить все отдельные элементы...
РЕДАКТИРОВАТЬ: Я считаю, что если емкость оказывается намного больше, чем нужно, конструктор будет обрезать избыток, когда он закончит, чтобы узнать, сколько на самом деле различных элементов.
В любом случае, если у вас есть коллекция, которую вы собираетесь добавить в HashSet<T>
и это реализует ICollection<T>
то передача его конструктору вместо добавления элементов один за другим будет в принципе победой:)
РЕДАКТИРОВАТЬ: один из обходных путей будет использовать Dictionary<TKey, TValue>
вместо HashSet<T>
и просто не использовать значения. Это не будет работать во всех случаях, так как это не даст вам тот же интерфейс, что и HashSet<T>
,
Ответ Джона Скита почти полный. Чтобы решить эту проблему с HashSet<int>
Я должен был сделать следующее:
public class ClassUsingHashSet
{
private static readonly List<int> PreallocationList
= Enumerable.Range(0, 10000).ToList();
public ClassUsingHashSet()
{
this.hashSet = new HashSet<int>(PreallocationList);
this.hashSet.Clear();
}
public void Add(int item)
{
this.hashSet.Add(item);
}
private HashSet<int> hashSet;
}
Этот трюк работает, потому что после Clear
HashSet
не обрезается, как описано в документации:
Емкость остается неизменной до вызова
TrimExcess
сделан.
Я использую этот код для установки начальной емкости для HashSet. Вы можете использовать его как расширение или напрямую
public static class HashSetExtensions
{
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
public static HashSet<T> SetCapacity<T>(this HashSet<T> hs, int capacity)
{
var initialize = hs.GetType().GetMethod("Initialize", Flags);
initialize.Invoke(hs, new object[] { capacity });
return hs;
}
public static HashSet<T> GetHashSet<T>(int capacity)
{
return new HashSet<T>().SetCapacity(capacity);
}
}
UPD. 04 июля
Этот код также может быть улучшен за счет использования кэширования отражения. Вот так:
public static class HashSetExtensions
{
private static class HashSetDelegateHolder<T>
{
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
public static MethodInfo InitializeMethod { get; } = typeof(HashSet<T>).GetMethod("Initialize", Flags);
}
public static void SetCapacity<T>(this HashSet<T> hs, int capacity)
{
HashSetDelegateHolder<T>.InitializeMethod.Invoke(hs, new object[] { capacity });
}
public static HashSet<T> GetHashSet<T>(int capacity)
{
var hashSet = new HashSet<T>();
hashSet.SetCapacity(capacity);
return hashSet;
}
}
Эта возможность была добавлена в 4.7.2:
HashSet<T>(Int32)
Initializes a new instance of the HashSet<T> class that is empty,
but has reserved space for capacity items and uses the default
equality comparer for the set type.
Единственный способ инициализировать HashSet с начальной емкостью - это создать его с помощью экземпляра класса, такого как List<T>
, что реализует ICollection<T>
, Это назовет граф на ICollection<T>
выделить достаточно места для хранения коллекции и добавить все элементы в HashSet без перераспределения.