【问题标题】:Recursive function in Java - thread safe collectionJava中的递归函数 - 线程安全集合
【发布时间】:2014-08-13 15:06:10
【问题描述】:

假设我有一个 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 条件来阻止它。我的问题是:什么在这里不起作用?我想用数组visitedDocs 操作有问题。如何使用已访问的文件进行每个递归调用实际版本的数组?

尽我所能,我有一个递归函数,它对某个集合 X 进行操作:

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

并且X 必须始终是实际的。

【问题讨论】:

  • 我要注意的是,列出的代码第一次不会调用visitURL...它调用readDocument,而你还没有给我们这个代码。
  • 从哪里提取文档的链接?

标签: java multithreading copyonwritearraylist


【解决方案1】:

每次调用visitURL 时,都会创建visitedDocs 的新实例。所以,每次调用开始时它都是空的,最后只包含tmp的当前迭代。

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

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

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

【讨论】:

  • 我可以通过在代码中的某处创建静态 ArrayList 来实现类似的东西吗?结果,我只想在递归的每个级别上都有一个已经访问过文档的对象。
  • 是的,你应该可以。您需要在最持久的类中的某处声明静态 CopyOnWriteArrayList,但 visitURL 可以访问。如private static CopyOnWriteArrayList&lt;String&gt; visitedList = new CopyOnWriteArrayList&lt;String&gt;();。那么你在visitURL() 中的代码将是:CopyOnWriteArrayList&lt;String&gt; visitedDocs = new CopyOnWriteArrayList&lt;String&gt;(visitedList); 不幸的是,没有看到整个类,我无法告诉你静态变量应该在哪里。
【解决方案2】:

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

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,因为它会在每个写入访问时制作其内部内容的完整副本!

【讨论】:

  • 我明天可以试试,看看效果如何。谢谢你的想法。你能解释一下为什么我不应该使用递归算法吗?我应该如何使用这个功能?如何处理返回的已访问队列?
  • 我已修改我的答案以解决您的第一个问题。关于返回值:不需要返回访问过的文档,但是我不知道你是否在算法之外的某个地方需要这个集合,所以我决定这样做。随意跳过。
  • 现在我发现我不能使用它。我需要知道我在文档树中的深度以便准备格式化的日志文件
  • 嗯,这将是一个非常重要的信息!您想如何处理链接在多个文档中的文档?只在第一次访问或每次访问时记录它们?
  • 感谢您的帮助。我重新考虑了自己的代码,并在那里发现了一个错误。现在它可以工作了.... if 语句中应该只有两条指令:)
猜你喜欢
  • 2011-11-23
  • 1970-01-01
  • 2018-02-24
  • 2012-09-19
  • 2011-02-28
  • 2013-12-10
  • 2019-02-10
  • 2014-08-13
  • 1970-01-01
相关资源
最近更新 更多