4

我必须为我们的 web 应用程序编写一个批量操作版本,让您在 UI 的基础上进行更有限的操作。所需的操作是将对象分配给一个类别。一个类别可以有多个对象,但给定的对象只能属于一个类别。

该任务的工作流程是:

1) 使用浏览器,上传如下形式的文件:

# ObjectID, CategoryID
Oid1, Cid1
Oid2, Cid1
Oid3, Cid2
Oid4, Cid2
[etc.]

该文件很可能有数十到数百行,但绝对可能有数千行。

在理想的世界中,给定的对象 id 只会在文件中出现一次(反映了一个对象只能分配给一个类别的事实)但是由于文件是在我们的控制之外创建的,因此不能保证这实际上是真的并且处理必须处理这种可能性。

2)服务器将接收文件,对其进行解析,对其进行预处理并显示如下页面:

723 objects to be assigned to 126 categories
142 objects not found
 42 categories not found

Do you want to continue?

[Yes]     [No]

3)如果用户点击Yes按钮,服务器将实际完成工作。

由于我不想在步骤 (2) 和 (3) 中解析文件,因此作为 (2) 的一部分,我需要构建一个容器,该容器将跨越请求并保存数据的有用表示,这将使我很容易提供数据来填充“预览”页面,让我有效地完成实际工作。(虽然显然我们有会话,但我们通常只保留很少的内存会话状态。)

有一个现有的

assignObjectsToCategory(Set<ObjectId> objectIds, CategoryId categoryId)

通过 UI 完成分配时使用的函数。非常希望批量操作也使用此 API,因为除了简单分配之外,它还执行大量其他业务逻辑,并且在完成此批量分配时我们需要运行相同的业务逻辑。

最初,如果文件“非法”为给定对象指定了多个类别,这将是可以的——将对象任意分配给与其关联的文件的类别之一是可以的。

所以我最初认为,在步骤 (2) 中,当我浏览文件时,我将构建并放入交叉请求容器 a Map<CategoryId, Set<ObjectId>>(特别是HashMap用于快速查找和插入),然后当我需要完成工作时可以只在地图上进行迭代,并为每个CategoryId拉出关联Set<ObjectId>并将它们传递到assignObjectsToCategory().

但是,关于如何处理重复ObjectIds 的要求发生了变化。现在将按如下方式处理它们:

  • 如果一个ObjectId在文件中出现多次并且所有时间都与同一个相关联,则CategoryId将该对象分配给该类别。
  • 如果 anObjectId在文件中多次出现并与不同CategoryId的 s 相关联,则认为这是一个错误并在“预览”页面上提及它。

这似乎弄乱了我的Map<CategoryId, Set<ObjectId>>策略,因为它没有提供一种很好的方法来检测ObjectId我刚刚从文件中读出的文件是否已经与CategoryId.

所以我的问题是如何最有效地检测和跟踪这些重复ObjectId的?

我想到的是同时使用“正向”和“反向”映射:

public CrossRequestContainer
{
    ...

    Map<CategoryId, Set<ObjectId>> objectsByCategory;  // HashMap
    Map<ObjectId, List<CategoryId>> categoriesByObject; // HashMap
    Set<ObjectId> illegalDuplicates;

    ...
}

然后当每(ObjectId, CategoryId)对被读入时,它会被放入两个地图中。一旦文件被完全读入,我可以这样做:

for (Map.Entry<ObjectId, List<CategoryId>> entry : categoriesByObject.entrySet()) {
    List<CategoryId> categories = entry.getValue();
    if (categories.size() > 1) {
        ObjectId object = entry.getKey();
        if (!all_categories_are_equal(categories)) {
            illegalDuplicates.add(object);
            // Since this is an "illegal" duplicate I need to remove it
            // from every category that it appeared with in the file.
            for (CategoryId category : categories) {
                objectsByCategory.get(category).remove(object);
            }
        }
    }
}

当此循环完成时,objectsByCategory将不再包含任何“非法”重复项,illegalDuplicates并将包含所有“非法”重复项,以便根据需要报告回来。然后我可以遍历objectsByCategory,获取Set<ObjectId>每个类别的 ,并调用assignObjectsToCategory()来完成任务。

但是虽然我认为这会起作用,但我担心将数据存储两次,尤其是当输入文件很大时。而且我也担心我错过了一些东西:效率,这会非常缓慢。

有没有办法做到这一点,不会使用双内存但仍然可以快速运行?我是否遗漏了一些即使使用双倍内存仍会比我预期的慢很多的东西?

4

1 回答 1

1

鉴于您给出的限制,我没有办法使用更少的内存来做到这一点。

但是,一种可能的优化是只维护在多个类别中列出的对象的类别列表,否则只是将对象映射到类别,即:

Map<CategoryId, Set<ObjectId>> objectsByCategory;  // HashMap
Map<ObjectId, CategoryId> categoryByObject; // HashMap
Map<ObjectId, Set<CategoryId>> illegalDuplicates;  // HashMap

是的,这又增加了一个容器,但它(希望)只包含几个条目;此外,categoryByObject 映射的内存需求也减少了(减少了每个条目的一个列表开销)。

当然,逻辑要复杂一些。最初发现重复项时,应将对象从 categoryByObject 映射中删除并添加到非法重复映射中。在将任何对象添加到 categoryByObject 映射之前,您需要首先检查非法重复映射。

最后,在构建其他两个映射之后,在单独的循环中构建 objectsByCategory 映射可能不会影响性能,并且会稍微简化代码。

于 2011-04-28T03:00:21.230 回答