Store a color in Java - byte;byte;byte vs. byte[3] vs int
byte, java
Solution
Since four `byte`s fit in an `int`, you could use a single `int` for your colors (and still have extra room for a `byte` if you want to add, say, alpha, later). Sample little group of methods (untested, just so you get the idea):
public int toIntColor(byte r, byte g, byte b) {
int c = (int) r;
c = (c << 8) | g;
c = (c << 8) | b;
return c;
}
And to get the bytes back:
public byte red(int c) {
return c >> 16 & 0xFF;
}
public byte green(int c) {
return c >> 8 & 0xFF;
}
public byte blue(int c) {
return c & 0xFF;
}
Problem
I need to store a ton of RGB color objects. These are taking up between 8% & 12% of the total memory of my app for some common usages. I presently have it defined as follows: ``` class MyColor { byte red; byte green; byte blue; } ``` I assume that (most) JVMs actually use an int for each of those entries. The easiest alternative is: ``` class MyColor { byte [] color = new byte[3]; private static final int red = 0; private static final int green = 1; private static final int blue = 2; } ``` Will that put the entire array in a single int? Or is it an int[3] under the covers? If the first, this is great. If the second, then the best is: ``` class MyColor { int color; private static final int red_shift = 0; private static final int green_shift = 8; private static final int blue_shift = 16; } ``` Or is there a better approach? Update: I will also have a getRed(), setRed(int), ... as the accessors. I just listed the data components of the class to keep it smaller. And size is the critical issue here. The code doesn't spend a lot of time accessing these values so performance is not a big issue. Update 2: I went and ran this using SizeofUtil (referenced below - thank you). I did this using code as follows: ``` protected int create() { MyColor[] aa = new MyColor[100000]; for (int ind=0; ind<100000; ind++) aa[ind] = new MyColor2(); return 2; } }.averageBytes()); ``` And here's where it gets weird. First, if I don't do the for loop, so it only is creating the array (with all values null), then it reports 400016 bytes or 4 bytes/array element. I'm on a 64-bit system so I'm surprised this isn't 800000 (does Java have a 32-bit address space on a 64-bit O/S?). But then came the weird part. The total numbers with the for loop are: - 2800016.0 - 2600008.0 - 2800016.0 First surprise, the 2nd approach with byte[3] uses less memory! Is it possible that the JVM, seeing the byte[3] in the declaration, just allocates it inline? Second, the memory per object is (2,800,000 - 400,000) / 100,000 = 24. I'll buy that for the first approach where each byte is made a native 64-bit int. 3 * 8 bytes = 24 bytes. But for the third case where it's a single int? That makes no sense. Code here in case I missed something: ``` package net.windward; import java.util.Arrays; public class TestSize { public static void main(String[] args) { new TestSize().runIt(); } public void runIt() { System.out.println("The average memory used by MyColor1 is " + new SizeofUtil() { protected int create() { MyColor1[] aa = new MyColor1[100000]; for (int ind = 0; ind < 100000; ind++) aa[ind] = new MyColor1(); return 1; } }.averageBytes()); System.out.println("The average memory used by MyColor2 is " + new SizeofUtil() { protected int create() { MyColor2[] aa = new MyColor2[100000]; for (int ind = 0; ind < 100000; ind++) aa[ind] = new MyColor2(); return 2; } }.averageBytes()); System.out.println("The average memory used by MyColor3 is " + new SizeofUtil() { protected int create() { MyColor3[] aa = new MyColor3[100000]; for (int ind = 0; ind < 100000; ind++) aa[ind] = new MyColor3(); return 1; } }.averageBytes()); System.out.println("The average memory used by Integer[] is " + new SizeofUtil() { protected int create() { Integer[] aa = new Integer [100000]; for (int ind = 0; ind < 100000; ind++) aa[ind] = new Integer(ind); return 1; } }.averageBytes()); } public abstract class SizeofUtil { public double averageBytes() { int runs = runs(); double[] sizes = new double[runs]; int retries = runs / 2; final Runtime runtime = Runtime.getRuntime(); for (int i = 0; i < runs; i++) { Thread.yield(); long used1 = memoryUsed(runtime); int number = create(); long used2 = memoryUsed(runtime); double avgSize = (double) (used2 - used1) / number; // System.out.println(avgSize); if (avgSize < 0) { // GC was performed. i--; if (retries-- < 0) throw new RuntimeException("The eden space is not large enough to hold all the objects."); } else if (avgSize == 0) { throw new RuntimeException("Object is not large enough to register, try turning off the TLAB with -XX:-UseTLAB"); } else { sizes[i] = avgSize; } } Arrays.sort(sizes); return sizes[runs / 2]; } protected long memoryUsed(Runtime runtime) { return runtime.totalMemory() - runtime.freeMemory(); } protected int runs() { return 11; } protected abstract int create(); } class MyColor1 { byte red; byte green; byte blue; MyColor1() { red = green = blue = (byte) 255; } } class MyColor2 { byte[] color = new byte[3]; private static final int red = 0; private static final int green = 1; private static final int blue = 2; MyColor2() { color[0] = color[1] = color[2] = (byte) 255; } } class MyColor3 { int color; private static final int red_shift = 0; private static final int green_shift = 8; private static final int blue_shift = 16; MyColor3() { color = 0xffffff; } } } ```