【问题标题】:A linked list with multiple heads in JavaJava中具有多个头的链表
【发布时间】:2011-01-01 12:57:56
【问题描述】:

我有一个列表,我想在其中保留几个头指针。我尝试在同一个列表上创建多个 ListIterators,但这禁止我在列表中添加新元素...(请参阅Concurrent Modification exception)。

我可以创建自己的类,但我宁愿使用内置实现;)

更具体地说,这是两个基本操作的低效实现,一个不起作用:

class MyList <E> {
    private int[] _heads;
    private List<E> _l;

    public MyList ( int nbHeads ) {
        _heads = new int[nbHeads];
        _l = new LinkedList<E>();
    }

    public void add ( E e ) {
        _l.add(e);
    }

    public E next ( int head ) {
        return _l.get(_heads[head++]); // ugly
    }
}


class MyList <E> {
    private Vector<ListIterator<E>> _iters;
    private List<E> _l;

    public MyList ( int nbHeads ) {
        _iters = new Vector<ListIterator<E>>(nbHeads);
        _l = new LinkedList<E>();

        for( ListIterator<E> iter : _iters ) iter = _l.listIterator(); 
    }

    public void add ( E e ) {
        _l.add(e);
    }

    public E next ( int head ) {
        // ConcurrentModificationException because of the add()
        return _iters.get(head).next();
    }
}

【问题讨论】:

  • 您应该将您的代码放入 github 的 gist 中,或者更具体地说明您的要求。那么,您想为第二个头部“插入”元素还是更想要一个链接网络?
  • 多少个“头”? 2,或任意
  • Collections.synchronizedList()返回的列表上做多个迭代器工作?
  • 同步列表不能解决我的问题。我添加了一些例子来澄清。

标签: java linked-list


【解决方案1】:

我将通过将所有实际迭代器隐藏到包装类中的列表并将列表本身隐藏在其自己的包装中来解决此问题。列表的包装器会知道所有的迭代器包装器;在add() 上,它需要强制每个迭代器包装器记录当前位置,删除内部迭代器,然后执行实际添加(这将避免 ConcurrentModificationException,因为所有实际迭代器都已被销毁),然后将所有迭代器包装器重新创建它们的迭代器并将它们设置到必要的位置。由于您似乎只添加到列表的末尾,因此不需要花哨的索引,但您必须弄清楚已经推进到末尾的迭代器会发生什么 - 它们是在末尾还是在它们原来的位置在列表中的位置?当然,他们中的一些人可能已经告诉他们的来电者hasNext() 是假的......还有一件事:add()get() 我应该认为是synchronized

这里有一个测试驱动的解决方案。 PureListWrapper 和 PureIteratorWrapper,顾名思义,只是将它们所有的方法调用委托给它们包装的元素。

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import junit.framework.TestCase;

public class ConcurrentlyAddableListTest extends TestCase {


    public void testAdd() throws Exception {
        List<String> list = new ConcurrentlyAddableList<String>();
        list.add("apple");
        list.add("banana");
         Iterator<String> a = list.iterator();
         Iterator<String> b = list.iterator();
         b.next();
         Iterator<String> c = list.iterator();
         c.next();
         c.next();
         list.add("cherry");
         assertEquals("apple", a.next());
         assertEquals("banana", b.next());
         assertEquals("cherry", c.next());
    }


    private static class ConcurrentlyAddableList<T> extends PureListWrapper<T> {
        private final Set<WrappedIterator<T>> iterators = new HashSet<WrappedIterator<T>>();

        @Override
        public Iterator<T> iterator() {
            WrappedIterator<T> iterator = new WrappedIterator<T>(super.iterator());
            iterators.add(iterator);
            return iterator;
        }

        @Override
        public synchronized boolean add(T o) {
            final HashSet<WrappedIterator<T>> set = new HashSet<WrappedIterator<T>>(iterators);
            for (WrappedIterator<T> iterator : set)
                iterator.rememberPosition(this);
            boolean result = super.add(o);
            for (WrappedIterator<T> iterator : set)
                iterator.restorePosition(this);
            return result;
        }
    }

    private static class WrappedIterator<T> extends PureIteratorWrapper<T> {
        private int index = 0;

        public WrappedIterator(Iterator<T> iterator) {
            super(iterator);
        }

        @Override
        public T next() {
            index++;
            return super.next();
        }

        public void restorePosition(List<T> list) {
            setIterator(list.iterator());
            int prevIndex = index;
            index = 0;
            while (index < prevIndex)
                next();
        }

        public void rememberPosition(List<T> list) {
            setIterator(null);
        }

    }
}

【讨论】:

    【解决方案2】:

    异常原因在its javadoc中描述:

    例如,一般不会 允许一个线程修改一个 收集,而另一个线程是 迭代它。一般来说, 迭代结果未定义 在这些情况下。一些 迭代器实现(包括 那些通用的 集合实现由提供 JRE)可能会选择抛出这个 如果此行为是异常 检测到。这样做的迭代器是 被称为快速失败迭代器,因为它们 快速而干净地失败,而不是 冒着任意的、非确定性的风险 未确定时间的行为 未来。

    请注意,此异常不会 总是表明一个对象有 同时被一个 不同的线程。如果单线程 发出一系列方法 违反合约的调用 对象,该对象可能会抛出 这个例外。例如,如果一个 线程直接修改集合 当它迭代 带有快速失败迭代器的集合, 迭代器会抛出这个 例外。

    也就是说,对于 JDK 中的所有集合类,对集合的更改会使其所有迭代器(执行更改的迭代器除外)无效。

    您可以通过使用索引而不是迭代器来解决这个问题,但这需要一个随机访问列表,而链表不能提供有效的随机访问。

    因此,如果你真的需要这样的数据结构,你就必须超越 JDK 或者自己实现一个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多