【问题标题】:Store a color in Java - byte;byte;byte vs. byte[3] vs int在 Java 中存储颜色 - byte;byte;byte vs. byte[3] vs int
【发布时间】:2013-12-07 16:12:14
【问题描述】:

我需要存储大量 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 位操作系统上是否有 32 位地址空间?)。

但是奇怪的部分来了。 for 循环的总数是:

  • 2800016.0
  • 2600008.0
  • 2800016.0

第一个惊喜,使用 byte[3] 的第二种方法使用更少的内存! JVM是否有可能看到声明中的字节[3],只是内联分配它?

其次,每个对象的内存是 (2,800,000 - 400,000) / 100,000 = 24。我将购买它作为第一种方法,其中每个字节都被制作为本机 64 位 int。 3 * 8 字节 = 24 字节。但是对于第三种情况,它是一个单一的整数?这没有任何意义。

代码在这里,以防我错过了什么:

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

【问题讨论】:

    标签: java byte


    【解决方案1】:

    由于四个bytes 适合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;
    }
    

    【讨论】:

    • 如果我使用方法#3 是的。但是方法#1是否将每个字节扩展为一个int?如果它将所有 3 个字节放在一个 int 中,那么使用方法 #3 将无法取胜。
    【解决方案2】:

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

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

    【讨论】:

    • 我认为你的数字是错误的 - 64 位 JVM 上的 16 个字节。第三种方法将占用 4 个字节(在 64 位 JVM 上可能是 8 个字节),而不是 16 个 - 我相信???
    • 我认为我的数字是正确的 - 我刚刚检查过了。第三种方法与Integer 几乎相同,在 64 (32) 位 JVM 上需要 16 (8)。
    • @DavidThielen 你可以用这个简单的方法自己检查:code.google.com/p/core-java-performance-examples/source/browse/…
    • 我刚刚尝试使用 SizeofUtil - 结果非常奇怪。我是不是搞错了什么?
    • 奇怪,你跑MemoryUsageExamplesTest了吗?您是否在 JVM 参数中添加了 -XX:-UseTLAB
    【解决方案3】:

    将每种颜色作为一个 RGB int 存储在一个整数数组中:

    int[] colors;
    

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

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

    我假设(大多数)JVM 实际上对这些条目中的每一个都使用 int。

    不,它们是真正的字节,虽然它会占用 4 个字节而不是 3 个字节的空间,因此它占用的空间与 int 字段相同。

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

    【讨论】:

      【解决方案4】:

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

      所以简单定义:

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

      现在您想将所有颜色分量存储在一个整数中。这需要一些位操作技巧。假设您以这种格式存储整数:

      00000000BBBBBBBBGGGGGGGGRRRRRRRR(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];
      

      希望你现在能理解。

      【讨论】:

        【解决方案5】:
        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;
        }
        

        不是很理想,因为那里有两个对象,一个 byte[] 和一个 MyColor。这使开销加倍。据我所知,没有任何优化可能会将 byte[] 转换为 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 HotSpot(TM) 64 位服务器 VM(24.45-b08,混合模式)。

        【讨论】:

        • 第二个对象是 2 个对象(不像 C++ 中可以声明字节 [3] 颜色)。所以正如你所说,选择#2是最糟糕的。但问题仍然存在,如果我有 3 个字节,它们每个占用 4 个字节(一个 int)吗?
        • 我看不出它是如何在你的屏幕截图中产生内存占用的,所以我只是使用 SizeofUtil 进行了尝试——结果非常奇怪。我是不是搞错了什么?
        • 数组 #19 是 bigarr,显示的大小为 100,024。红色下划线;各种面板都代表了它的状态。我不确定SizeofUtil 是如何工作的,如果你想在程序中找到大小,也许看看你是否能弄清楚 java.lang.instrument.Instrumentation,尽管我认为探查器最容易使用。
        • 我认为单个字节确实占用了 int 的空间,但 Java 中的 byte[] 是一种独特的类型,而不是“单字节数组”。从内存分配的角度来看,byte[9] 的数据区域很可能分配了三个 32 位字,尽管硬件通常具有(并且 JVM 将使用)可以访问 32 位的 8 位块的指令位字不打扰其余部分。
        • @supercat 很好,我相信这可以通过查看汇编器输出来检查。 Atlas,我没有必要的设置来转储它,但我会调查一下。
        【解决方案6】:

        其他答案都没有考虑过的一个基本问题是您是否希望一种颜色成为 RGB 三元组或识别某种持有 一个 RGB 三元组。考虑一个表单上有两个文本对象的场景;表单的背景颜色指定为红色。其中一个文本对象的背景颜色被指定为与表单相同;另一个被指定为红色。尽管两个文本对象的背景都显示为相同的红色,但一个对象将具有表单的背景颜色,而另一个对象将具有其属性匹配的独立颜色。如果表单的背景颜色变为绿色,则其中一个文本对象将继续与表单颜色相同,而另一个将继续为红色。

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

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

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

        【讨论】:

          【解决方案7】:

          Doorknob 将bytes 存储在int 中的想法很好,但他的实现对我不起作用。我最终使用了 Java 内置的 Color class。例如,要创建一个表示颜色的ints 数据结构,您可以这样做:

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

          稍后,如果您想解码int

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

          【讨论】:

            猜你喜欢
            • 2017-09-14
            • 1970-01-01
            • 1970-01-01
            • 2010-10-30
            • 2012-03-17
            • 2021-12-24
            • 2020-02-03
            • 1970-01-01
            相关资源
            最近更新 更多