[英]Store a color in Java - byte;byte;byte vs. byte[3] vs int
我需要存储大量的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循环的总数为:
第一个惊喜是,使用byte [3]的第二种方法使用的内存更少! 看到声明中的byte [3]的JVM是否可能只是内联分配它?
其次,每个对象的内存为(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;
}
}
}
由于int
可以容纳四个byte
,因此您可以为颜色使用一个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;
}
您的第一种方法似乎比其他两种方法更好。 在64位JVM上将需要16个字节,在32位上将需要12个字节。 第二个是最昂贵的,第三个也是16个字节。
如果要存储图像,还可以将颜色存储在三个byte[width][height]
矩阵中,这样可以节省很多字节。 这样做的想法是放弃MyColor
类,该类每个实例占用额外的13个字节,从而节省约80%的内存。
将各种颜色存储为int数组中的RGB int:
int[] colors;
它高效且非常方便。 您还可以通过使用字节数组为每种颜色节省另一个字节(25%),但这不太方便,可能不值得。
如果使用任何类型的MyColor对象,则在甚至可以开始存储颜色数据本身之前,在对象标头上至少要浪费8个字节,在对对象的引用上至少要浪费4个字节。
我假设(大多数)JVM实际上为每个条目使用一个int。
不,它们是真实字节,尽管它将占用4个字节而不是3个字节的空间,所以它与int字段占用的空间相同。
byte[] color = new byte[3];
是效率最低的。 数组是一个单独的对象,在计算实际数组数据之前,至少需要8个额外的字节用于数组对象标头,4个字节用于其length
字段,以及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;
}
这不是很理想,因为那里有两个对象,一个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™64位服务器VM(24.45-b08,混合模式)。
这完全取决于您要存储的颜色深度。 假设您具有24位颜色深度,即8位红色,8位绿色和8位蓝色,那么您只能将所有三个值存储在一个整数中。 因为java整数是32位。
因此,只需定义:
int colorValue = 0; //RGB Composite color value with 24 bit depth.
现在,您要将所有颜色分量存储在一个整数中。 这需要一些操作技巧。 假设您以这种格式存储整数:
00000000BBBBBBBBBBGGGGGGGGRRRRRRRR(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];
希望你现在能理解。
其他任何答案都没有考虑的一个基本问题是,您是要让颜色成为 RGB三元组还是要确定 包含 RGB三元组的事物。 考虑表单上两个文本对象的情况; 表单的背景色指定为红色。 文本对象的背景颜色之一被指定为与表单相同。 另一个被指定为红色。 尽管两个文本对象的背景都显示为相同的红色,但是一个对象将具有表单的背景色,而另一个对象将具有属性匹配的独立颜色。 如果将表单的背景颜色更改为绿色,则文本对象之一将继续与表单相同,而另一个将继续为红色。
如果对颜色使用可变的类类型,则该类型的每个变量都将标识包含RGB三元组的对象。 如果多个变量标识相同的对象,则使用这些变量中的任何一个来更改对象的属性将有效地更改所有变量的该属性。 如果识别该对象的唯一变量是应该更改的变量(如上述文本对象),则可能会很好,但如果识别该对象的某些变量应独立于该变量,则可能会很糟糕。如果文本对象标识了与背景相同的颜色对象)。
使用不可变的类类型可以使语义更清晰(对不可变对象的引用可能被视为简单地封装了其内容),但是在任何时候需要更改某种颜色时,都需要查找或创建一个颜色对象。封装正确的颜色; 与仅更新存储的号码相比,这可能需要做更多的工作。
除非需要在颜色之间建立关系,否则我建议使用整数表示颜色是可行的方法。 它相对有效,语义很简单。 用于表示颜色的整数类型的每个不同存储位置都将彼此独立,并且它们之间没有不必要的交互作用。
Doorknob的想法是将byte
s存储在一个int
是好的,但是他的实现对我不起作用。 我最终使用了Java的内置Color
类 。 例如,要创建表示颜色的int
数据结构,可以执行以下操作:
Color c = new Color(55, 155, 255);
int ci = c.getRGB();
以后,如果您想解码int
:
Color cd = new Color(ci);
int R = cd.getRed(); // 55
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.