В предыдущей статье мы узнали, как анализировать чрезмерно большие XML-файлы и превращать их в потоки RxJava. На этот раз давайте посмотрим на большой файл JSON. Мы будем основывать наши примеры на крошечных цветах. Json, содержащих почти 150 записей такого формата:
|
1
2
3
4
5
6
7
|
{ "aliceblue": [240, 248, 255, 1], "antiquewhite": [250, 235, 215, 1], "aqua": [0, 255, 255, 1], "aquamarine": [127, 255, 212, 1], "azure": [240, 255, 255, 1], //... |
Малоизвестный факт: лазурь — это тоже цвет, а питон — змея. Но вернемся к RxJava. Этот файл крошечный, но мы будем использовать его для изучения некоторых принципов. Если вы будете следовать им, вы сможете загружать и непрерывно обрабатывать произвольно большие, даже бесконечно длинные файлы JSON. Во-первых, стандартный способ « Джексона » похож на JAXB: загрузка всего файла в память и сопоставление его с Java-бинами. Однако, если ваш файл в мегабайтах или гигабайтах (потому что каким-то образом вы нашли JSON лучшим форматом для хранения гигабайтов данных …), этот метод не сработает. К счастью, Джексон обеспечивает потоковый режим, похожий на StAX.
Загрузка файлов JSON токен-токен с использованием Jackson
Нет ничего плохого в стандартном ObjectMapper который принимает JSON и превращает его в коллекцию объектов. Но чтобы избежать загрузки всего в память, мы должны использовать низкоуровневый API, используемый ObjectMapper внизу. Давайте снова посмотрим на пример JSON:
|
1
2
3
4
|
{ "aliceblue": [240, 248, 255, 1], "antiquewhite": [250, 235, 215, 1], //... |
С точки зрения диска и памяти, это одномерный поток байтов, который мы можем логически объединить в токены JSON:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
START_OBJECT '{'FIELD_NAME 'aliceblue'START_ARRAY '['VALUE_NUMBER_INT '240'VALUE_NUMBER_INT '248'VALUE_NUMBER_INT '255'VALUE_NUMBER_INT '1'END_ARRAY ']'FIELD_NAME 'antiquewhite'START_ARRAY '['VALUE_NUMBER_INT '250'VALUE_NUMBER_INT '235'VALUE_NUMBER_INT '215'VALUE_NUMBER_INT '1'END_ARRAY ']'... |
Вы поняли идею. Если вы знакомы с теорией компилятора, это один из первых шагов во время компиляции. Компилятор преобразует исходный код из символов в токены.
Но, если вы знаете теорию компилятора, вы, вероятно, не разбираете JSON на жизнь. Так или иначе! Библиотека Джексона работает таким образом, и мы можем использовать ее без прозрачного отображения объектов:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
import com.fasterxml.jackson.core.JsonFactory;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.core.JsonToken; JsonParser parser = new JsonFactory().createParser(new File("colors.json"));parser.nextToken(); // JsonToken.START_OBJECT;while (parser.nextToken() != JsonToken.END_OBJECT) { final String name = parser.getCurrentName(); parser.nextToken(); // JsonToken.START_ARRAY; parser.nextValue(); final int red = parser.getIntValue(); parser.nextValue(); final int green = parser.getIntValue(); parser.nextValue(); final int blue = parser.getIntValue(); parser.nextValue(); parser.getIntValue(); System.out.println(name + ": " + red + ", " + green + ", " + blue); parser.nextToken(); // JsonToken.END_ARRAY;}parser.close(); |
… или если вы избавитесь от некоторого дублирования и сделаете код немного легче для чтения:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import lombok.Value; JsonParser parser = new JsonFactory().createParser(new File("colors.json"));parser.nextToken(); // JsonToken.START_OBJECT;while (parser.nextToken() != JsonToken.END_OBJECT) { System.out.println(readColour(parser));}parser.close(); //... private Colour readColour(JsonParser parser) throws IOException { final String name = parser.getCurrentName(); parser.nextToken(); // JsonToken.START_ARRAY; final Colour colour = new Colour( name, readInt(parser), readInt(parser), readInt(parser), readInt(parser) ); parser.nextToken(); // JsonToken.END_ARRAY; return colour;} private int readInt(JsonParser parser) throws IOException { parser.nextValue(); return parser.getIntValue();} @Valueclass Colour { private final String name; private final int red; private final int green; private final int blue; private final int alpha;} |
Какое это имеет отношение к RxJava? Вы, вероятно, можете догадаться — мы можем читать этот файл JSON по запросу, по частям. Это позволяет механизму противодавления работать бесперебойно:
|
1
2
3
4
|
final Flowable colours = Flowable.generate( () -> parser(new File("colors.json")), this::pullOrComplete, JsonParser::close); |
Позвольте мне объяснить, что делают эти три лямбда-выражения. Первый устанавливает JsonParser — наше изменяемое состояние, которое будет использоваться для производства (получения) большего количества элементов:
|
1
2
3
4
5
|
private JsonParser parser(File file) throws IOException { final JsonParser parser = new JsonFactory().createParser(file); parser.nextToken(); // JsonToken.START_OBJECT; return parser;} |
Ничего фантастического. Второе лямбда-выражение имеет решающее значение. Он вызывается каждый раз, когда подписчик желает получить больше товаров. Если он запрашивает 100 элементов, это лямбда-выражение будет вызываться 100 раз:
|
1
2
3
4
5
6
7
8
|
private void pullOrComplete(JsonParser parser, Emitter<Colour> emitter) throws IOException { if (parser.nextToken() != JsonToken.END_OBJECT) { final Colour colour = readColour(parser); emitter.onNext(colour); } else { emitter.onComplete(); }} |
Конечно, если мы достигаем END_OBJECT (закрытие всего файла JSON), мы сигнализируем, что поток закончен. Последнее лямбда-выражение просто позволяет очистить состояние, например, закрыв JsonParser и лежащий в его основе File . Теперь представьте, что размер этого файла JSON составляет сотни гигабайт. Имея Flowable<Colour> мы можем безопасно использовать его с произвольной скоростью, не рискуя перегрузкой памяти.
| Ссылка: | Потоковая передача большого файла JSON с часто задаваемыми вопросами по Jackson — RxJava от нашего партнера по JCG Томаша Нуркевича в блоге Java и соседних блогах. |