如何加快 Android 应用程序从资产加载 140,000 项 ArrayList
How to speed up Android app loading 140,000-item ArrayList from an Asset
我有一个 Java 程序,我刚刚把它变成了一个 slooooooow-to-load Android 应用程序。问题:它处理一个 140,000 个单词 "dictionary"(存储在 Asset
文件中),在其中查找匹配 "Windows wildcard" 模式的单词:例如,S???CK* 会匹配STICKS、SHACK、STACK、Whosebug 等。在 Windows 7 中速度非常快。在 phone 上不是这样。
我做过的一件事是将所有 140,000 个单词读入 ArrayList
(令我震惊的是它编译了 运行),之后,只要模式不以通配符开头,Collections.binarySearch(...)
几乎可以立即进行查找。
但是将其读入数组列表需要 60 秒,并且用户输入被阻止。每次 onCreate
必须是 运行 时都会发生这种情况——也就是说,经常发生。
我想加快速度。
这里有一个 SSCCE
完美但太慢的方法:
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction
ft;
ft = getFragmentManager().beginTransaction();
ft.replace(R.id.layout_container, new OutputFragment());
ft.commit();
};
}
OutputFragment.java
public class OutputFragment extends Fragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater _layoutInflater,
ViewGroup _sourceOfLayoutParams,
Bundle savedInstanceState)
{
View v = _layoutInflater.inflate(R.layout.fragment_output,_sourceOfLayoutParams, false);
EditText et = (EditText)v.findViewById(R.id.txaOutput);
Matcher matcher = new Matcher(getActivity().getAssets());
for (int i = 0; i < 9; i++)
et.append("\n" + matcher.get(i));
return v;
}
}
Matcher.java
public class Matcher extends ArrayList<String> {
Matcher(AssetManager assets) {
Scanner scDict = null;
try { scDict = new Scanner(assets.open("dictionary.dic")); }
catch (IOException e) { e.printStackTrace(); }
int k = 0;
while(scDict.hasNext())// && ++k<10)
add(scDict.next());
}
}
activity_main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent"
xmlns:tools ="http://schemas.android.com/tools"
tools:context =".MainActivity"
>
<LinearLayout
android:id ="@+id/layout_container"
android:orientation ="vertical"
android:layout_width ="match_parent"
android:layout_height="match_parent">
</LinearLayout>
</RelativeLayout>
fragment_output.xml
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height="match_parent"
android:rowCount="33"
android:columnCount="2">
<TextView
android:id ="@+id/txvOutput"
android:text ="Output shown below"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:textAppearance ="?android:attr/textAppearanceLarge"
android:layout_row="0"
android:layout_column="0">
</TextView>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/txaOutput"
android:layout_row="2"
android:inputType="textMultiLine"
android:layout_column="0"
android:maxLines="100"/>
</GridLayout>
所以我想要的是加快速度。我已阅读 "Keeping Your Android App Responsive"。我不知道我能不能让它适合我的情况。我从那里拿了例子并尽可能地调整它:
private class LoadWords extends AsyncTask<Scanner, Integer, Long>
{
@Override
protected Long doInBackground(Scanner... params) { // variable arg list required (??)
while(params[0].hasNext()) //// no way this could work...
add(params[0].next());
return 0L;
};
}
我没想到我一输入它就可以工作params[0].hasNext()
但是似乎需要一个可变参数列表。
以下是我尝试实现它的方法:
LoadWords loadWords = new LoadWords(); /////////////////////////
InputStream stream = null;
Scanner scDict = null;
...
stream = assets.open("dictionary.dic");
...
scDict = new Scanner(stream);
loadWords.execute(scDict); ///////////////////// What should I pass?????
我想我应该放弃这种方法并尝试使用我必须管理的 Thread
。我对此感到不自在。
欢迎就如何进行提出任何建议。
我有一个部分解决方案,使用 AsyncTask
。加载 140,000 个单词发生在后台; GUI 立即响应,但并非所有单词都及时加载以返回所有匹配项。
public class ListMaker extends ArrayList<String>
{
Scanner scDict;
InputStream stream = null;
public Matcher(AssetManager assets)
{
LoadWords loadWords = new LoadWords();
stream = assets.open("dictionary.dic");
loadWords.execute((Object[]) null);
}
private class LoadWords extends AsyncTask<Object, Integer, ArrayList<String>> {
@Override
protected ArrayList<String> doInBackground(Object... params)
{
scDict = new Scanner(stream).useDelimiter("\r\n");
while (scDict.hasNext())
add(scDict.next());
return null;
}
@Override
protected void onPostExecute(ArrayList<String> result) {
MainActivity.setLoaded(true);
}
因为 Paulo Avelar 提到 index,我终于放弃了反对 Selvin 和 Ed George 的建议,转而使用 SQLite database
而不是将所有 140,000 个单词加载到内存中,这我从没想过这是个好主意,但它运作良好,足以让我开始。但是结果很差。
使用数据库,改进非常显着。
通过使用 "where word like ?"
(其中 "?"
是用户的模式)即时 对唯一的列进行索引(即,不过分)进行了通配符搜索。
将 140,000 个单词加载到数据库中需要一分钟(一次性任务,假设应用程序的数据未通过 Settings
清除),但它使用了 database.beginTransaction
和 endTransaction
这使得加载足够快。详情请.
我有一个 Java 程序,我刚刚把它变成了一个 slooooooow-to-load Android 应用程序。问题:它处理一个 140,000 个单词 "dictionary"(存储在 Asset
文件中),在其中查找匹配 "Windows wildcard" 模式的单词:例如,S???CK* 会匹配STICKS、SHACK、STACK、Whosebug 等。在 Windows 7 中速度非常快。在 phone 上不是这样。
我做过的一件事是将所有 140,000 个单词读入 ArrayList
(令我震惊的是它编译了 运行),之后,只要模式不以通配符开头,Collections.binarySearch(...)
几乎可以立即进行查找。
但是将其读入数组列表需要 60 秒,并且用户输入被阻止。每次 onCreate
必须是 运行 时都会发生这种情况——也就是说,经常发生。
我想加快速度。
这里有一个 SSCCE
完美但太慢的方法:
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction
ft;
ft = getFragmentManager().beginTransaction();
ft.replace(R.id.layout_container, new OutputFragment());
ft.commit();
};
}
OutputFragment.java
public class OutputFragment extends Fragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater _layoutInflater,
ViewGroup _sourceOfLayoutParams,
Bundle savedInstanceState)
{
View v = _layoutInflater.inflate(R.layout.fragment_output,_sourceOfLayoutParams, false);
EditText et = (EditText)v.findViewById(R.id.txaOutput);
Matcher matcher = new Matcher(getActivity().getAssets());
for (int i = 0; i < 9; i++)
et.append("\n" + matcher.get(i));
return v;
}
}
Matcher.java
public class Matcher extends ArrayList<String> {
Matcher(AssetManager assets) {
Scanner scDict = null;
try { scDict = new Scanner(assets.open("dictionary.dic")); }
catch (IOException e) { e.printStackTrace(); }
int k = 0;
while(scDict.hasNext())// && ++k<10)
add(scDict.next());
}
}
activity_main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent"
xmlns:tools ="http://schemas.android.com/tools"
tools:context =".MainActivity"
>
<LinearLayout
android:id ="@+id/layout_container"
android:orientation ="vertical"
android:layout_width ="match_parent"
android:layout_height="match_parent">
</LinearLayout>
</RelativeLayout>
fragment_output.xml
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height="match_parent"
android:rowCount="33"
android:columnCount="2">
<TextView
android:id ="@+id/txvOutput"
android:text ="Output shown below"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:textAppearance ="?android:attr/textAppearanceLarge"
android:layout_row="0"
android:layout_column="0">
</TextView>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/txaOutput"
android:layout_row="2"
android:inputType="textMultiLine"
android:layout_column="0"
android:maxLines="100"/>
</GridLayout>
所以我想要的是加快速度。我已阅读 "Keeping Your Android App Responsive"。我不知道我能不能让它适合我的情况。我从那里拿了例子并尽可能地调整它:
private class LoadWords extends AsyncTask<Scanner, Integer, Long>
{
@Override
protected Long doInBackground(Scanner... params) { // variable arg list required (??)
while(params[0].hasNext()) //// no way this could work...
add(params[0].next());
return 0L;
};
}
我没想到我一输入它就可以工作params[0].hasNext()
但是似乎需要一个可变参数列表。
以下是我尝试实现它的方法:
LoadWords loadWords = new LoadWords(); /////////////////////////
InputStream stream = null;
Scanner scDict = null;
...
stream = assets.open("dictionary.dic");
...
scDict = new Scanner(stream);
loadWords.execute(scDict); ///////////////////// What should I pass?????
我想我应该放弃这种方法并尝试使用我必须管理的 Thread
。我对此感到不自在。
欢迎就如何进行提出任何建议。
我有一个部分解决方案,使用 AsyncTask
。加载 140,000 个单词发生在后台; GUI 立即响应,但并非所有单词都及时加载以返回所有匹配项。
public class ListMaker extends ArrayList<String>
{
Scanner scDict;
InputStream stream = null;
public Matcher(AssetManager assets)
{
LoadWords loadWords = new LoadWords();
stream = assets.open("dictionary.dic");
loadWords.execute((Object[]) null);
}
private class LoadWords extends AsyncTask<Object, Integer, ArrayList<String>> {
@Override
protected ArrayList<String> doInBackground(Object... params)
{
scDict = new Scanner(stream).useDelimiter("\r\n");
while (scDict.hasNext())
add(scDict.next());
return null;
}
@Override
protected void onPostExecute(ArrayList<String> result) {
MainActivity.setLoaded(true);
}
因为 Paulo Avelar 提到 index,我终于放弃了反对 Selvin 和 Ed George 的建议,转而使用 SQLite database
而不是将所有 140,000 个单词加载到内存中,这我从没想过这是个好主意,但它运作良好,足以让我开始。但是结果很差。
使用数据库,改进非常显着。
通过使用 "where word like ?"
(其中 "?"
是用户的模式)即时 对唯一的列进行索引(即,不过分)进行了通配符搜索。
将 140,000 个单词加载到数据库中需要一分钟(一次性任务,假设应用程序的数据未通过 Settings
清除),但它使用了 database.beginTransaction
和 endTransaction
这使得加载足够快。详情请