С сайта безопасныедороги.рф необходимо из открытых данных в виде JSON вытащить информацию о геолокациях каждого дтп: em_place_latitude и em_place_longitude. Сам файл весит 1Гб и просто когда подключаешь файл - то выскакивает ошибка
GC overhead limit exceeded
Я работал с библиотекой simple-json в Eclipse
Так как я только начал знакомство с JSON, то вообще не понимаю как вытаскивать оттуда информацию, тем более из огромного файла.
Спасибо!
Ответ
На самом деле всё достаточно просто, если не загружать всё содержимое документа в память. Нужно, в принципе, помнить два основных подхода в разборе документов различных форматов: читается всё с помощью потока (InputStream/Reader или их аналоги) и решается как будет обрабатываться его содержимое: или собиранием всего в память (аналог XML DOM, когда документ, будучи полностью в памяти, позволяет с лёгкостью обходить его дерево), или же работать согласно событийной модели (аналог XML SAX или его pull-аналоги, когда парсер сообщает о том, какие токены встречаются в документе, а пользователь уже сам решает что и как с ними делать). Очевидно, что первый вариант проще в использовании, но требует куда больше ресурсов, в то время как второй вариант сложнее, но и более гибок, зачастую требуя вообще минимум ресурсов.
Gson позволяет работать с JSON-потоками с помощью pull-метода: это когда парсер не генерирует события, а предполагает, что пользователь сам знает, что и в каком порядке нужно "тянуть" с потока токенов, но и требуя чтобы каждый токен был обработан хоть как-то. Например:
private static final String ITEMS_NAME = "items";
// em_place_latitude не описан в схеме и не встречается в документе
private static final String LATITUDE_PROPERTY = "latitude";
// em_place_longitude не описан в схеме и не встречается в документе
private static final String LONGITUDE_PROPERTY = "longitude";
static void parseCrashCoordinates(final JsonReader jsonReader, final ICoordinatesListener listener)
throws IOException {
// Считываем { как начало объекта.
// Если его не считать или считать неверно, выбросится исключение -- это и есть суть pull-метода.
jsonReader.beginObject();
// Смотрим имя следующего свойства объекта и сравниваем его с ожидаемым.
final String itemsName = jsonReader.nextName();
if ( !itemsName.equals(ITEMS_NAME) ) {
// Не items? Возможно, у нас нет идей как его обработать -- лучше выбросить исключение.
throw new MalformedJsonException(ITEMS_NAME + " expected but was " + itemsName);
}
// Так же теперь вычитываем [
jsonReader.beginArray();
// И читаем каждый элемент массива
while ( jsonReader.hasNext() ) {
// Судя по схеме, каждый элемент массива - объект
jsonReader.beginObject();
double latitude = 0;
double longitude = 0;
// И так же пробегаемся по всех свойствах этого объекта
while ( jsonReader.hasNext() ) {
// Теперь просто смотрим, являются ли они нам известными
final String property = jsonReader.nextName();
switch ( property ) {
// latitude? Запоминаем.
case LATITUDE_PROPERTY:
latitude = jsonReader.nextDouble();
break;
// longitude? Запоминаем.
case LONGITUDE_PROPERTY:
longitude = jsonReader.nextDouble();
break;
// Иначе просто пропускаем любое значение свойства.
default:
jsonReader.skipValue();
break;
}
}
// Просто делегируем полученные координаты в обработчик
listener.onCoordinates(latitude, longitude);
// И говорим, что с текущим элементом массива, именно объектом, покончено.
jsonReader.endObject();
}
// Также закрываем последние ] и }
jsonReader.endArray();
jsonReader.endObject();
}
Как выглядит обработчик:
interface ICoordinatesListener {
void onCoordinates(double latitude, double longitude);
}
В принципе, возможны ситуации, когда в документе в координатах указана или широта или долгота, или оба значения отсутствуют (и, в принципе, обработка таких случаев является хорошим тоном), но при обработке 148817911056482-crash.json мне такие случаи не встречались.
Теперь протестируем всё это дело.
public static void main(final String... args)
throws IOException {
testOutput();
testCollecting();
}
// Этот тест просто выводит содержимое координат в стандартный поток вывода
// Заметьте: parseCrashCoordinates() сам не решает _что_ делать с координатами -- это целиком наше дело
// Поскольку мы вообще просто передаём данные дальше, нас вообще не волнует размер входных данных
// Теоретически, это может быть бесконечный поток данных -- круто ведь?
private static void testOutput()
throws IOException {
readAndParse((lat, lng) -> System.out.println("(" + lat + "; " + lng + ")"));
}
// Здесь мы, напротив, собираем координаты в список.
// Выдержит ли JVM увеличение списка coordinates? Возможно, но не факт: зависит от размера данных и памяти, доступной JVM.
private static void testCollecting()
throws IOException {
final List
private static final class Coordinate {
private final double latitude;
private final double longitude;
private Coordinate(final double latitude, final double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
}
private static void readAndParse(final ICoordinatesListener listener)
throws IOException {
try ( final JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(new FileInputStream(...)))) ) {
parseCrashCoordinates(jsonReader, listener);
}
}
Этим примером мне удалось разобрать 148817911056482-crash.json размером 746 МБ, не особо тратясь на вычислительные ресурсы (громко сказано, тем не менее :)). Хвост вывода таков:
(55.632584; 37.80792)
(51.5703; 135.8539)
(51.9139; 39.2233)
99497
(3 последние координаты и размер списка, в который были собраны все координаты).
Комментариев нет:
Отправить комментарий