Java 为什么';事件列表是否已被替换?(或者:替换它的陷阱是什么?)
我们的传统应用程序使用了一个糟糕的框架(好吧,我来点名吧,它是Tapestry 4),其中包含了大量的Java 为什么';事件列表是否已被替换?(或者:替换它的陷阱是什么?),java,swing,event-handling,Java,Swing,Event Handling,我们的传统应用程序使用了一个糟糕的框架(好吧,我来点名吧,它是Tapestry 4),其中包含了大量的事件监听器(~100000),用于最简单的操作。我猜这超出了javax.swing.event.EventListenerList要处理的范围,在这个不幸的用例中,它给我们带来了一些令人头痛的性能问题 我花了几个小时准备了下面基于HashMap/ArrayList的相当简单的替换,它几乎在各个方面都要快得多: 添加50000个侦听器: EventListenerList>2秒 EventLis
事件监听器(~100000),用于最简单的操作。我猜这超出了javax.swing.event.EventListenerList
要处理的范围,在这个不幸的用例中,它给我们带来了一些令人头痛的性能问题
我花了几个小时准备了下面基于HashMap/ArrayList的相当简单的替换,它几乎在各个方面都要快得多:
添加50000个侦听器:
>2秒EventListenerList
约3.5毫秒EventListenerMap
0.3-0.5毫秒EventListenerList
0.4-0.5毫秒EventListenerMap
>2秒EventListenerList
约280毫秒EventListenerMap
EventListenerList
更安全或性能更好
public class EventListenerMap
{
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private Map<Class, List> llMap = new HashMap<Class, List>();
public <L extends EventListener> void add ( Class<L> listenerClass, L listener )
{
try
{
writeLock.lock();
List<L> list = getListenerList( listenerClass );
if ( list == null )
{
list = new ArrayList<L>();
llMap.put( listenerClass, list );
}
list.add( listener );
}
finally
{
writeLock.unlock();
}
}
public <L extends EventListener> void remove ( Class<L> listenerClass, L listener )
{
try
{
writeLock.lock();
List<L> list = getListenerList( listenerClass );
if ( list != null )
{
list.remove( listener );
}
}
finally
{
writeLock.unlock();
}
}
@SuppressWarnings("unchecked")
public <L extends EventListener> L[] getListeners ( Class<L> listenerClass )
{
L[] copy = (L[]) Array.newInstance( listenerClass, 0 );
try
{
readLock.lock();
List<L> list = getListenerList( listenerClass );
if ( list != null )
{
copy = (L[]) list.toArray( copy );
}
}
finally
{
readLock.unlock();
}
return copy;
}
@SuppressWarnings("unchecked")
private <L extends EventListener> List<L> getListenerList ( Class<L> listenerClass )
{
return (List<L>) llMap.get( listenerClass );
}
}
公共类EventListenerMap
{
private final ReadWriteLock=new ReentrantReadWriteLock();
私有最终锁readLock=Lock.readLock();
private final Lock writeLock=Lock.writeLock();
私有映射llMap=newhashmap();
公共void添加(类listenerClass,L listener)
{
尝试
{
writeLock.lock();
List List=getListenerList(listenerClass);
if(list==null)
{
列表=新的ArrayList();
llMap.put(listenerClass,list);
}
添加(侦听器);
}
最后
{
writeLock.unlock();
}
}
公共无效删除(类listenerClass,L listener)
{
尝试
{
writeLock.lock();
List List=getListenerList(listenerClass);
如果(列表!=null)
{
删除(侦听器);
}
}
最后
{
writeLock.unlock();
}
}
@抑制警告(“未选中”)
公共L[]getListeners(类listenerClass)
{
L[]copy=(L[])Array.newInstance(listenerClass,0);
尝试
{
readLock.lock();
List List=getListenerList(listenerClass);
如果(列表!=null)
{
复制=(L[])列表。到阵列(复制);
}
}
最后
{
readLock.unlock();
}
返回副本;
}
@抑制警告(“未选中”)
私有列表getListenerList(类listenerClass)
{
return(List)llMap.get(listenerClass);
}
}
这是一个优化问题。Swing的EventListenerList假定:
- 列表上的侦听器数量非常少
- 监听器列表的数量可能非常大
- 添加/删除事件很少发生
EventListenerList
通过分配一个足够大的数组来容纳侦听器,从而使内存占用尽可能小。(,在添加第一个侦听器之前,它甚至不会分配任何内容,以确保没有侦听器时不会浪费空间。)其缺点是,每次添加新元素时,它都会重新分配数组并复制所有旧元素,当侦听器太多时,会给您带来天文数字的成本
(实际上,它的内存效率并没有尽可能高;列表是{Type,Listener}对,因此如果它是数组数组的数组,在某些情况下会稍微小一些。)
至于您的解决方案:HashMap
s过度分配内存以确保高效的哈希。同样,默认的ArrayList
构造函数为10个元素分配空间,并以块的形式增长。在一个奇怪的代码库中,每个列表上都有100k个侦听器,这个额外的内存只是用来保存所有侦听器的内存的一个小小的增加
但是,在较轻的侦听器负载下,您的实现需要为每个空列表花费16个指针(默认分配为HashMap
),为带有一个元素的EventListenerMap
花费26个指针,为带有两个不同类元素的映射花费36个指针。(这不包括HashMap
和ArrayList
结构大小的其余部分。)对于相同的情况,EventListenerList
分别花费0、2和4个指针
这似乎是对您的代码的巨大改进。+1本月的疯狂(iest)问题,Q about(对于最简单的操作)每个步骤都是独立的还是链接的,从树或信号量开始,如果是,那么另一个疯狂(iest)建议,使用EventHandler并在运行时以字符串形式构建路径,然后可能不需要在内存中保存一堆方法,phaaa我喜欢这个想法,来自你的问题,让RPG永远存在(然后是相同的疯狂无休止的代码),你在相对触发时间中有输入错误吗?您报告说您的实现可能会“慢一点”,0.4毫秒,而
EventListenerList
则是0.3秒