簡體   English   中英

如何解析大型JSON文件以獲取搜索欄建議

[英]How to go about parsing a large JSON file for search bar suggestions

我正在嘗試為天氣應用程序解析一個29mb的JSON,其中包含世界上的城市。

這是JSON結構:

[
  {
    "id": 707860,
    "name": "Hurzuf",
    "country": "UA",
    "coord": {
      "lon": 34.283333,
      "lat": 44.549999
    }
  },
  {
    "id": 519188,
    "name": "Novinki",
    "country": "RU",
    "coord": {
      "lon": 37.666668,
      "lat": 55.683334
    }
  },
  {
    "id": 1283378,
    "name": "Gorkhā",
    "country": "NP",
    "coord": {
      "lon": 84.633331,
      "lat": 28
    }
  },
...
]

我有一個Fragment,其中包含保存的城市列表,以及一個fab,用於提示帶有文本字段的警報對話框,用戶應在其中輸入城市名稱並從解析的數據中獲取建議。

我的問題是,解析該文件會占用大量內存,並且減慢了用戶XP的速度(即使該文件在其他線程上運行,該Fab也會被禁用,直到操作完成為止)。 每次用戶訪問城市片段都分析文件似乎很愚蠢,因此將結果對象保留在內存中,所以我想我的問題是我應該如何處理?

使用GSON像這樣的線程或文章建議是偉大的,但它並沒有解決問題的重復性。

public static List<City> getCitiesFromJSON(Context context){
    List<City> cityList = new LinkedList<>();
    try{
        InputStream is = context.getAssets().open("jsons/city.list.min.json");
        JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));

        reader.beginArray();

        Gson gson = new GsonBuilder().create();

        while (reader.hasNext()){
            City cityJson = gson.fromJson(reader, City.class);
            City city = new City();
            city.setId(cityJson.getId());
            city.setName(cityJson.getName());
            city.setCountry(cityJson.getCountry());
            city.setCoord(new Coord(cityJson.getCoord().getLon(),cityJson.getCoord().getLat()));
            cityList.add(city);
        }
        reader.close();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return cityList;
}

tl; dr

每次用戶嘗試將其添加到列表時,都需要解析大JSON以獲取搜索建議,但是該操作既耗費內存又耗時,因此我需要一種更好的方法。

我認為在您的情況下,將城市加載到內存(Singleton Object)中比較好,或者如果目標客戶端資源/內存將受到影響,那么您將需要一台遠程服務器來執行搜索引擎(例如ElasticSearch)或其余的應用程序將進行處理和緩存

另外,緩存搜索請求將有助於防止重復處理。

方法1 :更好的方法是在應用程序啟動后立即在后台請求JSON並將其保存到本地存儲(SQlite,Room Persistence或只是sharedpreference)。

方法2:第二個技巧是僅顯示足夠大的數字,而不顯示大數字。 假設用戶有1000個匹配用戶輸入字符串的結果。 只需查詢本地或遠程數據庫以顯示最匹配的100。沒有理智的人可以滾動瀏覽100個結果,而無需再次修改/寫入其輸入文本。

最佳方法:使用分頁庫在用戶向下滾動時加載數據。

這是我解決此問題的方法:

  • 使用Room來處理數據庫操作:

城市實體:

@Entity
public class City {
    @PrimaryKey
    private long id;
    private String name;
    private String country;
    @Embedded
    private Coord coord;

    public City(){

    }

    public City(JSONObject json){
        this.id = json.optLong("id");
        this.name = json.optString("name");
        this.country = json.optString("country");
        this.coord = new Coord(json.optJSONObject("coord"));
    }

    public City(long id, String name, String country, Coord coord) {
        this.id = id;
        this.name = name;
        this.country = country;
        this.coord = coord;
    }

    // getters and setters ...
}

市DAO:


@Dao
public interface CityDao {
    @Query("SELECT * FROM city")
    List<City> getAll();

    // Used to query search suggestions
    @Query("SELECT * FROM city WHERE name LIKE :name || '%' ORDER BY name ASC LIMIT 10")
    City findByName(String name);

    // Used to determine whether or not the db was created
    @Query("SELECT COUNT(*) FROM city")
    int getCitySize();

    @Insert
    void insertAll(City... cities);

    @Insert
    void insertCity(City city);

    @Delete
    void deleteCity(City city);

    @Query("DELETE FROM city")
    public void nukeCities();
}

AppDatabase:

@Database(entities = {City.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract CityDao cityDao();
}
  • 在Application類中,我實現了一個帶有回調的Runnable,該回調在解析JSON並填充db后啟動CountDownLatch的倒計時(由AddCityFragment用於啟用fab)

應用:

public class App extends Application implements RunnableCompleteListener {
    private final CountDownLatch startLatch = new CountDownLatch(1);
    private AppDatabase appDB;

    @Override
    public void onCreate() {
        super.onCreate();

        appDB = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "cities-db").build();

        NotifyingRunnable readCitiesJSON = new NotifyingRunnable() {
            List<City> citiesList = new ArrayList<>();

            @Override
            public void doRun() {
                int dbCitySize = appDB.cityDao().getCitySize();
                // if db is empty
                if (dbCitySize == 0){
                    citiesList = HelperFunctions.getCitiesFromJSON(getApplicationContext());
                    appDB.cityDao().insertAll(citiesList.toArray(new City[citiesList.size()]));
                }
            }
        };
        readCitiesJSON.addListener(this);
        Thread worker = new Thread(readCitiesJSON);
        worker.start();
    }

    public AppDatabase getDatabase() {
        return appDB;
    }

    public CountDownLatch getStartLatch() {
        return startLatch;
    }

    @Override
    public void notifyOfRunnableComplete(Runnable runnable) {
        startLatch.countDown();
    }
}

可運行的實現:

public interface RunnableCompleteListener {
    void notifyOfRunnableComplete(final Runnable runnable);
}

public abstract class NotifyingRunnable implements Runnable {
    private final Set<RunnableCompleteListener> listeners = new CopyOnWriteArraySet<RunnableCompleteListener>();

    public final void addListener(final RunnableCompleteListener listener){
        listeners.add(listener);
    }

    public final void removeListener(final RunnableCompleteListener listener){
        listeners.remove(listener);
    }

    private final void notifyListeners(){
        for(RunnableCompleteListener listener : listeners){
            listener.notifyOfRunnableComplete(this);
        }
    }

    @Override
    public final void run() {
        try{
            doRun();
        }finally {
            notifyListeners();
        }
    }

    public abstract void doRun();
}

  • 更改了getCitiesFromJSON以將json視為對象,不需要流訪問,因為我將為此使用數據庫

    public static List<City> getCitiesFromJSON(Context context){
        String json = "";
        try {
            InputStream is = context.getAssets().open("jsons/city.list.min.json");
            int size = is.available();

            // read the entire asset into a local buffer
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            json = new String(buffer, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<City> citiesList = new ArrayList<City>();
        Type listType = new TypeToken<ArrayList<City>>() {}.getType();

        // convert json into a list of cities
        try {
            citiesList = new Gson().fromJson(json, listType);
        }catch (Exception e){
            Log.e("Error parsing", e.toString());
        }
        return citiesList;
    }
  • 最后,在片段的onCreateView內,一個線程等待閂鎖遞減計數以啟用fab
 @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ...

        fabAddCity = view.findViewById(R.id.fabAddCity);
        fabAddCity.setOnClickListener(this);
        // disabled by default
        fabAddCity.setEnabled(false);
        fabAddCity.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
        // the thread that controls the fab state
        Thread fabController = new Thread(() -> {
            try {
                ((App)getActivity().getApplication()).getStartLatch().await();
                getActivity().runOnUiThread(() -> {
                    fabAddCity.setEnabled(true);
                    fabAddCity.getBackground().setColorFilter(null);
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        fabController.start();
        ...
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM