从数组中移除元素(Java)

172

有没有一种快速(且外观漂亮)的方法可以从Java数组中删除一个元素?


5
即使问题是重复的,其他问题中的答案也既不快速又不好看。它将数组手动转换为ArrayList。 - f1v3
15个回答

269

1
@Clive Guava似乎只能在集合上工作。 - Peter Lawrey
4
这会同时缩小数组的大小吗? - Supun Wijerathne
1
@SupunWijerathne 它必须改变大小/长度。 - Peter Lawrey
6
在Java8中它无法工作,我没有找到任何名为removeElement的方法。 - Atul Agrawal
1
@AtulAgrawal 你需要在最后一行链接中包含这个库。 - Peter Lawrey
显示剩余3条评论

47

你的问题不是很清楚。从你自己的回答中,我能更好地了解你想要做什么:

public static String[] removeElements(String[] input, String deleteMe) {
    List result = new LinkedList();

    for(String item : input)
        if(!deleteMe.equals(item))
            result.add(item);

    return result.toArray(input);
}

注意:此代码未经测试。读者需要自行添加错误检查(如果inputdeleteMe为null,我会抛出IllegalArgumentException;在null列表输入上返回一个空列表是没有意义的。从数组中删除null字符串可能有意义,但也留给读者练习;目前,如果deleteMe为null,它将抛出NPE,因为它试图调用equals方法。)

我的做法:

我使用了LinkedList。迭代应该同样快,而且如果您删除大量元素,则可以避免任何调整大小或分配过大的列表。您也可以使用ArrayList,并将初始大小设置为输入的长度。这可能不会产生太大的差异。


3
请使用 List<String> 结果。在当前编译器中执行此操作时,toArray 命令会产生类型错误(另一个解决方案是将结果强制转换)。 - user1086498

46

最好的选择是使用集合,但如果由于某些原因无法使用,可以使用arraycopy。您可以使用它来在略微不同的偏移量从同一数组中复制到另一个位置。

例如:

public void removeElement(Object[] arr, int removedIdx) {
    System.arraycopy(arr, removedIdx + 1, arr, removedIdx, arr.length - 1 - removedIdx);
}

回复评论后的编辑:

这不是另一种好方法,它确实是唯一可接受的方法--任何允许此功能的工具(如Java.ArrayList或Apache Utils)都将在幕后使用此方法。 另外,您确实应该使用ArrayList(或如果经常删除中间元素则使用LinkedList),因此,除非您正在做作业,否则这甚至不应该成为一个问题。

为了分配集合(创建新数组),然后删除一个元素(集合将使用arraycopy执行此操作),然后在每个删除上调用toArray(创建第二个新数组),这样就已经不是优化问题,而是糟糕的编程。

假设您有一个占用100mb内存的数组。现在,您想遍历它并删除20个元素。

试一试吧...

我知道你认为这不会那么大,或者如果你要一次删除那么多,你会以不同的方式编码它,但我修复了很多这样的代码,人们都基于这样的假设。


4
执行“删除”操作(即将数组左移一个元素),最后一个元素会不会重复出现?也就是说,在执行删除操作后,a.length 不会改变,对吧?我并不是说我不喜欢这个想法,只是需要注意这一点。 - Adamski
+1。这对我的目的很有效。(我修复了你示例中的小问题,希望你不介意。) - Gunslinger47
是的,这只会将元素向左移动,最后一个元素仍然存在。 我们必须使用新数组进行复制。 - Reddy
顺便提一下,org.apache.commons.lang.ArrayUtils 也是这样做的。 - Reddy
假设您正在向数组添加和删除元素,那么也应该跟踪数组中的“最后”项,因此不需要复制。 - Bill K

40

你无法从基本的Java数组中移除一个元素。相反,可以查看各种集合和ArrayList。


我知道,我只是想用ArrayList或类似的方式来实现一个美观的方法。你有什么提示吗? - Tobias
+1:使用链表,生活更简单。 - S.Lott
9
链表通常不是一个好的选择。List接口提供随机访问,但LinkedList的访问时间复杂度为O(n),而不是O(1)。 - Tom Hawtin - tackline
1
您可以通过System.arrayCopy从数组中删除元素,但无法更改其大小。然而,列表是一个更好的解决方案。 - TofuBeer
@Tom:选择LinkedList是否正确还取决于其他因素。即通过索引访问链表的“随机访问”是O(n)。 - Todd Owen
显示剩余3条评论

16

一个看起来不错的解决方案是在一开始就使用列表而不是数组。

List.remove(index)

如果你必须使用数组,那么两次调用System.arraycopy很可能是最快的方法。

Foo[] result = new Foo[source.length - 1];
System.arraycopy(source, 0, result, 0, index);
if (source.length != index) {
    System.arraycopy(source, index + 1, result, index, source.length - index - 1);
}

(Arrays.asList 也是处理数组的好方法,但似乎不支持 remove 操作。)


1
+1:使用LinkedList或ArrayList。 - S.Lott
最终版本应该是 if (result.length != index)... 而不是 if (source.length != index)... 吗? - SteveR

13
我认为问题是要求不使用Collections API的解决方案。数组可用于低级细节,其中性能很重要,或用于松散耦合的SOA集成。在后者中,将它们转换为Collections并将它们传递给业务逻辑是可以的。对于低级性能问题,它通常已经被快速而肮脏的命令式状态混乱(例如for循环)所混淆。在这种情况下,来回转换Collections和数组是麻烦的,难以阅读,甚至会消耗资源。顺便说一下,TopCoder怎么样?总是那些数组参数!因此,当在竞技场时,请准备好处理它们。以下是我对问题的理解和解决方案。它在功能上与Bill Kjelovirt所给出的都不同。此外,它优雅地处理了元素不在数组中的情况。希望这有所帮助!
public char[] remove(char[] symbols, char c)
{
    for (int i = 0; i < symbols.length; i++)
    {
        if (symbols[i] == c)
        {
            char[] copy = new char[symbols.length-1];
            System.arraycopy(symbols, 0, copy, 0, i);
            System.arraycopy(symbols, i+1, copy, i, symbols.length-i-1);
            return copy;
        }
    }
    return symbols;
}

1
太好了。有太多回复是回答与OP不同的问题。 - Return_Of_The_Archons

4
你可以使用ArrayUtils API以一种“看起来不错的方式”移除它。它在数组上实现了许多操作(删除、查找、添加、包含等)。 看一下这个,它使我的生活更简单了。

3

数组的长度是不可改变的,但可以通过将新值复制到现有索引编号并存储它们来改变索引所持有的值。例如:1=mike , 2=jeff // 10 = george 11 覆盖 1,使得 mike 的值被重写。

Object[] array = new Object[10];
int count = -1;

public void myFunction(String string) {
    count++;
    if(count == array.length) { 
        count = 0;  // overwrite first
    }
    array[count] = string;    
}

1
我认为指出数组长度无法更改是一个重要的细节! - Torsten Robitzki

3

需要一些更多的前提条件来满足 Bill K 和 dadinn 所撰写的内容。

Object[] newArray = new Object[src.length - 1];
if (i > 0){
    System.arraycopy(src, 0, newArray, 0, i);
}

if (newArray.length > i){
    System.arraycopy(src, i + 1, newArray, i, newArray.length - i);
}

return newArray;

3
好的,非常感谢。现在我使用类似这样的东西:
public static String[] removeElements(String[] input, String deleteMe) {
    if (input != null) {
        List<String> list = new ArrayList<String>(Arrays.asList(input));
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(deleteMe)) {
                list.remove(i);
            }
        }
        return list.toArray(new String[0]);
    } else {
        return new String[0];
    }
}

如果你真的需要保持初始数组不变,最好创建一个空列表并填充正确的元素,而不是用这种方式做。 - Nicolas
我不确定当人们建议使用集合时,他们是否考虑到了这一点,但无论如何,请小心处理那些列表索引。看起来你正在跳过任何删除后紧随其后的元素(尝试 {"a", "b", "deleteMe", "deleteMe", "c"})。 - Sam Martin

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