【问题标题】:creating a fast android dictionary (word counts)创建一个快速的安卓词典(字数)
【发布时间】:2014-08-20 00:04:31
【问题描述】:

我目前正在开发各种统计数据的应用程序。 一项任务是分析大量句子的字数。

规格为:

  • 从 SQLiteDatabase 读取句子(最多 20k,平均约 15 个单词)
  • 转换:用空格分割(得到句子的单词)
  • 转换:toLowerCase(尽量减少单词的变化)
  • 转换:替换 [^a-zA-Z](原因同上)
  • 获取前 x 个(尚不确定,可能是 10-15 个)最常用单词的单词 + 计数
  • 如果消息已发送/接收,则保留一个标志

这是我目前的做法:

db.execSQL("create temp table if not exists WORDS (WORD varchar, SENT integer)");
Cursor c1 = db.rawQuery("select lower(MSG) as SENTENCE, SENT from MESSAGELIST",null);
    while (c.moveToNext()) {
        String[] words = c.getString(c.getColumnIndex("SENTENCE")).split(
                "\\s+");
        int from_me = c.getInt(c.getColumnIndex("SENT"));
        for (int i = 0; i < words.length; i++) {
            words[i] = words[i].replaceAll("[^a-zA-z]", "");
            if (!words[i].equals("")) {
                db.execSQL("insert into WORDS values ('" + words[i] + "', "
                        + from_me + ")");
            }
        }
    }
    Cursor c2 = db.rawQuery(
            "select WORD, COUNT(*) as CNT from WORDS where SENT=0 group by WORD order by CNT desc limit 10",
            null);
    Cursor c3 = db.rawQuery(
            "select WORD, COUNT(*) as CNT from WORDS where SENT=1 group by WORD order by CNT desc limit 10",
            null);

我已经假设这段代码很慢。我猜字符串操作需要很多时间。

仅仅为了查询而从数据库中提取并重新输入也感觉不对。但是,我知道PostgreSQL 中有regexp_split_to_arrayregexp_split_to_table,这将使查询可以留在数据库中。我还没有在SQLite找到解决方案来做到这一点

我花了很多时间试图找出不同的解决方案,但我现在有点卡住了。是否有任何(相对)快速的方法来执行所需的任务?我也愿意接受建议,以使字数尽可能合理。

带有一些建议实现的当前版本:

改进:

  • Prepared Statements:快约 29%
  • 预编译正则表达式:快约 21%
  • 评论部分表明我的计数实现,但这种方法提高了运行时间(有和没有索引)
  • 通过事务批量插入:大约快 9%
  • 用于替换的 CharMatcher:快约 8%
  • 用于计数的 HashMultiset:快约 2%

    c = db.rawQuery("select lower(DATA) as SENTENCE, SENT from MESSAGELIST", null);
    
    CharMatcher pat_rep = CharMatcher.inRange('A', 'Z')
            .or(CharMatcher.inRange('a', 'z')).precomputed();
    Pattern pat_split = Pattern.compile("\\s");
    HashMultiset<String> sent = HashMultiset.create();
    HashMultiset<String> rcvd = HashMultiset.create();
    
    while (c.moveToNext()) {
        String[] words = pat_split.split(c.getString(c.getColumnIndex("SENTENCE")));
        int from_me = c.getInt(c.getColumnIndex("SENT"));
    
        for (int i = 0; i < words.length; i++) {
            words[i] = pat_rep.retainFrom(words[i]);
            if (!words[i].equals("")) {
                if (from_me == 1) {
                    sent.add(words[i]);
                } else {
                    rcvd.add(words[i]);
                }
            }
        }
    }
    db.execSQL("create temp table if not exists WORDS (WORD varchar, SENT integer, CNT integer)");
    SQLiteStatement ins = db.compileStatement("insert into WORDS values (?, ?, ?)");
    db.beginTransaction();
    
    Iterator<String> i = sent.iterator();
    while (i.hasNext()) {
        String in = i.next();
        ins.bindString(1, in);
        ins.bindLong(2, 1);
        ins.bindLong(3, sent.count(in));
        ins.executeInsert();
        ins.clearBindings();
    }
    i = rcvd.iterator();
    while (i.hasNext()) {
        String in = i.next();
        ins.bindString(1, in);
        ins.bindLong(2, 0);
        ins.bindLong(3, rcvd.count(in));
        ins.executeInsert();
        ins.clearBindings();
    }
    db.setTransactionSuccessful();
    db.endTransaction();
    c = db.rawQuery(
            "select WORD, CNT from WORDS where SENT=0 group by WORD order by CNT desc limit 10",
            null);
    Cursor c2 = db.rawQuery(
            "select WORD, CNT from WORDS where SENT=1 group by WORD order by CNT desc limit 10",
            null);
    

【问题讨论】:

  • 如果要进行复杂的统计计算,为什么这个数据库只存在于应用程序上?你能不把它发送到你的服务器吗?
  • @Falmarri:我希望应用程序脱机运行,因为没有服务器可以将其发送到(截至目前);)另外:除了使用服务器之外还有什么好处'表现?
  • 您必须在您的msgword 列上放置一些索引,尤其是当您的数据库中有很多记录时。

标签: java android performance sqlite dictionary


【解决方案1】:
db.execSQL("insert into WORDS values ('" + words[i] + "', "
                    + from_me + ")");

数据库访问过多。为每一个单词都打数据库并不顺利。由于重复的单词很多,您可以将它们数在Multiset 中,并在记忆紧张或完成时与它们的计数一起存储。

为每次出现创建单独的行也是没有意义的。添加一列count(最好以不同的方式称呼它,因为“count”是一个关键字)。

使用准备好的语句。通过每次创建查询字符串,您强制数据库一次又一次地解析它。并为 GC 制作作品。

words[i] = words[i].replaceAll("[^a-zA-z]", "");

使用Pattern.compileCharMatcher。后者在没有特殊字符的常见情况下不会产生垃圾。

private final CharMatcher alpha = CharMatcher.inRange('A', 'Z')
        .or(CharMatcher.inRange('a', 'z')).precomputed();

alpha.retainFrom(words[i]);

这应该会有很大帮助,尤其是数据库方面的东西。试试看,不够的话再来。

【讨论】:

  • 非常感谢您的输入,我实现了一些东西(请参阅更新的问题)并且已经获得了相当大的性能提升!我还没有尝试过 Guava 库的东西。
  • @MazePutze CharMatcher 是一个有用的东西,但是这个用例对于使用库来说不够重要。 OTOH Guava 非常棒,值得学习和使用。如果您需要更多改进,请随时发布后续问题。我想,CR 会更合适。不要忘记接受我的回答。
  • 我添加了库,并且 CharMatcher 和 HashMultiset 又得到了 10% 的改进,这证明了我猜想使用库是合理的。总的来说,我的执行速度提高了大约 70%,这已经是一个非常大的帮助。但是,如果有更多改进建议或更好的使用实现,例如hashmultiset 我很感激更多的输入。谢谢!
  • @MazePutze 如前所述,请随时在CR 上发布您改进的代码。当我们看到您的新代码时,您会得到更多直接的答案。
  • 做到了here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多