Tak v Jave je to ukladani kratkych retezcu skutecne dost nevyhodne, jak to zvetsuje naroky cele JVM v beznych aplikacich? (jako asi je jedno, jestli je to o nejaky 1-2 MB vic pro normalni aplikaci , ale to asi budou jina cisla...)
A jak je na tom Python - vzpominam si, ze nedavno tam byly nejaky problemy prave u kratkych retezcu a GC.
Jestli dobře vidím, tak na 64bit je prázdný string 37 bytes (asi + terminating zero pro lepší interoperabilitu s C).
Takže ve srovnání Java na tom tak špatně není, aspoň co se týče hlavní struktury (*). Navíc je otázka, zda potřebuje Java ještě overhead pro memory blok, Python tam určitě počítá pouze velikost samotné struktury.
* - v constant pool má každý objekt vlastní pole nebo jej může sdílet víc stringů? Tím by se ta neefektivita zmenšila výrazně...
Ad sdílení stringů, to v Jave v ramci jedne tridy skutecne funguje, muzeme si to ostatne vyzkouset na tride, kde se vyskytuje stejny konstantni retezec (literal) na ruznem miste:
public class Test {
String s1 = "Hello world!";
String s2 = "Hello world!";
public void foo(String s) {
System.out.println("Hello world!");
System.out.println(s);
}
public void bar() {
foo("Hello world!");
}
}
javap -verbose Test vyplivne mj. i constant pool a z disasemblovaneho kodu je videt, ze se vsude pouziva odkaz na jediny zaznam #2:
Compiled from "Test.java"
public class Test extends java.lang.Object
SourceFile: "Test.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #9.#22; // java/lang/Object."[init]":()V
const #2 = String #23; // Hello world!
const #3 = Field #8.#24; // Test.s1:Ljava/lang/String;
const #4 = Field #8.#25; // Test.s2:Ljava/lang/String;
const #5 = Field #26.#27; // java/lang/System.out:Ljava/io/PrintStream;
const #6 = Method #28.#29; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #7 = Method #8.#30; // Test.foo:(Ljava/lang/String;)V
const #8 = class #31; // Test
const #9 = class #32; // java/lang/Object
const #10 = Asciz s1;
const #11 = Asciz Ljava/lang/String;;
const #12 = Asciz s2;
const #13 = Asciz [init];
const #14 = Asciz ()V;
const #15 = Asciz Code;
const #16 = Asciz LineNumberTable;
const #17 = Asciz foo;
const #18 = Asciz (Ljava/lang/String;)V;
const #19 = Asciz bar;
const #20 = Asciz SourceFile;
const #21 = Asciz Test.java;
const #22 = NameAndType #13:#14;// "[init]":()V
const #23 = Asciz Hello world!;
const #24 = NameAndType #10:#11;// s1:Ljava/lang/String;
const #25 = NameAndType #12:#11;// s2:Ljava/lang/String;
const #26 = class #33; // java/lang/System
const #27 = NameAndType #34:#35;// out:Ljava/io/PrintStream;
const #28 = class #36; // java/io/PrintStream
const #29 = NameAndType #37:#18;// println:(Ljava/lang/String;)V
const #30 = NameAndType #17:#18;// foo:(Ljava/lang/String;)V
const #31 = Asciz Test;
const #32 = Asciz java/lang/Object;
const #33 = Asciz java/lang/System;
const #34 = Asciz out;
const #35 = Asciz Ljava/io/PrintStream;;
const #36 = Asciz java/io/PrintStream;
const #37 = Asciz println;
{
java.lang.String s1;
java.lang.String s2;
public Test();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."[init]":()V
4: aload_0
5: ldc #2; //String Hello world!
7: putfield #3; //Field s1:Ljava/lang/String;
10: aload_0
11: ldc #2; //String Hello world!
13: putfield #4; //Field s2:Ljava/lang/String;
16: return
LineNumberTable:
line 1: 0
line 2: 4
line 3: 10
public void foo(java.lang.String);
Code:
Stack=2, Locals=2, Args_size=2
0: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #2; //String Hello world!
5: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 15
public void bar();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: ldc #2; //String Hello world!
3: invokevirtual #7; //Method foo:(Ljava/lang/String;)V
6: return
LineNumberTable:
line 9: 0
line 10: 6
}
Aha chapu. Ve zdrojacich jsem nasel pouze kontrolu, zda se pri nacitani stringu z constant poolu nejedna o duplicitu - potom se novy symbol nevytvari. K tomu jednomu poli pro vsechny string - podle vseho se kazdy retezec vytvari s nulovym offsetem (funkce Handle java_lang_String::basic_create(int length, bool tenured, TRAPS)
Sdílení char polí se děje určitě v metodě String.substring(), což může působit pěkný memory leak, pokud se udělá malý substring z nějakého obludně dlouhého.
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
Drtivá většina civilizovaného světa interně jede takhle, protože se to ukázalo jako nejpoužitelnější:
- 8-bitů na znak v defaultní kódové stránce OS.
- 16-bitů na znak v UCS-2
- občas se vyskytne 8-bitů na znak + 16-bitů informace o kódové stránce 1x pro celý string.
Jistý majoritní OS jede interně celý v UCS-2, ale některé funkce podporují UTF-16.