Что такое упаковка и распаковка и в чем заключаются компромиссы?

Ищу ясный, лаконичный и точный ответ.

В идеале как фактический ответ, хотя ссылки на хорошие объяснения приветствуются.

Ответов (8)

Решение

Значения в штучной упаковке - это структуры данных, которые представляют собой минимальную оболочку для примитивных типов *. Упакованные значения обычно хранятся как указатели на объекты в куче .

Таким образом, упакованные значения используют больше памяти и требуют как минимум двух поисков в памяти для доступа: один раз для получения указателя, а другой - для отслеживания этого указателя на примитив. Очевидно, это не то, что вам нужно во внутренних циклах. С другой стороны, значения в штучной упаковке обычно лучше работают с другими типами в системе. Поскольку они являются первоклассными структурами данных в языке, они имеют ожидаемые метаданные и структуру, которые имеют другие структуры данных.

В Java и Haskell общие коллекции не могут содержать неупакованные значения. Общие коллекции в .NET могут содержать неупакованные значения без каких-либо штрафов. Если универсальные шаблоны Java используются только для проверки типов во время компиляции, .NET будет генерировать определенные классы для каждого универсального типа, созданного во время выполнения .

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

* Для этого обсуждения примитивное значение - это любое значение, которое может быть сохранено в стеке вызовов , а не как указатель на значение в куче. Часто это просто типы машин (целые числа, числа с плавающей запятой и т. Д.), Структуры, а иногда и массивы статического размера. .NET-land называет их типами значений (в отличие от ссылочных типов). Люди, занимающиеся Java, называют их примитивными типами. Хаскеллионы просто называют их распакованными.

** В этом ответе я также сосредоточусь на Java, Haskell и C#, потому что это то, что я знаю. Как бы то ни было, Python, Ruby и Javascript имеют исключительно упакованные значения. Это также известно как подход «Все является объектом» ***.

*** Предостережение: достаточно продвинутый компилятор / JIT может в некоторых случаях фактически обнаружить, что значение, которое семантически упаковано при просмотре источника, может безопасно быть распакованным значением во время выполнения. По сути, благодаря блестящим разработчикам языка ваши боксы иногда бесплатны.

Boxing это процесс преобразования типа значения в ссылочный тип. А Unboxing это преобразование ссылочного типа в тип значения.

EX: int i = 123;
    object o = i;// Boxing
    int j = (int)o;// UnBoxing

Типы значений являются: int, char и structures, enumerations . Ссылочные типы являются: Classes, interfaces, arrays, strings и objects

Упаковка и распаковка упрощают обработку типов значений как объектов. Упаковка означает преобразование значения в экземпляр ссылочного типа на объект. Например, Int это класс и int тип данных. Преобразование int в Int - это пример бокса, тогда как преобразование Int в int распаковку. Эта концепция помогает в сборке мусора, распаковка, с другой стороны, преобразует тип объекта в тип значения.

int i=123;
object o=(object)i; //Boxing

o=123;
i=(int)o; //Unboxing.

В .Net:

Часто вы не можете полагаться на тип переменной, которую будет использовать функция, поэтому вам нужно использовать объектную переменную, которая начинается с наименьшего общего знаменателя - в .Net это так object .

Однако object это класс, и его содержимое хранится в виде ссылки.

List<int> notBoxed = new List<int> { 1, 2, 3 };
int i = notBoxed[1]; // this is the actual value

List<object> boxed = new List<object> { 1, 2, 3 };
int j = (int) boxed[1]; // this is an object that can be 'unboxed' to an int

Хотя оба они содержат одинаковую информацию, второй список больше и медленнее. Каждое значение во втором списке на самом деле является ссылкой на объект object, содержащий int .

Это называется упакованным в коробку, потому что int он обернут object . Когда его отбрасывают обратно, int он распаковывается - конвертируется обратно в его значение.

Для типов значений (т.е. всех structs ) это происходит медленно и потенциально требует гораздо больше места.

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

Еще одна проблема с типом значения в штучной упаковке заключается в том, что не очевидно, что вы имеете дело с блоком, а не со значением. Когда вы сравниваете два, structs вы сравниваете значения, но когда вы сравниваете два, classes тогда (по умолчанию) вы сравниваете ссылку - то есть это один и тот же экземпляр?

Это может сбивать с толку при работе с типами значений в штучной упаковке:

int a = 7;
int b = 7;

if(a == b) // Evaluates to true, because a and b have the same value

object c = (object) 7;
object d = (object) 7;

if(c == d) // Evaluates to false, because c and d are different instances

Легко обойти:

if(c.Equals(d)) // Evaluates to true because it calls the underlying int's equals

if(((int) c) == ((int) d)) // Evaluates to true once the values are cast

Однако при работе со значениями в штучной упаковке следует соблюдать осторожность.

из C# 3.0 в двух словах :

Бокс - это акт преобразования типа значения в ссылочный тип:

int x = 9; 
object o = x; // boxing the int

распаковка ... наоборот:

// unboxing o
object o = 9; 
int x = (int)o; 

Упаковка и распаковка - это процесс преобразования примитивного значения в объектно-ориентированный класс-оболочка (упаковка) или преобразования значения из объектно-ориентированного класса-оболочки обратно в примитивное значение (распаковка).

Например, в java вам может потребоваться преобразовать int значение в Integer (бокс), если вы хотите сохранить его в, Collection потому что примитивы не могут быть сохранены в Collection, только объекты. Но когда вы хотите получить его обратно из, Collection вы можете захотеть получить значение как, int а не как, Integer чтобы вы его распаковали.

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

В наши дни это чаще всего обсуждается в контексте функции «autoboxing / autounboxing» Java (и других языков). Вот объяснение автобокса, ориентированное на Java .

Общие коллекции .NET FCL:

List<T>
Dictionary<TKey, UValue>
SortedDictionary<TKey, UValue>
Stack<T>
Queue<T>
LinkedList<T>

все были разработаны для преодоления проблем производительности упаковки и распаковки в предыдущих реализациях коллекции.

Для получения дополнительной информации см. Главу 16, CLR через C# (2-е издание) .

Как и все остальное, автобоксинг может быть проблематичным, если его не использовать осторожно. Классический вариант - получить исключение NullPointerException и не удастся его отследить. Даже с отладчиком. Попробуй это:

public class TestAutoboxNPE
{
    public static void main(String[] args)
    {
        Integer i = null;

        // .. do some other stuff and forget to initialise i

        i = addOne(i);           // Whoa! NPE!
    }

    public static int addOne(int i)
    {
        return i + 1;
    }
}