在Java中使用不同的键搜索对象

3

我是一名从C++转向Java的开发者。因此,我还没有完全掌握以Java方式完成任务所需的所有专业知识。我有以下类。

class Site
{
   String siteName;
   Integer siteId;
   Integer views;
   Integer searches;
}

我维护了两张地图来搜索这个类的对象(使用站点名称或站点ID)

HashMap<String, Site> siteNameToSiteMap;
HashMap<Integer, Site> siteIdToSiteMap;

然而,从现在开始,我必须向Site类添加一个名为parentBrand的字段。这将强制我创建另一个Map以便能够搜索它。

HashMap<String, Site> parentBrandToSiteMap;

这样的“索引”变量可能会增加,因此也会增加我维护的地图数量。我记得在C ++中开发时使用过Boost Multi-indexed容器来解决类似问题。在Java中有没有等效的、受支持良好、文档完善的库可以使用?如果没有,是否有一种方式可以重构我的代码来解决我的问题。

从这个点开始:我必须向类中添加一个名为parentBrand的字段,事情变得令人困惑。哪个类是parentBrand?为什么不是HashMap<String, ParentBrand>而是HashMap<String, Site>等等。 - Andremoniy
如果不同映射的键是互斥的(例如,parentBrandToSiteMap 中的键不能等于 siteNameToSiteMap 中的任何键),则可以使用单个 HashMap<String,Site> 存储所有关联(每个 Site 将由最多 3 个键索引)。这将需要在将站点 ID 放入单个映射时将其转换为字符串。 - Eran
你可以考虑将所有这些数据存储在数据库中,这样你就可以按照自己的方式创建索引。如果你担心I/O开销,那么可以使用像H2这样的内存数据库。 - RealSkeptic
@RealSkeptic,内存数据库可能是我最后的选择。我希望使用一个库。当你有一个库时,更容易获得管理权限并更容易集成(我希望)。 - Pranav Kapoor
你到底想要避免什么?多余的代码?重复?内存开销? - shmosel
@shmosel 额外的代码和重复。同时,使用Boost multi-index容器所带来的问题,目前的方式似乎只是一种原始的操作方式。 - Pranav Kapoor
3个回答

2
我很惊讶没有类似boost多索引容器的版本可用(也许有,只是我不知道)。但在Java中自己编写一个版本并不太难。
一个简单但可行的版本可能如下所示:

基本网站对象

我使用了略有不同的Site对象,只是为了保持简单(因为我在公交车上无法访问此帖子...)
    public class Site {
        Integer id;
        String name;
        String rating;
        // .. Constructor and toString removed for brevity
    }

一个封装的版本

我稍后将介绍一些实用的类,但它们有点丑陋。这只是为了展示在稍微封装一下之后最终接口会变得更加易于理解:

class SiteRepository { 
   private final MultiMap<Site> sites = new MultiMap<>();
   public final AbstractMap<String, Site> byName = sites.addIndex((site)->site.name);
   public final AbstractMap<Integer,Site> byId = sites.addIndex((site)->site.id);
   public final AbstractMap<String,List<Site>> byRating = sites.addMultiIndex((Site site)->site.rating);
   public void add(Site s) { sites.add(s); }
}

SiteRepository repo = new SiteRepository();
repo.add(...);
Site site = repo.byId.get(1234);
repo.byId.forEach((Integer id, Site s) -> System.err.printf("   %s => %s\n", id, s));

MultiMap核心

可能应该被称为MultiIndex,因为MultiMap有其他含义...

    public static class MultiMap<V> {

        public static class MultiMapIndex<K,V> extends AbstractMap<K,V> {
            @Override
            public Set<Entry<K, V>> entrySet() {
                return map.entrySet();
            }
            HashMap<K,V> map = new HashMap<>();
       }


        public <K> MultiMapIndex<K,V> addIndex(Function<V, K> f) {
            MultiMapIndex<K,V> result = new MultiMapIndex<>();
            Consumer<V> e = (V v) -> result.map.put(f.apply(v), v);
            mappers.add(e);
            values.forEach(e);
            return result;
        }

        public <K> MultiMapIndex<K,List<V>> addMultiIndex(Function<V, K> f) {
            MultiMapIndex<K,List<V>> result = new MultiMapIndex<>();
            Consumer<V> e = (V v) -> {
                K key = f.apply(v);
                List<V> list = result.map.get(key);
                if (list == null) {
                    list = new ArrayList<>();
                    result.map.put(key, list);
                }
                list.add(v);
            };
            mappers.add(e);
            values.forEach(e);
            return result;
        }

        public void add(V v) {
            values.add(v);
            mappers.forEach( e -> e.accept(v));
        }

        private List<Consumer<V>> mappers = new ArrayList<>();
        private List<V> values = new ArrayList<>();    
    }

更多底层示例

    public static void main(String[] args) {
        // Create a multi-map
        MultiMap<Site> multiMap = new MultiMap<>();

        // Add an index by Site.id
        MultiMapIndex<Integer, Site> byId = multiMap.addIndex((site)->site.id);

        // Add some entries to the map
        multiMap.add(new Site(1234,"A Site","A"));
        multiMap.add(new Site(4321,"Another Site","B"));
        multiMap.add(new Site(7777,"My Site","A"));

        // We can add a new index after the entries have been
        // added - this time by name.
        MultiMapIndex<String, Site> byName = multiMap.addIndex((site)->site.name);

        // Get a value by Id.
        System.err.printf("Get by id=7777 = %s\n", byId.get(7777));
        // Get a value by Name
        System.err.printf("Get by name='A Site' = %s\n", byName.get("A Site"));  

        // We can do usual mappy things with the indexes,
        // such as list the keys, or iterate over all entries
        System.err.printf("byId.keys() = %s\n", byId.keySet());
        byId.forEach((Integer id, Site s) -> System.err.printf("   %s => %s\n", id, s));

        // In some cases the map is not unique, so I provide a 
        // way to get all entries with the same value as a list.
        // in this case by their rating value.
        MultiMapIndex<String, List<Site>> byRating = multiMap.addMultiIndex((Site site)->site.rating);
        System.err.printf("byRating('A') = %s\n", byRating.get("A"));
        System.err.printf("byRating('B') = %s\n", byRating.get("B"));

        // Adding stuff after creating the indices is fine.
        multiMap.add(new Site(3333,"Last Site","B"));
        System.err.printf("byRating('A') = %s\n", byRating.get("A"));
        System.err.printf("byRating('B') = %s\n", byRating.get("B"));
    }
}

最后一个中肯的回答。但是我认为你应该在你的回答中加入F的某种定义。 - RealSkeptic
嗯,对不起,刚开始有点不完善。但是我在公交车上花了一些时间来修改它。提供了一个经过重大改进的工作答案。(尽管是基于Java 8) - Michael Anderson

-2

我认为你可以通过列表来搜索你的对象:

List<Site> sites;
for (Site s : sites) {
 if (s.getSiteName().equal(siteName)) {
 // do something
 }
 if (s.getSiteId().equal(siteId)) {
 // do something
 }
}

为什么 OP 想要在 O(n) 中搜索而不是 O(1)? - RealSkeptic

-3
你应该创建一个Bean(容器),因为Java不需要代码优化,它会被JIT编译器自动优化。
public class SiteMap {
    String siteName;
    Integer siteId;
    String parentBrand;

.... Getters and setters ...
}

List<SiteMap> myList = new ArrayList<>();

如果您需要比较或排序,则可以在SiteMap类上实现Comparable接口,以允许在需要时对详细信息进行排序。
如果使用Java 8,则还可以使用Streams来过滤或获取所需的内容。因为有一个fetchFirst方法。
SiteMap mysite = myList.stream()
            .filter(e -> e.siteName.equals("Amazon.com"))
            .findFirst()
            .get();

为什么所有的踩都没有评论如何改进答案或为什么它不合适。这不是Stackoverflow应该运作的方式,列出的方法将起作用,您可以使用过滤器流搜索所有参数,我只展示了第一个,您也可以使用.filter(e->e.siteId = 123)。 - Theresa Forster
不,使用列表的时间复杂度是O(n),而使用HashMap的时间复杂度是O(1)。随着项目数量的增加,这会有显著的差异。它绝对不是“同样快速”的。而编译器优化也与此无关。 - RealSkeptic
如果操作人员试图从特定的数据结构中获得毫秒级的改进,那么他们应该使用Java语言,Java应该被编写为易于阅读和修改,而不是为了获取最后一毫秒的访问时间。 - Theresa Forster
@TheresaForster,只是为了让你有一个比较,目前我的数据集中至少会有10-20K个Site对象(预计还会增加)。 - Pranav Kapoor
@PranavKapoor 我所展示的方法是常见的Java方法,也是良好的实践。如果你有这么多的项目,最好将其存储在数据库中,并从那里检索,因为将20,000个项目加载到内存中会给即使是强大的计算机带来压力。此外,我曾经在一家为英国所有英超联赛提供网站服务的公司工作,我们曾在某一时刻使用仅有3台集群服务器运行Java,每秒处理1500万次请求。 - Theresa Forster
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接