我目前正在开发各种统计数据的应用程序。 其中一项任务是分析大量句子的字数。
规范为:
- 从 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_array
和 regexp_split_to_table
,这使得可以保留在数据库上进行查询。我还没有找到在 SQLite
我花了很多时间试图找出不同的解决方案,但现在有点陷入困境。是否有任何(相对)快速的方法来执行所需的任务?我也欢迎提出建议,使字数统计尽可能合理。
当前版本以及一些建议的实现:
改进:
- 准备好的语句:速度提高约 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);
最佳答案
db.execSQL("insert into WORDS values ('" + words[i] + "', "
+ from_me + ")");
数据库访问过多。为每个单词都敲击 DB 并不顺利。由于重复的单词较多,您可以将它们计入 Multiset 中。当内存紧张或完成时,将其与计数一起存储。
为每个事件创建单独的行也是没有意义的。添加一列count
(最好以不同的方式调用它,因为“count”是一个关键字)。
使用准备好的语句。通过每次创建一个查询字符串,您可以强制数据库一次又一次地解析它。并且还为 GC 生成工作。
words[i] = words[i].replaceAll("[^a-zA-z]", "");
使用Pattern.compile或CharMatcher 。后者在没有特殊字符的常见情况下不会产生垃圾。
private final CharMatcher alpha = CharMatcher.inRange('A', 'Z')
.or(CharMatcher.inRange('a', 'z')).precomputed();
alpha.retainFrom(words[i]);
这应该有很大帮助,尤其是数据库方面的东西。尝试一下,如果还不够的话再来。
关于java - 创建一个快速的 Android 词典(字数统计),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25394873/