为什么字节数组不能存储在java中的整数数组中

时间:2013-07-28 13:28:27

标签: java

此代码有效

int h;
byte r;
h=r;

但这些不是

int[] h;
byte[] r;
h=r;

或者说

int[] h =new byte[4];

我想知道为什么?

6 个答案:

答案 0 :(得分:22)

byteint进行隐式转换,但byte[]转换为int[]。这很有意义--JIT编译器知道要获取int[]中的值,它只需要将索引乘以4并将其添加到数据的开头(在验证之后,并且假设没有额外的填充,当然)。如果您可以为byte[]变量分配int[]引用,则该功能无效 - 表示不同。

该语言本来可以设计为允许转换,但是要创建一个 new int[],其中包含所有字节的副本,但这样做会非常令人惊讶。 Java的其余部分的设计,其中赋值运算符只是将一个值从运算符的右侧复制到左侧的变量。

或者,我们可以对VM施加限制,每个数组访问都必须查看相关数组对象的实际类型,并找出如何正确获取元素......但这本来是可怕的(甚至比参考型阵列协方差的当前肮脏更糟糕。)

答案 1 :(得分:10)

这就是设计。当您将byte分配给更宽int时,这没关系。但是当你声明new byte[4]时,那是一个[“连续”]内存的一部分,粗略地说,等于4 * 8位(或4个字节)。一个int是32位,因此从技术上讲,所有byte数组的大小都等于一个int的大小。在C中,您可以直接访问内存,可以执行一些指针魔术并将byte指针转换为int指针。在Java中,你不能,这是安全的。

无论如何,你为什么要那样? 免责声明:除了您的游乐场之外,以下代码被视为极不可能。 好吧,我得到了不安全工作的例子。看看ideone:http://ideone.com/e14Omr

我希望评论足够解释。

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        /* too lazy to run with VM args, use Reflection */
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        /* get array address */
        Unsafe unsafe = (Unsafe)f.get(null);
        byte four_bytes[] = {25, 25, 25, 25};
        Object trash[] = new Object[] { four_bytes };
        long base_offset_bytes = unsafe.arrayBaseOffset(Object[].class);
        long four_bytes_address = unsafe.getLong(trash, base_offset_bytes); // <- this is it
        long ints_addr = unsafe.allocateMemory(16); // allocate 4 * 4 bytes, i.e. 4 ints
        unsafe.copyMemory(four_bytes_address + base_offset_bytes, ints_addr, 4); // copy all four bytes
        for(int i = 0; i < 4; i++) {
            System.out.println(unsafe.getInt(ints_addr + i)); //run through entire allocated int[],
                                                              // get some intestines
        }
        System.out.println("*****************************");
        for(int i = 0; i < 16; i++) {
            System.out.println(unsafe.getByte(ints_addr + i)); //run through entire allocated int[],
                                                              // get some intestines
        }
    }
}

答案 2 :(得分:9)

差异首先是由于原始类型和引用类型之间的行为差​​异。

如果您不熟悉它,原始类型具有“价值语义”。这意味着当a = b;a为基本类型bbyteshort,{{1}时intlong },floatdoublebooleanchar)复制numeric / boolean值。例如:

int a = 3;
int b = a; // int value of a is copied to b
a = 5;
System.out.println(b); // outputs: 3

但是数组是对象,对象具有“引用语义”。这意味着当a = b; ab都被声明为数组类型时,所引用的数组对象将变为共享。在某种意义上,值仍然被复制,但是这里“值”只是指向位于内存中其他位置的对象的指针。例如:

int[] a = new int[] { 3 };
int[] b = a; // pointer value of a is copied to b, so a and b now point at the same array object
a[0] = 5;
System.out.println(b[0]); // outputs: 5
a = null; // note: 'a' now points at no array, although this has no effect on b
System.out.println(b[0]); // outputs: 5

因此可以执行int = byte因为数值将被复制(因为它们都是基本类型),并且因为任何可能的byte类型值都可以安全地存储在int中(它是a "widening" primitive conversion)。

int[]byte[]都是对象类型,因此当您执行int[] = byte[]时,您要求对象(数组)共享(没有复制)。

现在你要问,为什么int数组和字节数组不能共享数组内存?如果他们这样做会是什么意思呢?

Ints是字节大小的4倍,因此如果int和byte数组具有相同数量的元素,则会导致各种无意义。如果您尝试以内存有效的方式实现它,那么在访问int数组的元素以查看它们是否实际是字节数组时,将需要复杂(且非常慢)的运行时逻辑。从字节数组内存中读取int必须读取并加宽字节值,并且int存储必须要么丢失高3字节,要么抛出一个异常,说明没有足够的空间。 ,您可以通过填充所有字节数组以快速但内存浪费的方式执行此操作,以便每个元素有3个浪费的字节,以防有人想要将字节数组用作int阵列。

另一方面,也许你想要为每个int打包4个字节(在这种情况下,共享数组将不具有相同数量的元素,具体取决于您用来访问它的变量的类型)。不幸的是,这也会导致废话。最大的问题是它不能跨CPU架构移植。在little-endian PC上,b[0]将引用i[0]的低字节,但在ARM设备上b[0]可能指向i[0]的高字节(当程序运行时它甚至可以改变,因为ARM具有可切换的字节序)。访问数组长度属性的开销也会变得更复杂,如果字节数组的长度不能被4整除,会发生什么?!

您可以在C中执行此操作,但这是因为C数组没有明确定义的长度属性,并且因为C不会尝试保护您免受其他问题的影响。 C不关心你是否超出数组边界或混淆字节序。但Java确实关心,因此在Java中共享阵列内存是不可行的。 (Java没有unions。)

这就是为什么int[].classbyte[].class都分别扩展了类Object,但两个类都没有扩展另一个类。您不能在声明为指向int数组的变量中存储对字节数组的引用,就像您无法在List类型的变量中存储对String的引用一样};他们只是不兼容的课程。

答案 3 :(得分:1)

简单地说,类型byte []不会扩展int []

答案 4 :(得分:1)

当你说

int[] arr = new byte[5];

你复制参考文献。右侧是对字节数组的引用。基本上,这看起来像:

|__|__|__|__|__|
0  1  2  3  4            offset of elements, in bytes
^
|
reference to byte array

左侧是对int数组的引用。但是,预计这将是这样的:

|________|________|________|________|________|
0        4        8        12       16
^
|
reference to int array

因此,简单地复制参考是不可能的。为了获得arr [1],代码将查看起始地址+4(而不是开始地址+ 1)。

实现你想要的唯一方法是创建一个具有相同元素数量的int []并复制那里的字节。

不自动执行此操作的理由:

  • 将单个字节解释为int基本上没有成本,特别是不必分配任何内存。
  • 复制字节数组完全不同。必须分配新的int数组,它至少是字节数组的4倍。复制过程本身可能需要一些时间。

结论:在Java中,你总是可以说&#34;我想把这个特殊字节视为一个int。&#34;但你不能说:&#34;我想要处理一些包含字节的数据结构(如数组或类实例),就好像它包含整数一样。&#34;

答案 5 :(得分:-1)

你不能因为它的大元素将被存储在较小的元素中.Integer不能存储在byte.Its我们的内存设计谁决定这些类型的分配