【问题标题】:How to increase an array's length如何增加数组的长度
【发布时间】:2012-04-21 01:28:24
【问题描述】:

我有一个简单的问题。我在java中有一个整数数组,它需要它的长度在整个类中变化。具体来说,我需要它在某些情况下增加一。我是这样试的。

        sbgHeadX = new int[ numberOfSBG ];

我会在需要时增加整数变量 numberOfSBG,但我认为这行不通。有没有其他办法?

【问题讨论】:

  • 你能提供一个合理的最大值吗?显然,您的数组永远不会大于 Integer.MAX_VALUE ,但如果您知道数组也永远不会大于 300,您可以将其长度设置为 300。
  • @emory - 就个人而言,我认为这样做会产生强烈的代码气味:结果是一个数组,其中一些元素“不应该被使用”。这是一种容易出错且笨拙的编码风格,需要传递一个带有有效元素数量的单独变量。也打破了.size() 产生要使用的元素数量的假设。
  • 如果您不打算使用 ArrayList,则必须重新创建它。

标签: java arrays


【解决方案1】:

如果你不想或不能使用ArrayList,那么有一个实用方法:

Arrays.copyOf() 

这将允许您指定新的大小,同时保留元素。

【讨论】:

  • @TheUnfunCat 是的。我已将其删除。谢谢
【解决方案2】:

Java 中的数组具有在声明时指定的固定大小。要增加数组的大小,您必须创建一个更大的新数组并将所有旧值复制到新数组中。

例如:

char[] copyFrom  = { 'a', 'b', 'c', 'd', 'e' };
char[] copyTo    = new char[7];

System.out.println(Arrays.toString(copyFrom));
System.arraycopy(copyFrom, 0, copyTo, 0, copyFrom.length);
System.out.println(Arrays.toString(copyTo));

您也可以使用动态数据结构,例如列表。

【讨论】:

  • 恕我直言,使用 Arrays.copyOf(),就像 Jakub 的回答一样,会给出更短、更易读的代码。
【解决方案3】:

我建议您使用ArrayList,因为您不必再​​担心长度了。创建后,您无法修改数组大小:

数组是一个容器对象,它包含固定数量的单一类型值。数组的长度是在创建数组时确定的。创建后,它的长度是固定的。

(Source)

【讨论】:

  • 如何访问数组列表的特定索引?
  • @user1276078 yourArraylist.get(2) 相当于yourClassicArray[2]
  • 虽然这可行,但这可能会导致不必要的自动装箱,因为 OP 表明它是一个原始 int。 Java 集合需要 Objects 作为它们的值,因此每次调用集合方法时,JVM 都会将所有值转换为 Integer。随着时间的推移,这可能会增加成本。显然,程序员需要决定是否值得像下面的 Hunter McMillen 建议的那样做“开销”,但恕我直言,他的回答是对这个问题的更准确的回答。
  • OP 询问如何更改数组的大小,他没有要求替代数组。 -1
【解决方案4】:

第一件事:

  • 在Java 中,一旦创建了一个数组,它的长度就是固定的。数组无法调整大小。
  • 您可以将数组的元素复制到具有不同大小的新数组中。最简单的方法是使用Arrays.copyOf() 方法之一。
  • 如果您需要可变大小的集合,最好使用 ArrayList 而不是数组。

话虽如此,在某些情况下,您可能别无选择,只能更改在代码之外某处创建的数组的大小。1 唯一的方法是操作创建数组的代码的生成字节码。

概念验证

以下是一个小型概念验证项目,它使用 Java instrumentation 动态更改数组的大小2。示例项目是一个maven项目,结构如下:

.
├─ pom.xml
└─ src
   └─ main
      └─ java
         └─ com
            └─ stackoverflow
               └─ agent
                  ├─ Agent.java
                  └─ test
                     └─ Main.java

Main.java

此文件包含我们将要操作其字节码的目标类:

package com.stackoverflow.agent.test;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] array = {"Zero"};

        fun(array);

        System.out.println(Arrays.toString(array));
    }

    public static void fun(String[] array) {
        array[1] = "One";
        array[2] = "Two";
        array[3] = "Three";
        array[4] = "Four";
    }
}

main 方法中,我们创建一个大小为1 的String 数组。在fun 方法中,在数组边界之外分配了4 个附加值。按原样运行此代码显然会导致错误。

Agent.java

此文件包含将执行字节码操作的类:

package com.stackoverflow.agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class Agent {
    public static void premain(String args, Instrumentation instrumentation) {
        instrumentation.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader l, String name, Class<?> c,
                    ProtectionDomain d, byte[] b) {

                if (name.equals("com/stackoverflow/agent/test/Main")) {
                    byte iconst1 = (byte) 0x04;
                    byte iconst5 = (byte) 0x08;
                    byte anewarray = (byte) 0xbd;

                    for (int i = 0; i <= b.length - 1; i++) {
                        if (b[i] == iconst1 && b[i + 1] == anewarray) {
                            b[i] = iconst5;
                        }
                    }

                    return b;
                }

                return null;
            }
        });
    }
}

在字节码级别,Main 类中String 数组的创建包含两个命令:

  • iconst_1,它将值为 1 的 int 常量压入堆栈 (0x04)。
  • anewarray,它会弹出堆栈的值并创建一个相同大小的引用数组3 (0xbd)。 上面的代码在 Main 类中查找该命令组合,如果找到,则将 const_1 命令替换为 const_5 命令 (0x08),从而有效地将数组的维度更改为 5。4

pom.xml

maven POM文件用于构建应用JAR,配置主类和Java代理类。5

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.stackoverflow</groupId>
  <artifactId>agent</artifactId>
  <version>1.0-SNAPSHOT</version>

  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.1</version>
        <configuration>
          <archive>
            <manifestEntries>
              <Main-Class>com.stackoverflow.agent.test.Main</Main-Class>
              <Premain-Class>com.stackoverflow.agent.Agent</Premain-Class>
              <Agent-Class>com.stackoverflow.agent.Agent</Agent-Class>
              <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

构建和执行

可以使用标准的mvn clean package 命令构建示例项目。

在不引用代理代码的情况下执行将产生预期的错误:

$> java -jar target/agent.jar 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
        at com.stackoverflow.agent.test.Main.fun(Main.java:15)
        at com.stackoverflow.agent.test.Main.main(Main.java:9)

使用代理代码执行时会产生:

$> java -javaagent:target/agent.jar -jar target/agent.jar
[Zero, One, Two, Three, Four]

这表明使用字节码操作成功更改了数组的大小。


1 问题herehere中出现了这种情况,后者促使我写下这个答案。
2从技术上讲,示例项目不会调整数组的大小。它只是使用与代码中指定的大小不同的大小创建它。实际上,在保持其引用并复制其元素的同时调整现有数组的大小会稍微复杂一些。
3 对于原始数组,相应的字节码操作将是 newarray (0xbc ) 代替。
4 如前所述,这只是一个概念证明(而且是一个非常老套的证明)。代替随机替换字节,更健壮的实现可以使用像ASM 这样的字节码操作库在任何newarrayanewarray 命令之前插入pop 命令,然后是sipush 命令。可以在this answer 的 cmets 中找到有关该解决方案的更多提示。
5在实际场景中,代理代码显然位于单独的项目中。

【讨论】:

    【解决方案5】:

    您可以使用 ArrayList。数组具有固定数量的大小。

    这里的Example 可以为您提供帮助。该示例的输出非常简单。

    输出: 2 5 1 23 14
    新长度:20
    索引 5:29 处的元素
    列表大小:6
    删除索引 2 处的元素:1
    2 5 23 14 29

    【讨论】:

      【解决方案6】:
      Item[] newItemList = new  Item[itemList.length+1];
          //for loop to go thorough the list one by one
          for(int i=0; i< itemList.length;i++){
              //value is stored here in the new list from the old one
              newItemList[i]=itemList[i];
          }
          //all the values of the itemLists are stored in a bigger array named newItemList
          itemList=newItemList;
      

      【讨论】:

        【解决方案7】:

        根据定义,数组是固定大小的。您可以使用 Arraylist 来代替,即“动态大小”数组。实际上发生的是VM“调整大小”* ArrayList 暴露的数组。

        See also

        *使用备份数组

        【讨论】:

        • “实际上发生的情况是VM调整了arraylist暴露的数组的大小。”。不正确。事实上,ArrayList 的代码创建了一个新的后备数组并复制现有数组的内容。数组长度不能改变……即使是 JVM。这将违反 Java 语言规范。
        • 你还是只说对了一半。不是 JVM 做出调整。 ArrayList 类在普通的 Java 代码中执行此操作。并且数组不被数组列表公开......它是一个被数组列表隐藏的私有对象。
        • 当你说“使用回拷贝数组”时,Java 新手要问的下一个问题是……“什么是回拷贝数组”。答案是没有这样的事情。我认为您真正的意思是......好吧......“ArrayList 的代码创建了一个新的支持数组并将现有数组的内容复制到它”。
        【解决方案8】:

        数组长度改变方法示例(旧数据处理):

        static int[] arrayLengthChange(int[] arr, int newLength) {
            int[] arrNew = new int[newLength];
            System.arraycopy(arr, 0, arrNew, 0, arr.length);
            return arrNew;
        }
        

        【讨论】:

          【解决方案9】:

          如果未在堆内存中声明数组的长度,则无法增加数组的长度(请参见下面的代码,用户询问第一个数组输入,然后询问您要增加多少数组并复制先前的数组元素):

          #include<stdio.h>
          #include<stdlib.h>
          
          int * increasesize(int * p,int * q,int x)
          {
              int i;
              for(i=0;i<x;i++)
              {
                   q[i]=p[i];
              }
              free(p);
              p=q;
              return p;
          }
          void display(int * q,int x)
          {
              int i;
              for(i=0;i<x;i++)
              {
                  printf("%d \n",q[i]);
                  
              }
          }
          
          int main()
          {
              int x,i;
              printf("enter no of element to create array");
              scanf("%d",&x);
              int * p=(int *)malloc(x*sizeof(int));
              printf("\n enter number in the array\n");
              for(i=0;i<x;i++)
              {
                  scanf("%d",&p[i]);
              }
              int y;
              printf("\nenter the new size to create new size of array");
              scanf("%d",&y);
              int * q=(int *)malloc(y*sizeof(int));
              display(increasesize(p,q,x),y);
              free(q);
          }
          

          【讨论】:

            【解决方案10】:

            回答您的问题而不说“使用 ArrayList”之类的话,这可能并不总是一种选择,特别是当您想要获得最佳性能时:

            没有本地方法可以做到这一点,但您可以使用类似这样的方法:

            int[] newArray = new int[oldArray.length + 1];
            System.arrayCopy(oldArray, 0, newArray, 0, oldArray.length);
            oldArray = newArray;
            

            就是这样,最后一个位置可以免费使用。请注意,您可以将“1”更改为任何您喜欢的数字。如果你反复使用,你也可以用这段代码做一个方法。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-11-20
              • 2020-06-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-03-20
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多