Почему я не могу предварительно выделить hashset<T>
?
Есть моменты, когда я могу добавить к нему много элементов, и я хочу исключить изменение размера.
Почему я не могу предварительно выделить hashset<T>
?
Есть моменты, когда я могу добавить к нему много элементов, и я хочу исключить изменение размера.
Ответ ниже был написан в 2011 году. Теперь он в .NET 4.7.2 и .NET Core 2.0; это будет в .NET Standard 2.1.
Там нет технической причины, почему это не должно быть возможно - 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);
}
}
обн. 04 jule
Этот код может быть также усилен с помощью кэширования отражений. Здесь мы идем:
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>
. Он вызовет Count на ICollection<T>
выделить достаточно места для хранения коллекции и добавления всех элементов в HashSet без перераспределения.