有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

以Java字节存储颜色;字节字节vs.字节[3]vs.整数

我需要存储大量RGB颜色对象。这些数字占8%至;我的应用程序总内存的12%,用于一些常见用途。我目前将其定义如下:

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

我假设(大多数)JVM实际上对每个条目都使用int。最简单的选择是:

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

这会把整个数组放在一个int中吗?或者它是封面下的int[3]?如果是第一个,这很好。如果是第二种,那么最好的是:

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

还是有更好的方法

更新:我还将有一个getRed(),setRed(int)。。。作为访问者。我只是列出了类的数据组件,以使其更小。而尺寸是这里的关键问题。代码不会花很多时间访问这些值,因此性能不是一个大问题

更新2:我使用SizeofUtil运行了这个程序(参考如下-谢谢)。我使用如下代码完成了此操作:

    protected int create() {
        MyColor[] aa = new MyColor[100000];
        for (int ind=0; ind<100000; ind++)
            aa[ind] = new MyColor2();
        return 2;
    }
}.averageBytes());

这就是它变得奇怪的地方。首先,如果我不执行for循环,因此它只创建数组(所有值都为null),那么它报告400016字节或4字节/数组元素。我在64位系统上,所以我很惊讶这不是800000(Java在64位O/S上有32位地址空间吗?)

但接着出现了奇怪的部分。带有for循环的总数为:

  • 2800016.0
  • 2600008.0
  • 2800016.0

首先令人惊讶的是,字节为[3]的第二种方法使用的内存更少!JVM看到声明中的字节[3]时,是否可以内联分配它

其次,每个对象的内存是(2800000-400000)/100000=24。对于第一种方法,每个字节都是原生的64位整数。3*8字节=24字节。但是对于第三种情况,它是一个int?这毫无意义

在这里输入代码,以防我遗漏了什么:

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

共 (6) 个答案

  1. # 1 楼答案

    因为四个byte适合一个int,所以你可以使用一个int作为你的颜色(如果你想添加,比如说,alpha,以后还可以为一个byte留出额外的空间)。示例一小组方法(未经测试,只是为了让您了解情况):

    public int toIntColor(byte r, byte g, byte b) {
        int c = (int) r;
        c = (c << 8) | g;
        c = (c << 8) | b;
        return c;
    }
    

    为了找回字节:

    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;
    }
    
  2. # 2 楼答案

    这完全取决于你想储存的颜色深度。假设你有一个24位的颜色深度,即8位红色、8位绿色和8位蓝色,那么你只能在一个整数中存储所有三个值。因为java整数是32位的

    所以简单地定义一下:

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

    现在要将所有颜色组件存储在一个整数中。这需要一些操作技巧。假设您以以下格式存储整数:

    00000000 BBBBGGGGGGGGRRRRRR(R、G和B各8位)。然后需要以下功能:

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

    现在要存储大量颜色,只需声明许多整数:

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

    希望你现在能理解

  3. # 3 楼答案

    你的第一种方法似乎比另外两种更好。在64位JVM上需要16个字节,在32位JVM上需要12个字节。第二个是最昂贵的,第三个也是16字节

    你也可以把颜色存储在三个byte[width][height]矩阵中。如果你在存储图像,这将节省大量字节。其想法是放弃MyColor类,该类每个实例需要额外13个字节,从而节省约80%的内存

  4. # 4 楼答案

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

    为每种颜色创建一个新对象,也有作为对象[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;
    }
    

    不太理想,因为有两个对象,一个字节[]和一个颜色。这使开销增加了一倍。据我所知,没有任何优化可以将字节[]转换为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;
    }
    

    这仍然与基于字节的MyColor具有相同的对象开销,而且还需要不断地进行位移位

    我想推荐如下:

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

    类型安全性差,但它的开销最小,并且可以像https://stackoverflow.com/a/20443523/2299084建议的那样存储在数组中


    正如一个简单的测试程序所示,单个字节不会占用int值的空间:

    public class Test{
        public static byte[] bigarr = new byte[100000];
    
        public static void main(String[] args) {
            try{Thread.sleep(100000);}catch(Exception e){}
        }
    }
    

    和一个分析器:这是使用Java热点(TM)64位服务器VM(24.45-b08,混合模式)

  5. # 5 楼答案

    将每种颜色存储为整数数组中的RGB整数:

    int[] colors;
    

    它既高效又方便。通过使用字节数组,还可以为每种颜色节省另一个字节(25%),但这不太方便,而且可能不值得

    如果使用任何类型的MyColor对象,在开始存储颜色数据本身之前,至少有8个字节浪费在对象标题上,另外4个字节浪费在对象引用上

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

    不,它们是实字节,虽然它会占用4字节的空间,而不是3字节,所以它占用的空间与int字段相同

    byte[] color = new byte[3];的效率最低。数组是一个单独的对象,在计算实际数组数据之前,需要至少8个额外字节作为数组对象头,4个字节作为其length字段,4个字节作为对它的引用

  6. # 6 楼答案

    其他答案都没有考虑过的一个基本问题是,你想让一种颜色成为RGB三重态还是识别某个包含RGB三重态的东西。考虑窗体上的两个文本对象的场景;表单的背景色指定为红色。文本对象的背景色之一被指定为与表单相同;另一个指定为红色。虽然两个文本对象的背景显示相同的红色,但一个对象将具有窗体的背景色,而另一个对象将具有属性匹配的独立颜色。如果窗体的背景色更改为绿色,则其中一个文本对象将继续与窗体相同的颜色,而另一个将继续为红色

    如果对颜色使用可变类类型,则该类型的每个变量将标识一个包含RGB三元组的对象。如果多个变量标识同一个对象,那么使用这些变量中的任何一个来更改对象的属性将有效地更改所有变量的属性。如果标识该对象的唯一变量是应该更改的变量(与上面的文本对象一样),这可能是好的,但如果标识该对象的一些变量应该独立于它(例如,如果文本对象标识了与背景相同的颜色对象),这可能是非常糟糕的

    使用不可变类类型可以使语义更清晰(对不可变对象的引用可能被认为只是封装了其内容),但任何时候需要更改某个对象的颜色时,都需要找到或创建一个封装了正确颜色的颜色对象;这比简单地更新一个存储的数字要复杂得多

    除非需要建立颜色之间的关系,否则我建议使用整数来表示颜色。它相对高效,语义简单明了。用于表示颜色的整数类型的每个不同存储位置将相互独立,它们之间没有不必要的交互