11

我正在尝试从ArrayList<String>

for(int i=0; i<myList.size(); i++)
{
    if(myList.get(i).contains("foo"))
    {
        myList.remove(i);
    }
}

但是,这会在我的列表中留下“空白”。我希望列表省略空元素,并在迭代后缩小到必要的大小。

有没有一种聪明的方法可以做到这一点而不必切换到LinkedList?

4

7 回答 7

32

但是,这会在我的列表中留下“空白”。

不,它没有。它从列表中完全删除条目。其他元素被适当移动。它对您编写的方式所做的就是跳过对下一个条目的检查......因为那将“洗牌”成为 element i但接下来您将看看 element i + 1

避免这种情况的一种简单方法是向后工作:

for (int i = myList.size() - 1; i >= 0; i--) {
    if (myList.get(i).contains("foo")) {
        myList.remove(i);
    }
}

当然,或者使用其他答案中提到的迭代器。两者都可以工作 - 如果您要删除多个条目,上面的代码可能会稍微高效一些,因为在您开始时移动的次数会更少。这不太可能很重要。

不幸的是,为了使用迭代器解决方案,您必须显式使用迭代器 - 使用增强的 for 循环时不能从集合中删除。

于 2013-10-15T08:54:34.797 回答
7

改用Iteratorand 调用Iterator.remove()

Iterator it = myList.iterator();
while(it.hasNext()) {
    if (it.next().contains("foo")) { 
        it.remove();
    }
}

这样,您还可以避免减少列表大小的麻烦,同时将其作为循环的退出条件,并使用可能变化的索引来访问它。

当然,向后遍历列表也可以。

于 2013-10-15T08:54:54.530 回答
3

您正在寻找的智能方式是Iterator界面。例如:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
   String nextItem = it.next();
   if (nextItem.contains("foo")) {
      it.remove();
   }
}
于 2013-10-15T08:55:02.813 回答
3

Scala函数式编程的影响,我建议您将值复制到新列表以实现不变性。

List<String> filtered = new ArrayList<String>();
for (String s : myList) {
   if (!s.contains("foo")) {
       filtered.add(s);
   }
}

我还建议尝试 2 个库:Guavalambdaj

于 2013-10-15T09:04:27.997 回答
2

ArrayList 在后台维护一个数组。我想深入研究java.util.ArrayListand的源代码java.util.LinkedList

首先 ArrayList 在后台维护一个数组。一旦你创建了一个 ArrayList 实例,它就会创建一个大小为 10 的数组,并且它会随着元素的插入而增长。它的尺寸增长到 3(size)/2 +1

这是源代码。

arrat 列表的默认大小。查看构造函数代码

public ArrayList() {
         this(10);
    }

它的大小增长到 3(size)/2 + 1。这里是源代码ArrayList#ensureCapacity方法在现场调用ArrayList#add

public void ensureCapacity(int minCapacity) {
         modCount++;
         int oldCapacity = elementData.length;
         if (minCapacity > oldCapacity) {
             Object oldData[] = elementData;
             int newCapacity = (oldCapacity * 3)/2 + 1;
             if (newCapacity < minCapacity)
                 newCapacity = minCapacity;
             // minCapacity is usually close to size, so this is a win:
             elementData = Arrays.copyOf(elementData, newCapacity);
        }
     }

当您从 ArrayList 中删除任何项目时。它从列表中删除,其他列表项向下移动到已删除项的位置。需要特别注意的是,对这个对象的引用设置为 null 并且对象符合 GC 条件,但仍然为 ArrayList 分配了一个引用。ArrayList 后面的数组大小是相同的

这是源代码

public E remove(int index) {
         rangeCheck(index);

        modCount++;
         E oldValue = elementData(index);

         int numMoved = size - index - 1;
         if (numMoved > 0)
             System.arraycopy(elementData, index+1, elementData, index,
                              numMoved);
         elementData[--size] = null; // Let gc do its work

         return oldValue;
     }

正如 Jon Skeet 回答的那样,当一个项目被移除时,下一个被移除项目的项目将位于被移除项目的位置。

但是,删除后分配的内存空间是相同的。java.util.LinkedList 代表这个问题。LinkedList里面的所有item都是动态分配和释放的(当然是GC的工作)

java.util.LinkedList在后台维护一个双向链表。每个添加和删除操作都会更改 LinkedList 使用的内存空间。该项目被删除,并且从其上一个和下一个项目中对项目的引用被更新。

这是源代码:

private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
           throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {
           for (int i = 0; i <= index; i++)
                e = e.next;
       } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
       return e;
   }

我假设 GC 会在删除项目后立即收集项目,我知道这不确定。但是删除的内存位置是 GC 的候选者。请注意对对象和对象本身的引用。

ArrayList 和 LinkedList 都删除项目,而 ArrayList 仍然存储对象类型的引用和原始类型的内存空间,链接列表也删除引用和内存空间。至少,引用和内存也将符合 GC 条件。

于 2013-10-15T09:57:31.210 回答
1

删除列表后会自动缩小。

假设您删除索引 3 处的元素,该元素将被删除,列表将缩小,并且索引 4 处的元素在删除后将具有索引 3。

你应该做这个:

for(int i=0; i<myList.size(); i++)
{
    if(myList.get(i).contains("foo"))
    {
        myList.remove(i);
        // as element is removed, next element will have decremented index
        i--;
    }
}
于 2013-10-15T08:59:30.067 回答
0

不,它不会在您的列表中留下“空格”,但您会错过从列表中删除所有必需元素的机会。

让我们尝试用下面的例子来解释。我有一个包含 4 个元素的 ArrayList。(A B C D)。

for (int i = 0; i < list.size(); i++) {
            if (((String) list.get(i)).contains("c")) {
                list.remove(i);
            }
            if (((String) list.get(i)).contains("b")) {
                list.remove(i);
            }
        }

for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i)+" ");
    }

结果:acd

向前遍历列表时,我尝试删除元素 (c,b) 但元素 c 仍然存在于我的列表中。

为了避免这种情况,我们可以像下面这样向后遍历。

for (int i = list.size() - 1; i >= 0; i--) {
            if (((String) list.get(i)).contains("a")) {
                list.remove(i);
            }
            if (((String) list.get(i)).contains("c")) {
                list.remove(i);
            }
        }

        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }

结果:bd

于 2016-05-10T09:56:07.830 回答