简体   繁体   中英

Store a color in Java - byte;byte;byte vs. byte[3] vs int

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;
        }
    }
}

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;
}

Your first approach seems being better than the other two. It will take 16 bytes on 64-bit JVM and 12 bytes on 32-bit. Second is the most expensive, third is also 16 bytes.

You could also store you colors in three matrixes of byte[width][height] if you're storing images, this would save you a lot of bytes. The idea is to give up MyColor class which takes additional 13 bytes per instance and thus save ~80% memory.

Store each color as an RGB int in an array of ints:

int[] colors;

It's efficient and pretty convenient. You could also save another byte per color (25%) by using an array of bytes but that is less convenient and likely not worth it.

If you use a MyColor object of any kind, you have at least 8 bytes squandered on the object header and another 4 bytes on the reference to the object, before you can even start to store the color data itself.

I assume that (most) JVMs actually use an int for each of those entries.

No, they're real bytes, although it will take up space for 4 bytes, not 3, so it takes the same space as an int field.

byte[] color = new byte[3]; is the least efficient. The array is a separate object needing at least 8 extra bytes for the array object header, 4 bytes for its length field, and 4 bytes for the reference to it, before you count the actual array data at all.

class MyColor {
    byte red;
    byte green;
    byte blue;
}

Creates a new object each for each color, also has the memory overhead of being an object [1] .

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;
}

Not really ideal as you have two objects there, a byte[] and a MyColor. This doubles the overhead. There is no optimization that I know of that might transform byte[] into an int.

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;
}

This still has the same object overhead as the byte-based MyColor, and it also has the overhead of having to bit-shift constantly.

I would recommend something like:

class MyColor{
  byte getR(int col){...}
  byte getG(int col){...}
  byte getB(int col){...}
  int getCol(byte r, byte g, byte b){...}
}

Poor type safety, but it has the smallest overhead and can be stored in an array as https://stackoverflow.com/a/20443523/2299084 suggests.


A single byte does not take up an int's worth of space, as shown by a simple test program:

public class Test{
    public static byte[] bigarr = new byte[100000];

    public static void main(String[] args) {
        try{Thread.sleep(100000);}catch(Exception e){}
    }
}

and a profiler: This is using Java HotSpot(TM) 64-Bit Server VM (24.45-b08, mixed mode).

It all depends on what depth of color you want to store. Lets say you have a 24 bit color depth ie 8 bit Red 8 Bit Green and 8 Bit Blue then you can store all the three values in one integer only. Because java integer is 32 bit.

So Simply define:

int colorValue = 0; //RGB Composite color value with 24 bit depth.

Now you want to store all the color components in one integer. That requires some bit operation tricks. Let us say you store your integer in this format:

00000000BBBBBBBBGGGGGGGGRRRRRRRR (8 Bits each for R, G & B). Then you require the following functions:

int getRed(int colorVal)
{
    return colorVal & 127; //Gives the last 8 bits
}
int getGreen(int colorVal)
{
    return (colorVal >> 8) & 127; //Gives the middle 8 bits
}
int getBlue(int colorVal)
{
    return (colorVal >> 16) & 127; //Gives the first 8 bits
}
int getColorVal(int red, int green, int blue)
{
    return (blue << 16) | (green << 8) | red;
}

Now to store your ton of colors simply declare that many integers:

int width = <WIDTH>;
int height = <HEIGHT>;
int colorData[width * height];

Hope you can understand it now.

An essential question which none of the other answers have considered is whether you want a color to be an RGB triplet or identify something that holds an RGB triplet. Consider the scenario of a two text objects on a form; the form's background color is specified as red. One of the text object's background color is specified as being the same as the form; the other's is specified as red. Although both text object's backgrounds appear the same red, one object will have the form's background color and the other will have an independent color whose properties match. If the form's background color were to change to green, one of the text objects would continue to be the same color as the form, while the other would continue to be red.

If you use a mutable class type for your colors, then each variable of that type will identify an object that holds an RGB triplet. If multiple variables identify the same object, then using any one of those variables to change a property of the object will effectively change that property for all the variables. This can be good if the only variables that identify that object are the ones that should be changed (as with the text object above), but it can be very bad if some of the variables that identify the object should be independent from it (eg if the text object had identified the same color object as the background).

Using an immutable class type can make the semantics clearer (a reference to an immutable object may be regarded as simply encapsulating the contents thereof) but any time it's necessary to change the color of something it will be necessary to find or create a color object that encapsulates the correct color; that can be a lot more work than simply updating a stored number.

Unless you need to establish relationships between colors, I would suggest that using integers to represent colors is the way to go. It's relatively efficient, and the semantics are straightforward. Every different storage location of an integer type that's used to represent a color will be independent of every other, with no unwanted interactions between them.

Doorknob's idea to store byte s in an int is good, but his implementation didn't work for me. I ended up using Java's built in Color class . For example, to create a data structure of int s that represent colors, you can do:

Color c = new Color(55, 155, 255);
int ci = c.getRGB();

Later, if you'd like to decode the int :

Color cd = new Color(ci);
int R = cd.getRed(); // 55

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM