【问题标题】:What is the logic behind Arrays.copyOfRange(byte[], int, int) strange behavior?Arrays.copyOfRange(byte[], int, int) 奇怪行为背后的逻辑是什么?
【发布时间】:2018-02-17 01:20:36
【问题描述】:

谁能解释一下 Arrays.copyOfRange(byte[], int, int)) 奇怪行为背后的逻辑?我可以用简单的例子来说明我的意思:

byte[] bytes = new byte[] {1, 1, 1};
Arrays.copyOfRange(bytes, 3, 4); // Returns single element (0) array
Arrays.copyOfRange(bytes, 4, 5); // Throws ArrayIndexOutOfBoundsException

在这两种情况下,我都复制了数组边界之外的范围(即start >= array.length),因此错误条件对我来说至少很奇怪(如果from < 0from > original.length)。在我看来应该是:如果from < 0from >= original.length。也许我错过了什么?

【问题讨论】:

标签: java arrays standard-library


【解决方案1】:

The JavaDoc 指定了关于它期望的参数的三点:

一个:

[from] 必须介于 0original.length(含)之间

两个:

[to] 必须大于或等于from

三:

[to] 可能大于 original.length

对于Arrays.copyOfRange(bytes, 3, 4)的情况,一、二、三为真,表示有效。

对于Arrays.copyOfRange(bytes, 4, 5) 的情况,只有两个和三个为真,这意味着它们是无效的。


预期的行为?是的。

不直观的行为?有点。

如果您的问题是“为什么会这样设计?”那么除了代码作者之外,没有人能告诉你答案。

【讨论】:

    【解决方案2】:

    看看,copyOfRange 是如何工作的:

    public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }
    

    最重要的部分是:

    System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
    

    所以当你调用 Arrays.copyOfRange(bytes, 3, 4); System.arraycopy的最后一个参数“长度-要复制的数组元素的数量”是0。arraycopy的调用看起来像System.arraycopy(original, from, copy, 0,0);

    【讨论】:

    • 嗯,用内部实现来证明糟糕的 API 是很奇怪的,当我们谈论标准库时甚至更奇怪。
    • 还请检查 System.arraycopy 何时抛出 IndexOutOfBoundsException:srcPos 参数是否定的。 destPos 参数是否定的。长度参数是否定的。 srcPos+length 大于 src.length,即源数组的长度。 destPos+length大于dest.length,目的数组的长度
    【解决方案3】:

    查了Arrays.copyOfRange(byte[] original, int from, int to)的出处

      public static byte[] copyOfRange(byte[] original, int from, int to) {
    1     int newLength = to - from;
    2     if (newLength < 0)
    3         throw new IllegalArgumentException(from + " > " + to);
    4     byte[] copy = new byte[newLength];
    5     System.arraycopy(original, from, copy, 0,
    6                      Math.min(original.length - from, newLength));
    7     return copy;
      }
    
    • 在第 1 行,目标数组的长度用1 计算
    • 在第 4 行创建了大小为1 的新数组,所有元素均为0
    • 在第 6 行,要复制的元素的长度计算为零,因为

      // Math.min(original.length - from, newLength));
      Math.min(3 - 3, 1)); --> returns zero
      
    • 在第 5 行,System.arraycopy 根本不会将任何内容复制到数组 copy

    如果您的frombytes.length + 1,则length 为负数(bytes.length - from)。

    一个小代码来演示

    public class ArrayCopy {
    
        public static void main(String[] args) {
            byte[] bytes = new byte[]{11, 12, 13};
            int from = 3;
            int to = 4;
            copyOfRange(bytes, from, to);
    
            from = 2;
            to = 3;
            copyOfRange(bytes, from, to);
    
            from = 4;
            to = 5;
            copyOfRange(bytes, from, to);
        }
    
        static void copyOfRange(byte[] bytes, int from, int to) {
            System.out.printf("%ncopyOfRange(bytes: %s  from: %d  to: %d)%n",
                    Arrays.toString(bytes),
                    from,
                    to
                    );
    
            // line 1
            int newLength = to - from;
            System.out.println("int newLength = " + newLength);
    
            // line 2
            if (newLength < 0) {
                throw new IllegalArgumentException(from + " > " + to);
            }
    
            // line 4
            byte[] copy = new byte[newLength];
    
            // to show that in the suspicious case System.arrayCopy does nothing
            copy[0] = 42;
            System.out.println("byte[] copy   = " + Arrays.toString(copy));
            int length = bytes.length - from;
            System.out.println("int length    = " + length);
            int minLenght = Math.min(length, newLength);
            System.out.println("int minLenght = " + minLenght);
    
            // line 5
            System.arraycopy(bytes, from, copy, 0, minLenght);
    
            System.out.println("byte[] copy   = " + Arrays.toString(copy));
        }
    }
    

    输出

    copyOfRange(bytes: [11, 12, 13]  from: 3  to: 4)
    int newLength = 1
    byte[] copy   = [42]
    int length    = 0
    int minLenght = 0
    byte[] copy   = [42]
    
    copyOfRange(bytes: [11, 12, 13]  from: 2  to: 3)
    int newLength = 1
    byte[] copy   = [42]
    int length    = 1
    int minLenght = 1
    byte[] copy   = [13]
    
    copyOfRange(bytes: [11, 12, 13]  from: 4  to: 5)
    int newLength = 1
    byte[] copy   = [42]
    int length    = -1
    int minLenght = -1
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
    

    【讨论】:

      【解决方案4】:

      这意味着您可以使用此方法创建任何长度的新“空”数组,就像您在 // Returns single element (0) array 情况中描述的那样。

      Arrays.copyOfRange 方法存在的主要目的是因为它“即时”创建新数组。当然,这个新的 Array 可以比源 Array 更大。

      这种行为是documented 功能。

      范围(to)的最终索引,必须大于等于from,可能大于original.length,这种情况下 '\u000' 被放置在副本中索引更大的所有元素中 大于或等于 original.length - 从。返回的长度 数组将是 to - from。

      为什么要这样实现?假设你要调用这个:

      byte[] bytes = new byte[] {1, 1, 1};
      byte[] newBytes = Arrays.copyOfRange(bytes, 2, 6); // Returns array length 6 - 2 = 4
      

      它将创建数组 [1, 0, 0, 0] 并且原始数组中不存在的所有元素将被初始化为当前类型的默认字面量。 但是如果您想指定 from 更大但不等于 bytes.length(文档不鼓励这样做),它将在调用此 System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); 期间导致 ArrayIndexOutOfBoundsException 因为 original.length - from 会更少比0

      所以从技术上讲,如果你使用这样的说明:

      int n = 4;
      Arrays.copyOfRange(bytes, bytes.length, bytes.length + n);
      

      假设您只想创建大小为n 的新空数组。换句话说,您正在创建一个新的 Array 并且没有从源 Array 复制任何内容。

      【讨论】:

        猜你喜欢
        • 2010-10-30
        • 2012-02-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多