Java中的递归函数 - 线程安全集合

时间:2014-08-13 15:06:11

标签: java multithreading copyonwritearraylist

假设我有一个xml文档,我可以在其中找到相同类型的其他文档的链接,这些文档也可以链接到另一个文档。在起点我有要阅读和分析的文件清单。我写了以下算法来阅读和分析这些文件:

    private static List<String> documentNames = new ArrayList<String>();

    main(...) {
       //add names to documentNames arrayList above.
       for(String documentName : documentNames) {
           readDocument(documentName);
        }
    }

函数readDocument如下所示:

       private static CopyOnWriteArrayList<String> visitURL(String documentName) {
       CopyOnWriteArrayList<String> visitedDocs = new CopyOnWriteArrayList<String>(); //visited Ref urls
         if (!visitedDocs .contains(documentName)) {
            analyseAndWriteOnDisk(documentName)    //it saves analised document on disk
            CopyOnWriteArrayList<String> tmp = visitURL(documentName);
            visitedDocs.addAll(tmp);
         } else {
            System.out.println(documentName " - I have seen it !");
         }
         return visitedDocs;
       }

它有效,但在执行程序后,我可以找到重复的文件(具有相同内容的文件)。我不应该拥有它们 - 我通过函数visitURL中的if-condition来阻止它。我的问题是:这里有什么用?我认为使用数组visitedDocs进行操作有问题。如何在已经访问过的文件的每个递归调用实际版本的数组中使用?

尽可能精确,我有一个递归函数,它对一些集合X进行操作:

   recursion(CollectionType X) {
      someoperations(X)
      recursion(X)
   }

X必须始终是实际的。

2 个答案:

答案 0 :(得分:0)

每次拨打visitURL时,您都会创建visitedDocs的新实例。因此,每次在调用开始时它都是空的,并且最后只包含tmp的当前迭代。

根据JavaDocs,您需要像这样调用新的:

CopyOnWriteArrayList<String> visitedDocs = new CopyOnWriteArrayList<String>(documentNames) //here you need to add the parameter of the ArrayList you want to copy, otherwise you're instantiating a blank ArrayList.

然后,您需要将documentNames设置为等于返回的visitedDocs

答案 1 :(得分:0)

您不应该使用递归算法。使用包含要分析的所有文档的队列以及包含已分析的所有文档的集合更容易。只要队列不为空,就可以从中提取文档,对其进行分析,并将已提取的链接添加到队列中(如果尚未访问)。

private Collection<String> visit(Collection<String> intialDocs) {
    Queue<String> documents = new LinkedBlockingQueue(initialDocs);
    Set<String> visited = new HashSet<>();
    while (!documents.isEmpty()) {
        String doc = documents.poll();
        visited.add(doc);

        Collection<String> links = analyzeDocument(doc);
        for(String link : links) {
            if (!visited.contains(link) documents.add(link);
        }
    }
    return visited;
}

private Collection<String> analyzeDocument(String document) {
    // TODO: analyze document and return a list of all links in that document
}

用法:

Set<String> allVisitedDocuments = visit(documentNames);

这种迭代方法优于递归解决方案:

  • 更容易看出它是如何运作的。
  • 更容易争辩说它会终止。
  • 调试更容易。
  • 如果需要,可以轻松并行化。
  • 只需更改用于排队文档的集合类型,即可轻松影响文档处理顺序。 (现在它执行广度优先搜索,如果您使用像Stack这样的LIFO,则会获得深度优先,而某些优先级队列可能会让您根据文档类型决定等等。)
  • 如果您有一系列链接文档,则递归可能会变得非常深,并且可能会发生堆栈溢出。

注意:如果您不使用多个主题,则应使用CopyOnWriteArrayList,因为它会在每个上创建其内部内容的完整副本访问!