Многие знакомы с книгой Никлауса Вирта «Алгоритмы и структуры данных». Раньше она называлась «Алгоритмы + структуры данных = программы». И это действительно все, что нам нужно, при этом все-таки данные первичны, так что давайте как следует изучим наши «кирпичики». Ведь специалист должен знать свой инструмент. Сегодня обратимся к базовым типам. Вы скажите «а что тут такого сложного? я не хочу тратить свое время на чтение такой фигни». Хорошо. Тогда ответьте мне на один вопрос: сколько в памяти нужно выделить для переменной типа boolean? Ответ не так прост как может показаться С или С++ программисту, но мы выясним это опытным путем =)

Перед началом нашего удивительного исследования мира двузначной логики и его размеров пройдемся по всем базовым типам:

Примитивный тип Зарезервированное слово Размер Мин. значение Макс. значение Атомарный
Boolean boolean ? +
Character char 8 бит = 1 байт Unicode 0 Unicode 2^16-1 +
Byte integer byte 8 бит = 1 байт -128 127 +
Short integer short 16 бит = 2 байта -2^15 2^15 — 1 +
Integer int 32 бита = 4 байта -2^31 2^31 — 1 +
Long integer long 64 бита = 8 байт -2^63 2^63 — 1
Floating-point float 32 бита = 4 байта IEEE 754 IEEE 754
Double precision floating point double 64 бита = 8 байт IEEE 754 IEEE 754

Атомарность типа говорит о том, что виртуальная машина выполняет операции над ним «в один заход». Например типы с плавающей запятой не атомарны, так как операции над мантиссой и экспонентой выполняются последовательно. Поэтому при использовании нескольких потоков в приложении, если разные потоки получат доступ к этой переменной, то последствия непредсказуемы.

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

Таким образом, если переменная будет считываться/модифицироваться более чем в одном потоке — объявляете ее volatile.

Внимание: в отличии от примитивных типов других языков, примитивные типы Java имеют фиксированный размер, что, отчасти, вызвано принципом «Write once, run anywhere».

Так что же у нас с булевским типом? Обратимся к документации. Тут можно найти следующую информацию:

boolean: The boolean data type has only two possible values: true and false. Use this data type for simple flags that track true/false conditions. This data type represents one bit of information, but its «size» isn’t something that’s precisely defined.

Говорят «не определен точно». Проведем эксперимент. В этом нам поможет нижеследующий код:


public class Runner {
static class BooleanContainer {
boolean a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
boolean b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
boolean c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
boolean d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
boolean e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
}
static class IntegerContainer {
int a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
int b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
int c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
int d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
int e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
}
private static long getMemory() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}

private static final int SIZE = 100000;

private static final int NUM_OF_PRIMITIVES = 80;
public static void main(String[] args) throws Exception {


BooleanContainer[] first = new BooleanContainer[SIZE];
IntegerContainer[] second = new IntegerContainer[SIZE];

System.gc();
long startMem = getMemory();

for (int i = 0; i < SIZE; i++) {
first[i] = new BooleanContainer();
}

System.gc();
long endMem = getMemory();

System.out.println("Size for LotsOfBooleans: " + (endMem - startMem));
System.out.println("Average size: "
+ ((endMem - startMem) / ((double) SIZE*NUM_OF_PRIMITIVES)));

System.gc();
startMem = getMemory();
for (int i = 0; i < SIZE; i++) {
second[i] = new IntegerContainer();
}
System.gc();
endMem = getMemory();

System.out.println("Size for LotsOfInts: " + (endMem - startMem));
System.out.println("Average size: "
+ ((endMem - startMem) / ((double) SIZE*NUM_OF_PRIMITIVES)));

/*
* Чтобы избежать сбора наших объектов слишком рано,
* нужно просимулировать действия с этими объектами.
* Ради интереса попробуйте убрать этот код и запустить
* программу.
*/
for (int i = 0; i < SIZE; i++) {
if (first[i].a0) { second[i].a0 += 0;}
}

}
}

Теперь посмотрим, что выводится в консоль:


Size for BooleanContainer: 9244424
Average size: 1.155553
Size for IntegerContainer: 33600000
Average size: 4.2

Замечу, что это результаты на моей машине, то есть JVM для Mac OS X, для других они вероятно будут немного иными.

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

Напоследок рассмотрим инициализацию массивов примитивных типов и два полезных метода toString() и deepToString() класса Arrays.


int[] vector = {1,2,3};
int[] vector2 = new int[3];
System.out.println(Arrays.toString(vector));

int[][] matrix = {{1,2},{3,4}};
int[][] matrix2 = new int[2][2];
System.out.println(Arrays.deepToString(matrix));

int[][] jaggedArray = {{1},{1,2,3},{1,2}};
int[][] jaggedArray2 = new int[3][];
jaggedArray2[0] = new int[1];
jaggedArray2[1] = new int[3];
jaggedArray2[2] = new int[2];
System.out.println(Arrays.deepToString(jaggedArray));

Вывод в консоль будет таким:


[1, 2, 3]
[[1, 2], [3, 4]]
[[1], [1, 2, 3], [1, 2]]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *