我有一种情况,即我用“ArrayList
”填充TransactionEvent
。 TransactionEvent
有一个属性“事务ID”。在大多数情况下,每个新事件的事务ID都大于先前事件的ID - 但是,这不能保证;即数据几乎已分类。
我的问题是:如何根据交易ID执行快速查询?我目前的想法是调用Collections.binarySearch(...)
,如果失败则执行线性搜索。但是,我注意到Javadoc声明binarySearch的结果是未定义的,因为数据是无序的,所以我可能不得不滚动自己的实现。
其他:
List
包含大量行(100,000)时,TableModel
是当前执行速度非常慢的基础。任何帮助表示感谢。
答案 0 :(得分:3)
使用LinkedHashMap,它结合了散列访问的双链表,您应该能够像使用ArrayList一样与TableModel连接,但也可以通过TransactionID上的散列查找来访问这些条目。
您甚至可以根据密钥替换(例如更新),而不会影响迭代顺序。
答案 1 :(得分:3)
您可以在添加每个TransactionEvent
时通过搜索插入点来对ArrayList进行排序。 Collections.binarySearch返回
搜索关键字的索引,如果它包含在列表中;否则,( - (插入点) - 1)。插入点定义为键将插入列表的点:第一个元素的索引大于键,或list.size(),如果列表中的所有元素都小于指定的键。请注意,当且仅当找到密钥时,这可以保证返回值>> =。
搜索插入点后,您可以使用ArrayList add(int index, Object element)方法,而不是像平常一样添加到列表末尾。这会使每个插入速度减慢一小部分,但它可以让您使用二进制搜索来快速查找。
答案 2 :(得分:1)
ArrayList适用于玩具大小的问题。 100.000行从玩具空间中获得了一点点。这意味着您必须更准确地了解需要支持的访问模式。排序的ArrayList可能已足够,如果处理速度增长的速度超过问题大小,您可能不想打扰,但BTree在100K元素上的速度会更快。
ArrayList在问题规模较大时存在以下问题:
具有固定页面大小(例如BTree)的两级集合可以提供帮助,因为增长意味着添加(理想情况下)大约sqrt(大小)页面,随机插入将最多将一页分成两页。
有两个必需的排序顺序,你可以简单地使用两个(排序的)BTree
[编辑] 早期问题的答案是问题的关键。对于1000个元素的ArrayList,插入成本为7微秒,1000000个元素为7毫秒。 BTree保持在微秒范围内(但对于1000个元素的页面大小,可能是两倍慢)。
您可以通过保留每个页面中元素数量的索引来创建索引访问。 如果在每个页面上设置脏标志,则可以使用后台线程更新每个页面的起始索引,或者可以使用延迟索引构建添加批量操作。
索引可能无效,但只是sqrt(size)大。对于100K元素,它平均只增加150个索引。这需要微秒,而不是毫秒
答案 3 :(得分:0)
从你所说的,看起来快速查看是最重要的事情。
所以也许您应该使用HashMap而不是ArrayList。在HashMap中,使用TransactionID作为Key存储TransactionEvents。 HashMap中的查找是O(1)。
请注意,如果超过其初始容量,添加到HashMap会变得非常慢 - 因为它必须进行重新哈希。如果可以的话,尝试使用最佳猜测(高位错误)初始化它,如果它将保留的数字。
对于100k行,您可能必须增加Java堆大小以防止OutOfMemoryErrors。
java -Xms<initial heap size> -Xmx<maximum heap size>
默认值为:
java -Xms32m -Xmx128m
修改强>
如果订购真的很重要,您可以使用SortedMap。
答案 4 :(得分:0)
您可以对列表进行排序。如果您在添加项目时插入排序,并且要添加的项目几乎已排序,则插入仍将有效地运行恒定时间。这将允许您以对数时间进行二进制搜索。
答案 5 :(得分:0)
我会使用二进制搜索来获取id的大致位置,然后线性向外搜索。不利的一面是,如果你要搜索的id不在列表中,那么它将需要O(n + log n)。
二进制搜索非常容易实现,我建议您阅读维基百科article。
答案 6 :(得分:0)
我遇到了同样的问题。我想出的解决方案是基于ArrayList的自定义集合,它也包含了所有元素的Map。 这并不难。如果您希望我发布源代码 - 请告诉我
答案 7 :(得分:0)
我的投票是您按顺序插入列表。然后你可以进行二进制搜索。几点说明:
答案 8 :(得分:0)
为什么不使用已排序的集合作为表模型而不是列表。 TreeMap似乎合乎逻辑,因为您的条目都是有序的。如果还需要按行或任何其他列快速访问,则只需添加辅助映射即可。基本上你正在做数据库索引的工作。
我认为由于某种原因你可以使用map.headSet(key)并找到第k个条目 - 这不起作用。你需要能够从表格行 - &gt; EventID(或接近它)。
如果你使用这样的模型
Map<EventID, Event> model = new TreeSet<EventID, Event>();
从概念上讲,你的getValueAt()看起来像这样:
getValueAt(int row, column) {
eventID = getSortPosition(row);
Event e = model.headSet(eventID).next();
return getColumn(e, column);
}
关键是能够有效地维护排序索引中的地图 - &gt;键(反向映射)。这是非繁琐的,因为在最顶层插入一个新事件会影响它下面所有人的绝对顺序。似乎这里应该有一个CS答案,但它逃脱了我。
这是最基本的实现: - 在每个插页上,您更新地图,然后实现您的有序地图。
ArrayList<Event> orderedEvents = new ArrayList<Event>();
public void insert(Event event) {
model.put(event.getID(), event);
// update the
model.headSet().addAll(orderedEvents);
}
你的getValueAt()非常简单。
getValueAt(int row, column) {w);
Event e = orderedEvents.get(row);
return getColumn(e, column);
}
我认为你应该重新考虑你的UI设计 如果您让用户浏览100K行表,添加搜索过滤器将解决您的性能问题:
答案 9 :(得分:0)
我的第一个答案并不是你真正想要的。现在我更好地理解了这个问题,试一试。我只实现了关键部分。这将占用更多内存,但由于我非常确定ArrayList存储引用,而不是对象本身,因此与实际对象存储相比,内存差异不应太大。
class TransactionEventStore
{
private ArrayList<TransactionEvent> byOrder, byId;
private void insertByOrder(TransactionEvent e) { this.byOrder.add(e); }
private void insertById(TransactionEvent e)
{
for(int i = this.byId.length() - 1; i > 0; i--)
if(e.getId() > this.byId.get(i).getId())
{
this.byId.add(i,e);
break;
}
}
public void insert(TransactionEvent e)
{
this.insertByOrder(e);
this.insertById(e);
}
}
现在,当您需要按广告订单查询时,请查看this.byOrder
,当您需要按ID查询时,请查看this.byId
。
答案 10 :(得分:0)
我从之前的帖子中清理了一些东西。 @Lizzard,您的解决方案最好是新条目通常在最后。如果以随机地图为内存的随机到达,下面的解决方案应该会更好。它还允许您推迟数组插入(可能是O(n)最坏的情况),直到您确实需要在最早的插入点下方绘制一行的单元格。
// sorted events (using natural ordering on eventID)
SortedSet<Event> model = new TreeSet<Event>();
ArrayList<Event> sortedList = new ArrayList<Event>();
Event lowestAddition, additionPrevEntry; // low water mark for insertions
public void insert(Event x) {
if (x < lowestAddition) {
Set<Event> headSet = model.headSet(x); // find the insertion point
additionPrevEntry = headSet.isEmpty()?model.last():headSet.first();
lowestAddition = x;
}
model.add(x); // add
}
public void materialize() {
SortedSet<Event> tailSet = model.tailSet(additionPrevEntry);
Event firstValue = tailSet.first(); // this element does not change its order
Integer order = firstValue.getOrder(); // keep order on Event
for (Event x : tailSet) {
x.setOrder(order);
sortedList.set(order, x);
order++;
}
lowestAddition = null; additionPrevEntry = null;
}
以下是您的swing代码的样子,我假设您使用的是Swing,因为您需要一个表模型:
// now your model code uses the array
public Object getValueAt(int row, int col) {
return getColumn(sortedList.elementAt(row), col);
}
// you can gain significant performance by deferring
// materialization until you acutally need it
public class DeferredJTable extends JTable {
public void paintComponent(Graphics G, ...) {
// if you knew what rows in the table were being drawn
// ahead of time, you could further defer
materialize();
super.paintComponent();
}
}