如何增加数组的长度

25

我有一个简短的问题。在Java中,我有一个整数数组,需要在整个类中使其长度变化。具体来说,我需要在特定情况下将其增加一个。我尝试像这样。

        sbgHeadX = new int[ numberOfSBG ];

我想在需要时增加整数变量numberOfSBG的值,但我觉得这样做行不通。是否还有其他方法?


你能给出一个合理的最大值吗?显然,你的数组永远不可能比Integer.MAX_VALUE更大,但是如果你知道你的数组也永远不会超过300,你可以将其长度设置为300。 - emory
1
@emory - 个人而言,我认为这样做是一种明显的代码异味:结果是一个数组,其中有些元素“不应该被使用”。这是一种容易出错和笨拙的编码风格,需要传递一个单独的变量来表示有效元素的数量。同时也打破了.size()返回可用元素数的假设。 - ToolmakerSteve
如果你不使用 ArrayList,那么你将不得不重新创建它。 - NomadMaker
10个回答

35

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

Arrays.copyOf() 

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


1
@TheUnfunCat 是的,我已经删除了它。谢谢。 - Erre Efe

25

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

或者你可以使用像List这样的动态数据结构。


3
在我看来,如Jakub的回答所示使用Arrays.copyOf()会使代码更短、更易读。 - ToolmakerSteve

16
我建议您使用 ArrayList,因为您不必再担心长度问题了。一旦创建,您无法修改数组的大小:

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

来源

我该如何访问ArrayList的特定索引? - kullalok
1
@user1276078 yourArraylist.get(2) 相当于 yourClassicArray[2] - talnicolas
1
虽然这样做是可行的,但可能会导致不必要的自动装箱,因为OP指出它是基本类型int。Java集合需要以对象形式作为值,因此每次调用集合方法时JVM都会将所有值转换为Integer。这可能随着时间的推移而变得昂贵。显然,程序员需要决定是否值得执行像Hunter McMillen在下面建议的“开销”,但我认为他的答案更准确地回答了这个问题。 - Steve Siebert
3
OP问如何改变数组的大小,他并没有要求提供数组的替代方案。-1 - arkon

3

先说一下:

  • 在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这里这里的问题中出现了这样的情况,后者促使我写下这个答案。
2 技术上,示例项目并没有调整数组大小。它只是使用不同于代码中指定的大小创建了一个数组。实际上,在保持其引用并复制其元素的同时调整现有数组的大小会更加复杂。
3 对于原始数组,相应的字节码操作将是newarray (0xbc)。
4 如前所述,这只是一个概念验证(而且非常hacky)。一个更健壮的实现可以使用像ASM这样的字节码操作库,在任何newarrayanewarray命令之前插入一个pop命令,然后是一个sipush命令。有关该解决方案的一些更多提示可以在这个答案的评论中找到。
5 在实际场景中,代理代码显然会在单独的项目中。


2
你可以利用ArrayList。数组具有固定的大小。
这个示例可以帮助你。该示例的输出非常简单易懂。
输出: 2 5 1 23 14 新长度:20 索引为5的元素:29 列表大小:6 删除索引为2的元素:1 2 5 23 14 29

1

按照定义,数组是固定大小的。您可以使用ArrayList代替它,它是一个“动态大小”的数组。实际上,发生的情况是VM通过ArrayList公开的数组“调整大小”。

另请参阅

*使用后向复制数组


3
实际上发生的是虚拟机会调整Arraylist公开的数组的大小。这是错误的。事实上,ArrayList的代码会创建一个新的支撑数组,并将现有数组的内容复制到新数组中。数组长度不能被改变,即使是虚拟机也不行。这将违反Java语言规范。 - Stephen C
你还只对了一半。这不是JVM做出的调整。ArrayList类在普通的Java代码中完成了它...而且数组并没有被数组列表暴露出来...它是一个被数组列表隐藏的私有对象。 - Stephen C
3
当你提到“使用复制回数组”时,一个Java新手会问的下一个问题是...“什么是复制回数组”。而对于这个问题的答案是并不存在所谓的“复制回数组”。我认为你真正想表达的是...嗯...“ArrayList的代码创建一个新的后备数组,并将现有数组的内容复制到其中”。 - Stephen C

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

1

数组长度变更方法示例(包括旧数据复制):

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

0

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

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

0
回答你的问题,而不是简单地说“使用ArrayList”,这可能并不总是一个选择,特别是当你想要最佳性能时:
没有原生的方法可以做到这一点,但你可以使用类似于这样的方法:
int[] newArray = new int[oldArray.length + 1];
System.arrayCopy(oldArray, 0, newArray, 0, oldArray.length);
oldArray = newArray;

就是这样,最后一个位置是可以自由使用的。请注意,您可以将“1”更改为任何您喜欢的数字。如果您经常使用此代码,还可以将其制作成方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接