我正在尝试从ArrayList<String>
for(int i=0; i<myList.size(); i++)
{
if(myList.get(i).contains("foo"))
{
myList.remove(i);
}
}
但是,这会在我的列表中留下“空白”。我希望列表省略空元素,并在迭代后缩小到必要的大小。
有没有一种聪明的方法可以做到这一点而不必切换到LinkedList
?
但是,这会在我的列表中留下“空白”。
不,它没有。它从列表中完全删除条目。其他元素被适当移动。它对您编写的方式所做的就是跳过对下一个条目的检查......因为那将“洗牌”成为 element ,i
但接下来您将看看 element i + 1
。
避免这种情况的一种简单方法是向后工作:
for (int i = myList.size() - 1; i >= 0; i--) {
if (myList.get(i).contains("foo")) {
myList.remove(i);
}
}
当然,或者使用其他答案中提到的迭代器。两者都可以工作 - 如果您要删除多个条目,上面的代码可能会稍微高效一些,因为在您开始时移动的次数会更少。这不太可能很重要。
不幸的是,为了使用迭代器解决方案,您必须显式使用迭代器 - 使用增强的 for 循环时不能从集合中删除。
改用Iterator
and 调用Iterator.remove()
。
Iterator it = myList.iterator();
while(it.hasNext()) {
if (it.next().contains("foo")) {
it.remove();
}
}
这样,您还可以避免减少列表大小的麻烦,同时将其作为循环的退出条件,并使用可能变化的索引来访问它。
当然,向后遍历列表也可以。
您正在寻找的智能方式是Iterator
界面。例如:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String nextItem = it.next();
if (nextItem.contains("foo")) {
it.remove();
}
}
受Scala
函数式编程的影响,我建议您将值复制到新列表以实现不变性。
List<String> filtered = new ArrayList<String>();
for (String s : myList) {
if (!s.contains("foo")) {
filtered.add(s);
}
}
我还建议尝试 2 个库:Guava
和lambdaj
ArrayList 在后台维护一个数组。我想深入研究java.util.ArrayList
and的源代码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 条件。
删除列表后会自动缩小。
假设您删除索引 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--;
}
}
不,它不会在您的列表中留下“空格”,但您会错过从列表中删除所有必需元素的机会。
让我们尝试用下面的例子来解释。我有一个包含 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