Почему одна длинная строка занимает больше места, чем множество маленьких строк?



вот некоторый код для DFA, реализованный в виде массива строк:



public class StringArray
{
private static final String[] stringArray = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""
};
}


Я думал, что смогу сэкономить пространство, используя одну длинную строку вместо массива строк:



public class LongString
{
private static final String longString =
""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
}


как оказалось, я ошибался. Eclipse Kepler генерирует следующие файлы классов:



 8516 Nov 28 22:42 StringArray.class
10005 Nov 28 22:42 LongString.class


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

688   2  

2 ответов:

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

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

public class StringArray
{
    private static final String[] stringArray = {
        "AAA",
        "AAB",
        "AAA",
        "AAC",
        "AAA"
    };
}

public class LongString
{
    private static final String longString =
        "AAA"
        + "AAB"
        + "AAA"
        + "AAC"
        + "AAA"
    ;       
}
есть три важные вещи, чтобы понять при компиляции этого кода.
  1. постоянная конкатенация строк выполняется во время компиляции. In дело в том, что это частный случай упрощения времени компиляции константных выражений. Вы можете найти точные правила для того, что является константным выражением в спецификации языка Java.
  2. инициализаторы скобок массива являются синтаксическим сахаром. Код эквивалентен созданию массива и назначению элементов по одному. (Обратите внимание, что это специфично для байт-кода Java. Dalvik (т. е. Android) имеет специальные инструкции стенографии для массива инициализация)
  3. встроенные инициализаторы-это синтаксический сахар. Код эквивалентен ручной инициализации полей в методе статического инициализатора.

Edit: одна незначительная деталь заключается в том, что в частном случае статических конечных полей, инициализированных в константное выражение inline, поле инициализируется с помощью атрибута ConstantValue вместо статического инициализатора, и все использования встроены. Так что в случае LongString, #3 фактически приведет к другому байт-коду, но поскольку постоянные записи пула для строки одинаковы, размер файла, занимаемый строками, не изменится.

сложил их вместе, и приведенный выше код эквивалентен следующему.

public class StringArray {
    private static final String[] stringArray;

    static {
        String[] temp = new String[5];
        temp[0] = "AAA";        
        temp[1] = "AAB";        
        temp[2] = "AAA";        
        temp[3] = "AAC";        
        temp[4] = "AAA";    
        stringArray = temp;    
    } 
}

public class LongString {
    private static final String longString;

    static {
        longString = "AAAAABAAAAACAAA";
    } 
}

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

когда вы доступ к постоянной строке, байт-код содержит инструкцию load constant (ldc или ldc_w), который представляет собой однобайтовый код операции, за которым следует индекс в пул констант класса. Пул констант-это отдельный раздел файла класса, в котором хранится список констант. Очевидно, что компилятор будет хранить каждую константу только один раз.

таким образом, байт-код для StringArray выглядит примерно так (удаляя несколько деталей, которые здесь не актуальны). Обратите внимание, что есть только 3 уникальные строки, хранящиеся в постоянном пуле. (На самом деле есть намного больше постоянных записей пула, относящихся к другим частям classfile, но они не важны здесь).

.class super StringArray

.field static final private stringArray [Ljava/lang/String;

.const [1] = String 'AAA'
.const [2] = String 'AAB'
.const [3] = String 'AAC'

.method static <clinit> : ()V
    iconst_5
    anewarray java/lang/String
    astore_0
    aload_0
    iconst_0
    ldc [1]
    aastore
    aload_0
    iconst_1
    ldc [2]
    aastore
    aload_0
    iconst_2
    ldc [1]
    aastore
    aload_0
    iconst_3
    ldc [3]
    aastore
    aload_0
    iconst_4
    ldc [1]
    aastore
    aload_0
    putstatic StringArray stringArray [Ljava/lang/String;
    return
.end method

в то время как длинная строка выглядит примерно так

.class super LongString

.field static final private longString Ljava/lang/String;

.const [1] = String 'AAAAABAAAAACAAA'

.method static <clinit> : ()V
    ldc [1]
    putstatic LongString longString Ljava/lang/String;
    return
.end method

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

P. S. На двоичном уровне, постоянно кодируются с измененной кодировкой utf8. Результатом является то, что символы 1-127-это один байт каждый, но нулевые символы-это два байта. Таким образом, вы можете сэкономить место, компенсируя все 1.

у вас есть 18 дублировать строки в массиве (StringArray класс). Поскольку каждая запись массива является отдельным строковым литералом, Для уникальный строки. Однако в вашем втором примере (LongString class), все дубликаты сохраняются и объединяются во время компиляции.

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

public class LongString
{
    private static final String longString = 
        "a" + "a" + "a" + "a" + "a" + "a" + "a" + "a" + "b";
}

public class StringArray
{
    private static final String[] stringArray = {
        "a", "a", "a", "a", "a", "a", "a", "a",
        "b"
    };
}

если мы откроем файл класс LongString.class, вы можете увидеть всю строку:

LongString.class

но если вы посмотрите на файл класс StringArray.class, дубликаты были удалены:

enter image description here

пространство между a и b и 01 00 01, что означает CONSTANT_Utf8_info запись длиной 1.

в вашем случае, повторное использование строк спасет 2,304 персонажей.

Comments

    Ничего не найдено.