Как и когда отказаться от использования массивов в С#?

Мне всегда говорили, что добавление элемента в массив происходит следующим образом:

Пустая копия массива + 1элемент и затем данные из исходный массив копируется в него тогда новые данные для нового элемента затем загружается

Если это правда, то использование массива внутри сценария, требующего большой активности элемента, противопоказано из-за использования памяти и процессора, правильно?

Если это так, не следует ли пытаться избежать использования массива как можно больше, когда вы будете добавлять много элементов? Если вы вместо этого используете iStringMap? Если да, то что произойдет, если вам нужно больше двух измерений и нужно добавить много добавлений элементов. Вы просто принимаете удар производительности или есть что-то еще, что нужно использовать?

Ответы

Ответ 1

Посмотрите на общий List<T> в качестве замены массивов. Они поддерживают большинство тех же вещей, что и массивы, включая выделение начального размера хранилища, если хотите.

Ответ 2

Это действительно зависит от того, что вы подразумеваете под "add."

Если вы имеете в виду:

T[] array;
int i;
T value;
...
if (i >= 0 && i <= array.Length)
    array[i] = value;

Тогда нет, это не создает новый массив, а на самом деле является самым быстрым способом изменить любой тип IList в .NET.

Если, однако, вы используете что-то вроде ArrayList, List, Collection и т.д., то вызов метода "Добавить" может создать новый массив - но они сообразительны, они не просто изменяют размер на 1 элемент, они растут геометрически, поэтому, если вы добавляете множество значений только раз в то время, им придется выделять новый массив. Даже тогда вы можете использовать свойство "Capacity", чтобы заставить его расти раньше, если вы знаете, сколько элементов вы добавляете (list.Capacity += numberOfAddedElements)

Ответ 3

В общем, я предпочитаю избегать использования массива. Просто используйте List <T> . Он использует внутренний массив динамического размера и достаточно быстро подходит для большинства целей. Если вы используете многомерные массивы, используйте List < List < List < T → > , если это необходимо. Это не намного хуже с точки зрения памяти, и гораздо проще добавлять элементы в.

Если вы используете 0.1% использования, требующего экстремальной скорости, убедитесь, что ваш список доступа действительно является проблемой, прежде чем пытаться его оптимизировать.

Ответ 4

Если вы собираетесь добавлять/удалять элементы, просто используйте List. Если он многомерный, вы всегда можете использовать List < List < int → или что-то.

С другой стороны, списки менее эффективны, чем массивы, если то, что вы в основном делаете, проходит через список, потому что все массивы находятся в одном месте в вашем кэше процессора, где объекты в списке разбросаны повсюду.

Если вы хотите использовать массив для эффективного чтения, но вы будете часто добавлять элементы, у вас есть два основных варианта:

1) Создайте его как список (или список списков), а затем используйте ToArray(), чтобы превратить его в эффективную структуру массива.

2) Выделите массив размером больше, чем вам нужно, а затем поместите объекты в предварительно выделенные ячейки. Если вам требуется еще больше элементов, чем вы предварительно выделили, вы можете просто перераспределить массив, когда он заполняется, удваивая размер каждый раз. Это дает O (log n) производительность изменения размера вместо O (n), как это было бы с массивом reallocate-once-per-add. Обратите внимание, что это почти так, как работает StringBuilder, что дает вам более быстрый способ непрерывного добавления к строке.

Ответ 5

Когда отказаться от использования массивов

  • В первую очередь, когда семантика массивов dont соответствует вашим намерениям - нужна динамически растущая коллекция? Набор, который не позволяет дублировать? Коллекция, которая должна оставаться неизменной? Избегайте массивов во всех этих случаях. Это 99% случаев. Просто указывая очевидную основную точку.

  • Во-вторых, когда вы не кодирование для абсолютной критичности производительности - это примерно 95% случаев. Массивы работают лучше, чем, особенно в итерации. Это почти всегда не имеет значения.

  • Когда вы не вынуждены аргументом с ключевым словом params - я просто пожелал params принять любой IEnumerable<T> или даже лучше сам язык для обозначения последовательности (а не тип структуры).

  • Когда вы не записываете устаревший код или работаете с interop

Короче говоря, очень редко, что вам действительно нужен массив. Я добавлю, почему можно его избежать?

  • Самая большая причина избежать массивов imo является концептуальной. Массивы ближе к реализации и дальше от абстракции. Массивы передают больше, как это делается, чем то, что делается против духа языков высокого уровня. Это неудивительно, учитывая, что массивы ближе к металлу, они прямо из специального типа (хотя внутренний массив - это класс). Не быть педагогическими, но массивы действительно переводить на семантический смысл очень редко требуется. Наиболее полезной и частой семантикой являются коллекции с любыми записями, наборами с отдельными элементами, картами ключевых значений и т.д. С любой комбинацией добавляемых, неизменно неизменяемых, уважающих порядок вариантов. Подумайте об этом, вам может понадобиться сборка или сборка только для чтения с предопределенными элементами без каких-либо дополнительных изменений, но как часто ваша логика выглядит так: "Я хочу динамически добавленную коллекцию, но только фиксированное число из них, и они также должны быть модифицируемыми"? Очень редко я бы сказал.

  • Массив был спроектирован в эпоху предипиков, и он имитирует роность с большим количеством хакеров времени выполнения, и он будет показывать свои странности здесь и там. Некоторые из уловов, которые я нашел:

    • Сломанная ковариация.

      string[] strings = ...
      object[] objects = strings;
      objects[0] = 1; //compiles, but gives a runtime exception.
      
    • Массивы могут дать вам ссылку на структуру!. Это не похоже нигде. Образец:

      struct Value { public int mutable; }
      
      var array = new[] { new Value() };  
      array[0].mutable = 1; //<-- compiles !
      //a List<Value>[0].mutable = 1; doesnt compile since editing a copy makes no sense
      print array[0].mutable // 1, expected or unexpected? confusing surely
      
    • Используемые методы, такие как ICollection<T>.Contains, могут быть разными для структур и классов. Это не имеет большого значения, но если вы забыли переопределить не общий Equals правильно для ссылочных типов, ожидающих, что общая коллекция будет искать общий Equals, вы получите неверные результаты.

      public class Class : IEquatable<Class>
      {
          public bool Equals(Class other)
          {
              Console.WriteLine("generic");
              return true;
          }
          public override bool Equals(object obj)
          {
              Console.WriteLine("non generic");
              return true;
          } 
      }
      
      public struct Struct : IEquatable<Struct>
      {
          public bool Equals(Struct other)
          {
              Console.WriteLine("generic");
              return true;
          }
          public override bool Equals(object obj)
          {
              Console.WriteLine("non generic");
              return true;
          } 
      }
      
      class[].Contains(test); //prints "non generic"
      struct[].Contains(test); //prints "generic"
      
    • Свойство Length и [] indexer на T[] кажутся регулярными свойствами, к которым вы можете получить доступ через отражение (которое должно включать в себя некоторую магию), но когда дело доходит до деревьев выражений, вы должны плевать из того же самого кода, который делает компилятор. Для этого существуют методы ArrayLength и ArrayIndex. Один такой вопрос здесь. Другой пример:

      Expression<Func<string>> e = () => new[] { "a" }[0];
      //e.Body.NodeType == ExpressionType.ArrayIndex
      
      Expression<Func<string>> e = () => new List<string>() { "a" }[0];
      //e.Body.NodeType == ExpressionType.Call;
      

Как отказаться от использования массивов

Наиболее часто используемым заменителем является List<T>, который имеет более чистый API. Но это динамически растущая структура, которая означает, что вы можете добавить к List<T> в конце или вставить где угодно до любой емкости. Невозможно заменить точное поведение массива, но люди в основном используют массивы как сборники, только если вы не можете добавить что-либо к концу. Подстановка - ReadOnlyCollection<T>. Я использую этот метод расширения:

public ReadOnlyCollection<T> ToReadOnlyCollection<T>(IEnumerable<T> source)
{
    return source.ToList().AsReadOnly();
}

Ответ 6

При изменении размера массива должен быть назначен новый массив, а содержимое скопировано. Если вы только изменяете содержимое массива, это просто назначение памяти.

Итак, вы не должны использовать массивы, когда вы не знаете размер массива, или размер может измениться. Однако, если у вас есть массив с фиксированной длиной, они являются простым способом извлечения элементов по индексу.

Ответ 7

ArrayList и List увеличивают массив более чем на один, когда это необходимо (я думаю, что это удваивает размер, но я не проверял источник). Они, как правило, лучший выбор при создании массива с динамическим размером.

Когда ваши тесты показывают, что изменение размера массива серьезно замедляет ваше приложение (помните, что преждевременная оптимизация является корнем всего зла), вы можете оценить, как писать собственный класс массива с измененным поведением изменения размера.

Ответ 8

Как правило, если вы должны иметь индексный результат поиска BEST, лучше всего сначала создать список, а затем превратить его в массив, тем самым сначала заплатив небольшой штраф, но избегая его позже. Если проблема заключается в том, что вы будете постоянно добавлять новые данные и удалять старые данные, тогда вы можете использовать ArrayList или List для удобства, но имейте в виду, что они являются только особыми массивами. Когда они "растут", они выделяют совершенно новый массив и копируют в него все, что очень медленно.

ArrayList - это просто массив, который растет, когда это необходимо. Добавить амортизируется O (1), просто будьте осторожны, чтобы убедиться, что изменение размера не произойдет в плохое время. Вставка - это O (n) все элементы справа должны быть перемещены. Удалить O (n) все элементы справа должны быть перемещены.

Также важно помнить, что List не является связанным списком. Это просто типизированный ArrayList. В документации отмечается, что в большинстве случаев он работает лучше, но не говорит, почему.

Лучше всего выбрать структуру данных, соответствующую вашей проблеме. Это зависит от количества вещей, и поэтому вы можете просмотреть nofollow noreferrer → System.Collections.Generic.

В этом конкретном случае я бы сказал, что если вы можете придумать хорошее ключевое значение Словарь, это ваш лучший выбор. Он имеет вставку и удаление, которая приближается к O (1). Однако даже с помощью Словаря вы должны быть осторожны, чтобы не изменять его внутренний массив (операция O (n)). Лучше всего дать им много места, указав более крупную, а затем ожидаемую в использовании начальную емкость в конструкторе.

-Rick

Ответ 9

Лучшее, что вы можете сделать, это выделить столько памяти, сколько вам нужно, если это возможно. Это предотвратит .NET от необходимости делать дополнительные вызовы для получения памяти в куче. Если это не так, тогда имеет смысл выделить в кусках пять или любой номер, имеющий смысл для вашего приложения.

Это правило, которое вы можете применить к чему-либо действительно.

Ответ 10

Стандартный массив должен быть определен с длиной, которая резервирует всю память, которая ему нужна в смежном блоке. Добавление элемента в массив помещает его в блок уже зарезервированной памяти.

Ответ 11

Массивы отлично подходят для нескольких записей, и многие из них читаются, особенно те, которые имеют итеративный характер - для чего-либо еще используют одну из многих других структур данных.

Ответ 12

Вы правы, массив отлично подходит для поиска. Однако изменения размера массива являются дорогостоящими.

Вы должны использовать контейнер, который поддерживает инкрементные настройки размера в сценарии, в котором вы изменяете размер массива. Вы можете использовать ArrayList, который позволяет установить начальный размер, и вы можете постоянно проверять размер по сравнению с емкостью, а затем увеличивать емкость на большой кусок, чтобы ограничить количество изменений.

Или вы можете просто использовать связанный список. Затем, однако, поиски медленны...

Ответ 14

Если я думаю, что собираюсь добавлять элементы в коллекцию много за всю свою жизнь, я буду использовать список. Если я точно знаю, какой будет размер коллекции, когда будет объявлен, тогда я буду использовать массив.

В другой раз, когда я обычно использую массив над списком, мне нужно вернуть коллекцию как свойство объекта - я не хочу, чтобы вызывающие объекты добавляли элементы этой коллекции через методы "Добавить список", но вместо этого хотели, чтобы они добавляли элементы к коллекции через мой интерфейс объекта. В этом случае я возьму внутренний список и вызову ToArray и верну его массив.

Ответ 15

Если вы собираетесь делать много добавления, и вы не будете делать произвольный доступ (например, myArray[i]). Вы можете использовать связанный список (LinkedList<T>), потому что ему никогда не придется "расти", как реализация List<T>. Имейте в виду, что вы можете реально получать доступ к элементам в реализации LinkedList<T>, используя интерфейс IEnumerable<T>.