我之前主要使用C++,现在正在使用Java进行编程。我发现,在C++中使用STL的一些基本操作,在Java中似乎比我预想的更加繁琐。我的结论是,可能有一种更好的Java语法,我还没有完全掌握。以下是一个用伪代码表示的示例。
我有一组物品,它们具有基于某些成员变量的自然排序关系,这些成员变量恰好是字符串。
class Thing
{
String key1;
String key2;
}
在C++中,我可以定义一个排序操作符<(Thing, Thing),并将它们放入std::set中。例如:
///
/// @brief
/// provide a total order for 'Things' using key1 and key2
///
bool operator<(const Thing& a, const Thing& b)
{
if (a.key1 < b.key1) return true;
else if (a.key1 > b.key1) return false;
else return a.key2 < b.key2;
}
我可以使用 set::find 在拥有 Thing 的情况下,以 O(log N) 时间找到元素。使用 operator<() 的额外重载,我可以使用 std::lower_bound 或 std::equal_range 进行只有 key1 或同时拥有 key1 和 key2 的搜索。 例如:
struct Ordering
{
/// A strict weak ordering not a total ordering
bool operator()(const Thing& A,const std::string& key1) const;
}
const_iterator iter = std::lower_bound(someThings.begin(),
someThings.end(),
key1,
Ordering());
为了让这个更具体化,想象一下key1是名称,key2是版本。我可以问是否有名为Foobar的软件,或者更具体地说,我们是否有Foobar v1.0。
乍一看,在Java中最直接的std :: set等价物似乎是TreeSet。 可以通过继承Comparator接口来实现排序。 然而,对于我所说的内容,似乎需要使用多个Maps在Java中实现此操作。 在C ++中,如果我想改变值,才会使用诸如std :: map之类的关联容器。 在C++中,像在Java TreeSet中一样,值是其自身的键。但是,我可以编写比较器,使用key1或key2适当地将“Thing”与“std :: string”进行比较,并在它们的std :: set中找到特定的thing。 在Java中,我觉得你必须使用Map才能实现这一点。 否则(因为Comparator只有一个类型参数),你会遇到混乱的情况:
public static class Order implements Comparator<Object>
{
@Override
@Constant
public int compare(Object a, Object b)
{
String aString;
String bString;
if (a instanceof String)
{
aString = (String)a;
}
else if (a instanceof Thing)
{
aString = ((Field)a).getKey1();
}
else
{
throw new ClassCastException("String or Field object expected.");
}
if (b instanceof String)
{
bString = (String)b;
}
else if (b instanceof Thing)
{
bString = ((Field)b).getKey1();
}
else
{
throw new ClassCastException("String or Field object expected.");
}
return aString.compareTo(bString);
}
};
然而,如果您这样做,您可以在Thing类中编写以下内容:
Set<Thing> things = new TreeSet<Thing>(new Order());
boolean hasFieldWithKey1(final String key1)
{
return this.fields.contains(key1);
}
使用Java Set,您只能测试对象是否存在,但无法检索正在搜索的对象。例如,您无法执行以下操作
Field getFieldWithKey1(final String key1)
{
return this.fields.floor(key1);
}
因为像floor()这样的方法只接受值类型(即Thing)的对象。
明显的解决方案是为每个键使用一个Map。
Map<String,Thing> thingsByKey1 = new TreeMap<Thing>(new Order());
作为一个有C++背景的人来说,这似乎是不必要的臃肿。为什么我需要再次存储键,当事情已经包含它了?如果我有两个键,情况会更糟。我将需要两个映射。
Map<String,Thing> thingsByKey1 = new TreeMap<Thing>(new OrderByKey1());
Map<String,Thing> thingsByKey2 = new TreeMap<Thing>(new OrderByKey2());
我现在不仅复制关键字,还创建了额外的不必要的树形数据结构(或具有更好运行时性能的HashMap)。对于上述排序实现,这可能也是“完全错误”的,因为每个关键字本身只形成了部分顺序而不是一组物品的完全顺序。
我看到这里有关搜索的问题被回答为使用线性搜索,这几乎总是最糟糕的选择。例如: 在集合中查找具有给定属性的所有对象 我注意到有一个BinarySearch版本接受一个比较器对象作为参数,但返回元素的索引而不是元素本身。这意味着在使用它之后需要不必要地调用get()(假设集合支持它)。
那么Java的高效时间和空间方式是什么?
std::set
在O(log N)时间内搜索不同的键。std::set
只使用1个比较器类,并且仅比较集合的value_type
,因此在使用find()
、lower_bound()
或upper_bound()
时,额外的重载是无用的。如果你要求助于std::find()
,那么你就会陷入线性搜索。 - Dave Sstd::find
是浪费时间的行为。std::set
的时间复杂度是O(log N)
,而在集合上使用std::find
的时间复杂度将会是O(N)
(即使比较次数仍然是O(log N)
)。 - David Rodríguez - dribeasa.key1 > b.key1
,则您的operator<
实现会导致未定义行为,因为它没有为该情况返回值。 - fredoverflow