2013-04-10 11 views
13

Próbuję parsować wyjątkowo duży plik json na iPadzie. Rozmiar pliku będzie się wahał od 50 do 100 mb (istnieje plik początkowy i będzie co miesiąc nowy pełny zestaw danych, które będą pobierane, analizowane i zapisywane w coredatach). Firma jako rozwiązanie dla przedsiębiorstw - plik json zawiera poufne dane klienta i musi być zapisany lokalnie na iPadzie, aby działał nawet w trybie offline. Udało się, gdy plik był poniżej 20mb, ale teraz zbiór danych stał się większy i naprawdę muszę go przeanalizować. Otrzymuję ostrzeżenia o pamięci podczas parsowania i po trzecim ostrzeżeniu po prostu się zawiesza. Mam kilka różnych jednostek danych podstawowych i właśnie ustawiam wszystkie wartości pochodzące z pliku json (kiedy aplikacja jest uruchamiana po raz pierwszy) i po wykonaniu wszystkich czynności wykonuję [context save].iPad - Parsowanie niezwykle dużego pliku json - Plik (między 50 a 100 MB)

Miałem nadzieję, że ktoś może mi doradzić jak radzić sobie z tak ogromnymi plikami. Myślałem o podzieleniu pliku json na kilka mniejszych plików json i może analizowałem je w wielu wątkach, ale nie wiem, czy to właściwe podejście. Chyba jednym wielkim problemem jest to, że cały plik jest przechowywany w pamięci - może jest jakiś sposób na "przesłanie" go do pamięci lub coś w tym stylu?

Używam JSONKit (https://github.com/johnezang/JSONKit) do parsowania pliku, ponieważ przeczytałem, że jest najszybszy (może jest wolniejszy, który jest łatwiejszy w pamięci?).

Z góry dziękuję.

+0

Prawdopodobnie najlepiej byłoby, gdyby dane były przesyłane w częściach, a nie w jednym dużym łańcuchu JSON. Twoje podstawowe ograniczenie rozmiaru to przestrzeń wymagana dla wszystkich obiektów JSON. –

+0

Co powiesz na zapisanie wszystkich danych w pliku sqlite lub zapisanie trwałości danych podstawowych za pomocą narzędzia Mac i skopiowanie go do aplikacji przed podpisaniem, zamiast przeniesienia tego na urządzenie? – Kerni

+0

Jeśli masz kontrolę nad interfejsem API dla serwera, poleciłbym interfejs API, który pobierałby parametr Offset i parametr Count. Przesunięcie określa przesunięcie w wynikach, a liczba wskazuje ile rekordów do pobrania. Zatem kolejne wywołania interfejsu API zwiększyłyby przesunięcie o wartość licznika. – rajagp

Odpowiedz

16

1) Wpisz swoje dane do pliku, a następnie za pomocą NSData za dataWithContentsOfFile: Opcje: Błąd: i określić NSDataReadingMappedAlways i NSDataReadingUncached flagi. Dzięki temu system będzie używał mmap() w celu zmniejszenia śladu pamięciowego, a nie obciążania pamięci podręcznej systemu plików blokami pamięci (co spowalnia, ale znacznie mniej obciąża system iOS).

2) Możesz użyć obiektu YAJL SAX style JSON parser, aby uzyskać obiekty podczas ich dekodowania.

Uwaga: nie zrobiłem 2), ale użyłem technik zawartych w 1).

3) Skończyło się, że potrzebowałem czegoś takiego, i napisał SAX-JSON-Parser-ForStreamingData, które można powiązać z dowolnym asynchronicznym downloaderem (w tym moim własnym).

+1

David pobił mnie do tego. Całkowicie się z nim zgadzam. Użyłem obu podejść i odniosłem sukces zarówno w plikach większych niż 100 MB, jak i w numeracji rekordów w setkach tysięcy - chociaż ostatecznie poszedłem z podejściem przywoławczym i kazałem naszym ludziom serwera to rozdzielić. To rozwiązało wszystkie moje problemy. Jest to idealne rozwiązanie, jeśli możesz użyć czegoś takiego jak NSJSONSerializtion lub JSONKit, ponieważ są one naprawdę szybkie, ale musisz pracować z tym, co dostajesz. –

+0

Więc będę musiał przejść z JSONKit na YAJL, czy to prawda? – gasparuff

+0

JSONKit nie pozwoli ci odzyskać części, więc musisz mieć wystarczającą ilość pamięci, aby pomieścić cały wynik, lub jego awaria. Nie mam doświadczenia z YAJL, ale wygląda na to, że robi to @MattLong i wydaje się, że jest to realna opcja, jeśli nie jedyna dostępna opcja. Powiedziałeś, że możesz znaleźć parser C lub C++, ale wyobrażam sobie, że byłoby jeszcze więcej pracy z twojej strony. –

2

Biorąc pod uwagę obecne ograniczenia pamięci na urządzeniu mobilnym, to prawdopodobnie niemożliwe do analizowania 100 tekst MB JSON i następnie stworzyć reprezentację obiektu Foundation, która sama zabrać około 10 razy większą ilość pamięci RAM niż wielkość źródła Tekst JSON.

Oznacza to, że twój wynik JSON zajmie około 1 GB pamięci RAM w celu przydzielenia miejsca wymaganego dla obiektów fundamentowych.

Najprawdopodobniej nie ma możliwości utworzenia jednej gigantycznej reprezentacji JSON - bez względu na to, jak się zdobywasz i czytasz i analizujesz dane wejściowe. Musisz podzielić go na wiele mniejszych. Może to wymagać modyfikacji po stronie serwera.

Innym rozwiązaniem jest to, ale dużo bardziej rozbudowany:

pomocą parsera SAX w stylu, który przybiera ogromne JSON jako wejście za pośrednictwem interfejsu API strumieniowego i wyjść kilka mniejszych tekstów JSON (wewnętrzne części).Parser stylu SAX może wykorzystywać bloki API (dispatch lib) do przekazywania wyników - mniejsze JSON asynchronicznie do innego analizatora JSON. Oznacza to, że mniejsze JSON-y są zasilane zwykłym parserem JSON, który generuje reprezentacje JSON, które z kolei są zasilane generatorem modeli CoreData.

Możesz nawet pobrać ogromny JSON i przeanalizować go jednocześnie z parserem stylu SAX, jednocześnie tworząc mniejsze JSON-y i jednocześnie zapisując je w Core Data.

Potrzebny jest parser JSON z interfejsem SAX API, który umożliwia analizowanie fragmentów tekstu wejściowego, szybkie wykonanie i tworzenie reprezentacji obiektów Foundation.

Znam tylko jedną bibliotekę JSON, która ma ten zestaw funkcji, a są nawet podane przykłady, które mogą częściowo pokazać, w jaki sposób można osiągnąć dokładnie to: JPJson na GitHub. Parser jest również bardzo szybki - na ARM jest szybszy niż JSONKit. Zastrzeżenie: jego implementacja jest w języku C++ i wymaga kilku kroków, aby zainstalować ją na komputerze programisty. Ma jednak dobrze udokumentowane API Objective-C.

Chciałbym dodać, że jestem autorem;) Niedługo dostępna jest aktualizacja, która wykorzystuje najnowszy kompilator C++ 11 i funkcje biblioteki C++ 11, co daje jeszcze szybszy kod (o 25% szybciej na ARM niż JSONKit i dwa razy szybciej niż NSJSONSerialization).

Aby podać te same fakty dotyczące prędkości: Analizator składni jest w stanie pobrać (przez Wi-Fi) i przeanalizować 25 MB danych zawierających 1000 JSON (po 25 KB) w 7 sekund w sieci Wifi 802.11gi 4 sekundy w Wifi 802.11n, w tym tworzenie i zwalnianie 1000 reprezentacji na iPadzie 2.

+0

Dzięki za odpowiedź. Na pewno przyjrzę się temu jutro w biurze. Pomysł polegałby na wykorzystaniu parsera JPJson do podzielenia dużego pliku json na mniejsze ciągi jsonów (lub pliki), a następnie użycia jakiegoś innego parsera json (Dom-style?) Do parsowania podzielonych części. – gasparuff

+0

Alternatywnie, oddziel duży JSON na wielu mniejszych serwerach i wysyłaj wiele JSON w jednym strumieniu ogromnych bajtów w jednym połączeniu. Analizator składni JPJson może analizować strumień danych składający się z wielu dokumentów JSON. Za każdym razem, gdy kończy jedną reprezentację JSON, wywołuje blok (lub wywołanie zwrotne) przekazując wynik jako parametr. W ten sposób możesz korzystać z istniejących klas, bez potrzeby samodzielnego kodowania. (zobacz JPAsyncJsonParser, [link] (https://github.com/couchdeveloper/JPJson) – CouchDeveloper

+0

OK, dziękuję. Problem polega na tym, że nie mam tak naprawdę kontroli nad tym, jak będzie przesyłany plik JSON co miesiąc. być jednym początkowym JSONem, który musi zostać przeanalizowany przy pierwszym uruchomieniu aplikacji i właśnie to próbuję teraz zrobić.) Wypróbuję różne rzeczy i udzielę informacji zwrotnej, jak tylko uzyskałem jakieś przydatne wyniki. – gasparuff