【问题标题】:how to speed up my ArrayList searching?如何加快我的 ArrayList 搜索?
【发布时间】:2011-11-26 00:46:34
【问题描述】:

我目前有一个 ArrayList 持有我创建的类的对象,然后我在 for loop 中解析 ArrayList 搜索并比较来自 ArrayList 和一些全局 variables 的一些数据加载到其他地方,但是这个ArrayList 不断增长,最终将有大约 115 个元素到最后,这需要很长时间才能搜索,执行此操作的函数也会为我阅读的每一行调用一次来自文本文件,文本文件的长度通常约为 400-500 行,因此您可以看出即使在小文件上进行测试也是非常缓慢的过程。有没有办法通过使用另一个collection 而不是ArrayList 来加快速度,我使用ArrayList 的原因是我必须知道它在找到匹配项时所在的索引。

这是类:

private ArrayList<PanelData> panelArray = new ArrayList<PanelData>(1);

    public class PanelData {
        String dev = "";
        String inst = "";
        double tempStart = 0.0;
        double tempEnd = 0.0;
    }

功能:

public void panelTimeHandler (double timeStart, double timeEnd) throws SQLException {   
        PanelData temps = new PanelData();
        temps.dev = devIDStr;
        temps.inst = instanceStr;
        temps.tempStart = timeStart;
        temps.tempEnd = timeEnd;
        boolean flag = false;

        if(!flag)
        {
            panelArray.add(temps);
            flag = true;
        }

        for(int i = 0; i < panelArray.size(); ++i ) {
            if(panelArray.get(i).dev.equals(devIDStr) && panelArray.get(i).inst.equals(instanceStr)) {
                if(panelArray.get(i).tempStart <= timeStart  && panelArray.get(i).tempEnd >= timeEnd ) {
                    //Do Nothing
                }
                else 
                {
                    temps.dev = devIDStr;
                    temps.inst = instanceStr;
                    temps.tempStart = timeStart;
                    temps.tempEnd = timeEnd;
                    insert();
                    panelArray.set(i, temps);
                }
            }
            else
            {
                temps.dev = devIDStr;
                temps.inst = instanceStr;
                temps.tempStart = timeStart;
                temps.tempEnd = timeEnd;
                panelArray.add(temps);
                insert();
            }
        }
    }

如果您还有什么想看的,尽管问,谢谢。牛肉。

更新:添加了 insert() 函数

private void insert() throws SQLException
{
    stmt = conn.createStatement();  

    String sqlStm = "update ARRAY_BAC_SCH_Schedule set SCHEDULE_TIME = {t '" + finalEnd + "'} WHERE SCHEDULE_TIME >=  {t '" + finalStart + "'} AND" +
        " SCHEDULE_TIME <=  {t '" + finalEnd + "'} AND VALUE_ENUM = 0 AND DEV_ID = " + devIDStr + " and INSTANCE = " + instanceStr;
    int updateSuccess = stmt.executeUpdate(sqlStm);

    if (updateSuccess < 1)
    {   
        sqlStm = "insert into ARRAY_BAC_SCH_Schedule (SITE_ID, DEV_ID, INSTANCE, DAY, SCHEDULE_TIME, VALUE_ENUM, Value_Type) " +
                " values (1, " + devIDStr + ", " + instanceStr + ", " + day + ", {t '" + finalStart + "'}, 1, 'Unsupported')";
        stmt.executeUpdate(sqlStm);
        sqlStm = "insert into ARRAY_BAC_SCH_Schedule (SITE_ID, DEV_ID, INSTANCE, DAY, SCHEDULE_TIME, VALUE_ENUM, Value_Type) " +
                " values (1," + devIDStr + ", " + instanceStr + ", " + day + ", {t '" + finalEnd + "'}, 0, 'Unsupported')";
        stmt.executeUpdate(sqlStm);
    }
    if(stmt!=null)
        stmt.close();
}

更新:

感谢 Matteo,我意识到我正在添加到数组中,即使我直到第 10 个元素才找到匹配项,然后它会在前 9 次添加到数组中,这在数组中创建了许多额外的元素,这就是为什么它太慢了,我添加了一些中断并对功能进行了一些调整,它大大提高了性能。谢谢大家的意见

【问题讨论】:

  • 您确定此代码有效吗?如果您有三个元素,并且只有第二个与您的 devIDStr 和 instanceStr 匹配,那么在我看来,您的结果有四个元素,第二个和最后一个指向同一个对象。
  • @Matteo 我实际上不确定panelTimeHandler 是否像我希望的那样工作,因为它太慢而无法找到,但我在这个程序的不同版本中做了一个类似的功能,它按计划工作所以我想一旦我让它完全运行,我可以调整它来工作
  • 我越看代码越觉得它有一些错误......如果panelArray包含3个元素,第二个匹配devIDStrinstanceStr,但条件@ 987654336@false,函数的执行是什么?由于!flag 为真,因此执行第一个 if 并将临时值添加到列表的末尾(因此列表中有 4 个元素)。然后for循环执行6次...(下一条评论)
  • @Matteo 好吧,你只是指出了一个我没有注意到的错误,我从来没有打算在函数中声明标志,因为我不希望每次调用函数时它都是 false,它的唯一目的是知道什么时候第一次调用该函数
  • 第一次元素不匹配devinst,因此最后一个else被执行,并在列表末尾添加了tems(panelArray包含5个元素)。第二次元素匹配并采用第一个else 分支:结果第 i 个元素用 temps 重新设置(panelArray 仍然包含 5 个元素)。第三次元素不匹配,因此在列表末尾再次添加临时值(panelArray 仍然包含 6 个元素)。元素匹配的第四次、第五次和第六次,但没有任何反应(采用 //Do nothing 分支)。

标签: java performance collections arraylist


【解决方案1】:

您可以使用LinkedHashSet。似乎您只将元素添加到列表的末尾,这正是 LinkedHashSet 在插入元素时所做的。
但是请注意,LinkedHashSet 不允许重复,因为它是一个集合。
使用contains()搜索元素是否存在将是O(1)

使用LinkedHashSet 还可以让您跟踪元素的添加位置,并按照插入顺序对其进行迭代。

【讨论】:

  • 好的,谢谢,我不认为我会有任何重复,因为进入其中的每个对象都会有一个唯一的 devIDStrinstanceStr
  • LinkedHashSet 是否随着我添加元素而增长,我可以将其初始容量声明为 1,然后让它根据需要增长
  • @Beef:LinkedHashSet 正在快速增长。默认构造函数会创建一个大小为 16 的空 LinkedHashSet,当平衡因子过高时,它会自动增加集合的大小。
【解决方案2】:

使用hashmap 怎么样?

我会为密钥创建一个小类:

class Key {
  String dev, instr;

  // todo: implements equals & hashCode
}

并创建地图:

Map<Key, PanelData> map = new HashMap...

然后您可以通过调用map.get(new Key(...)) 轻松找到您需要的元素。

除了创建一个新类之外,您还可以调整 PanelData 类,实现方法 equals 和 hashcode 以便两个类在它们的 devinstr 相等时相等。在这种情况下,您的地图变为:

Map<PanelData, PanelData> map ...

// to add:
map.put(temps, temps)

// to search:
PanelData elem = map.get(new PanelData(desiredDev, desiredInstr));

【讨论】:

  • 好的,您可以使用单独的列表以正确的顺序迭代您的元素......或者按照您的建议使用 LinkedHashMap! :)
  • @Matteo 我考虑使用HashMap 但是如果第一个if 匹配,我只想比较tempStarttempEnd,它们来自与前面使用的相同元素if 并且我不知道如何在使用 HashMap 时访问该特定元素,也使用 HashMap 我不知道如何访问该类中的特定数据。
【解决方案3】:

这里有很多优化。

1) 调用:panelArray.get(i) 被重复使用。在循环外声明一个 PanelData 变量,但只在循环开始时初始化一次:

PanelData pd = null;
for (int i = 0; i < panelArray.size(); ++i) {
    pd = panelArray.get(i);

    ...
}

2) 如果您的数据集允许,请考虑使用一些地图来帮助加快查找时间:

HashMap<String, PanelData> devToPanelDataMapping = new HashMap<String,PanelData>();
HashMap<String, PanelData> instToPanelDataMapping = new HashMap<String,PanelData>();

3) 考虑将字符串散列为整数或长整数,因为 String.equals() 与 (int == int) 相比速度较慢

4) 如果 ArrayList 是只读的,也许多线程解决方案可能会有所帮助。从文本文件中读取行的线程可以将单独的数据行分发给不同的“工作”线程。

【讨论】:

  • 密钥由两个字符串组成...在这种情况下,您不允许具有相同 dev 但不同 instr 的 PanelData!
【解决方案4】:

1) 首次创建 PanelArray 时,它的最大预期大小 + 10%。 List&lt;PanelData&gt; panelArray = new ArrayList&lt;PanelData&gt;(130) - 这将防止数组的动态重新分配,从而节省处理时间。

2) insert() 做什么?很可能那是你的资源猪。

【讨论】:

  • insert() 是一个使用从文本文件加载的全局变量在数据库上执行 sql 插入的函数,我不希望它太快,因为我知道插入速度很慢在某种程度上它下降了
  • 好的,您正在循环内插入数据库 - 经典的资源消耗案例。考虑批处理您的语句,而不是一次插入单行。
  • 我在循环中执行此操作的原因是因为每次从文本文件中读取一行时全局变量都会更改,并且我必须使用新的全局变量为每一行执行插入
  • 这并没有改变这样一个事实,即您可以批处理操作,直到您处理完文件,然后执行 + 提交所有插入作为循环外的一个事务。
  • 我要更新问题并在其中添加我的插入功能
【解决方案5】:

这个问题最好用不同的数据结构来解决,例如HashMapSortedSet

为了使用HashMap,您需要定义一个可以为devinst 字符串对生成哈希码的类。一种解决方案是这样的:

public class DevAndInstPair
{
    private String dev, inst;

    @Override
    public int hashCode() {
        return ((dev.hashCode() * 0x490aac18) ^ inst.hashCode());
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof DevAndInstPair)) {
            return false;
        }
        DevAndInstPair other = (DevAndInstPair) o;
        return (dev.equals(other.dev) && inst.equals(other.inst));
    }
}

然后您将使用HashMap&lt;DevAndInstPair, PanelData&gt; 作为地图类型。

或者,如果您知道某个字符从未出现在dev 字符串中,那么您可以使用该字符作为分隔dev 值和inst 值的分隔符。假设这个字符是一个连字符('-'),键值是dest + '-' + inst,映射的键类型是String

要使用SortedSet,您要么让PanelData 实现Comparable&lt;PanelData&gt;,要么编写一个实现Comparator&lt;PanelData&gt; 的类。记住比较操作必须和equals一致。

SortedSetHashMap 使用起来有点棘手,但我个人认为它是解决这个问题的更优雅的解决方案。

【讨论】:

  • SortedSet 未根据插入顺序排序 [这似乎是 OP 想要的]。而且 HashMap 根本没有排序!
  • @amit:维护插入顺序和/或排序集合似乎没有必要。在panelTimeHandler() 的实现中,@Beef 在ArrayList 中搜索具有相同devinst 字符串的PanelData 实例。如果 Beef 只需要通过这两个字段查找 PanelData 实例的能力,那么 HashMapSortedSet 都适合该问题。
猜你喜欢
  • 2016-02-21
  • 2018-10-02
  • 2022-06-22
  • 1970-01-01
  • 1970-01-01
  • 2016-03-10
  • 2018-08-07
  • 1970-01-01
  • 2012-05-01
相关资源
最近更新 更多