Подготовка к массиву С#

Учитывая заполненный byte[] values в С#, я хочу добавить значение (byte)0x00 в массив. Я предполагаю, что для этого потребуется создать новый массив и добавить содержимое старого массива. Скорость - важный аспект моего приложения. Каков наилучший способ сделать это?

- EDIT -

byte[] используется для хранения параметров DSA (Digital Signature Algorithm). Операция должна выполняться только один раз для каждого массива, но скорость важна, потому что я потенциально выполняю эту операцию для разных byte[] s.

Ответы

Ответ 1

Если вы собираетесь выполнять эту операцию только один раз, выбор невозможен. Код, предоставленный Monroe answer, должен делать только хорошо.

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values

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

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

Двухсторонняя очередь (известная как deque) была бы фантастической структурой данных для вас. Вы можете эффективно добавлять в начало или конец и эффективно получать доступ к данным в любой точке структуры (но вы не можете эффективно вставлять где-то, кроме начала или конца). Основная проблема здесь заключается в том, что .NET не обеспечивает реализацию deque. Вам нужно будет найти стороннюю библиотеку с реализацией.

Вы также можете сэкономить много при копировании, отслеживая "данные, которые мне нужно добавить" (используя список/очередь/и т.д.), а затем ожидая фактического добавления данных как можно дольше, чтобы вы максимально свести к минимуму создание новых массивов, а также ограничить количество копий существующих элементов.

Можно также рассмотреть вопрос о том, можно ли настроить структуру так, чтобы вы добавляли ее до конца, а не в начало (даже если вы знаете, что вам нужно будет отменить ее позже). Если вы добавляете много за короткий промежуток времени, возможно, стоит хранить данные в List (который может эффективно добавить в конец) и добавить в конец. В зависимости от ваших потребностей, возможно, даже стоит сделать класс, который является оберткой для списка, и это скрывает тот факт, что оно отменено. Вы можете сделать указатель, который отображает i в Count-i и т.д., Чтобы он отображался снаружи, как будто ваши данные хранятся в обычном режиме, даже если внутренний List фактически сохраняет данные назад.

Ответ 2

Как вы догадались, самый быстрый способ сделать это - создать новый массив длиной + 1 и скопировать все старые значения.

Если вы собираетесь делать это много раз, я предлагаю использовать List<byte> вместо byte[], поскольку стоимость перераспределения и копирования при росте базового хранилища амортизируется более эффективно; в обычном случае базовый вектор в List выращивается в два раза каждый раз, когда добавляется или добавляется элемент List, который превышает его текущую емкость.

...

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values

Ответ 3

Хорошо, ребята, давайте взглянем на вопрос о производительности в этом вопросе. Это не ответ, а просто микрозапуск, чтобы узнать, какой вариант более эффективен.

Итак, зададим сценарий:

  • Байт-массив из 1 000 000 элементов, случайно заполненный
  • Нам нужно добавить элемент 0x00

У нас есть 3 варианта:

  • Вручное создание и заполнение нового массива
  • Вручную создаем новый массив и используя Array.Copy(@Monroe)
  • Создание списка, загрузка массива, вставка элемента и преобразование списка в массив

Здесь код:

    byte[] byteArray = new byte[1000000];

    for (int i = 0; i < byteArray.Length; i++)
    {
        byteArray[i] = Convert.ToByte(DateTime.Now.Second);
    }

    Stopwatch stopWatch = new Stopwatch();

    //#1 Manually creating and populating a new array;

    stopWatch.Start();

    byte[] extendedByteArray1 = new byte[byteArray.Length + 1];

    extendedByteArray1[0] = 0x00;

    for (int i = 0; i < byteArray.Length; i++)
    {
        extendedByteArray1[i + 1] = byteArray[i];
    }

    stopWatch.Stop();
    Console.WriteLine(string.Format("#1: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    //#2 Using a new array and Array.Copy

    stopWatch.Start();

    byte[] extendedByteArray2 = new byte[byteArray.Length + 1];
    extendedByteArray2[0] = 0x00;                                
    Array.Copy(byteArray, 0, extendedByteArray2, 1, byteArray.Length);

    stopWatch.Stop();
    Console.WriteLine(string.Format("#2: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    //#3 Using a List

    stopWatch.Start();

    List<byte> byteList = new List<byte>();
    byteList.AddRange(byteArray);
    byteList.Insert(0, 0x00);

    byte[] extendedByteArray3 = byteList.ToArray();

    stopWatch.Stop();
    Console.WriteLine(string.Format("#3: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    Console.ReadLine();

И результаты:

#1: 9 ms
#2: 1 ms
#3: 6 ms

Я запускаю его несколько раз, и у меня разные номера, но пропорция всегда одна: # 2 всегда самый эффективный выбор.

Мой вывод: массивы более эффективны, чем списки (хотя они обеспечивают меньшую функциональность), и как-то Array.Copy действительно optmized (хотелось бы это понять, хотя).

Любая обратная связь будет оценена.

С уважением.

PS: это не бойцовый пост, мы находимся на Q & A сайте, чтобы учиться и учить. И узнать.

Ответ 4

Когда мне нужно часто добавлять данные, но также хочу, чтобы O (1) произвольный доступ к отдельным элементам, я буду использовать массив, который превышает выделенную часть заполнения для быстрого добавления новых значений. Это означает, что вам нужно сохранить фактическую длину содержимого в другой переменной, так как array.length укажет длину + дополнение. Новое значение добавляется при использовании одного слота прокладки, не требуется выделение и копирование, пока вы не закончите заполнение. Фактически, распределение амортизируется в течение нескольких операций добавления. Есть компромиссы в области скоростного пространства, как если бы у вас было много таких структур данных, вы могли бы иметь достаточное количество дополнений, используемых в любой момент в программе.

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

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

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

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

Есть много других структур данных, которые вы могли бы использовать в зависимости от применимости к вашему домену. Веревки или буфер разрыва; см. Какая лучшая структура данных подходит для реализации редактора как блокнот?; Trie делать некоторые полезные вещи, тоже.

Ответ 5

Лучший выбор зависит от того, что вы собираетесь делать с этой коллекцией позже в очереди. Если это единственное редактируемое изменение длины, которое когда-либо будет сделано, то лучше всего создать новый массив с одним дополнительным слотом и использовать Array.Copy(), чтобы сделать все остальное. Нет необходимости инициализировать первое значение, так как новые массивы С# всегда обнуляются:

byte[] PrependWithZero(byte[] input)
{
    var newArray = new byte[input.Length + 1];
    Array.Copy(input, 0, newArray, 1, input.Length);
    return newArray;
}

Если будут другие изменения, изменяющие длину, которые могут произойти, наиболее эффективным вариантом может быть использование List<byte> все время, пока дополнения не всегда к началу. (Если в этом случае даже связанный список может не быть вариантом, который вы можете уволить из рук.):

var list = new List<byte>(input);
list.Insert(0, 0);

Ответ 6

Я знаю, что это ОЧЕНЬ старый пост, но мне действительно нравится использовать лямбду. Конечно, мой код НЕ МОЖЕТ быть самым эффективным способом, но его можно прочитать и в одной строке. Я использую комбинацию .Concat и ArraySegment.

 string[] originalStringArray = new string[] { "1", "2", "3", "5", "6" };
 int firstElementZero = 0;
 int insertAtPositionZeroBased = 3;
 string stringToPrepend = "0";
 string stringToInsert = "FOUR"; // Deliberate !!!

 originalStringArray = new string[] { stringToPrepend }
            .Concat(originalStringArray).ToArray();

 insertAtPositionZeroBased += 1; // BECAUSE we prepended !!
 originalStringArray = new ArraySegment<string>(originalStringArray, firstElementZero, insertAtPositionZeroBased)       
                .Concat(new string[] { stringToInsert })
                .Concat(new ArraySegment<string>(originalStringArray, insertAtPositionZeroBased, originalStringArray.Length - insertAtPositionZeroBased)).ToArray();

Ответ 7

Я знаю, что это более 4-летняя принятая запись, но для тех, кто может быть релевантным Buffer.BlockCopy, будет быстрее.