[Gelöst] große JSON Datei

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.
sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

[Gelöst] große JSON Datei

Beitrag von sstvmaster »

Hi,

ich habe hier eine ca. 28MB große JSON Datei mit ca. 209000 Einträgen.

Ich würde diese gern in eine Stringlist laden, leider dauert das ewig.
Gibt es noch eine andere Methode relative schnell an diese Daten zu kommen?

Oder kann man auch in der JSON Datei suchen ohne diese ganz laden zu müssen?

Zum einlesen habe ich bisher folgenden Code benutzt:

Code: Alles auswählen

 
  FS := TFileStream.Create('city.list.json',fmOpenRead);
  J := GetJSON(FS);
  FS.Free;
 
  sl:=TStringList.Create;
  for i:=0 to j.count-1 do
  begin
    if (LowerCase(j.items[i].findpath('name').AsString) = 'hurzuf') then
    begin
      SL.Add(Format('%s=%d', [J.Items[i].FindPath('name').AsString,
                              J.Items[i].FindPath('id').AsInteger]));
    end;
  end;
  SL.Free;


Die JSON Datei sieht so aus (gekürzt):

Code: Alles auswählen

[
  {
    "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
    }
  },
  {
    "id": 1270260,
    "name": "State of Haryāna",
    "country": "IN",
    "coord": {
      "lon": 76,
      "lat": 29
    }
  }, ...
]


Danke!
Zuletzt geändert von sstvmaster am Do 1. Nov 2018, 19:51, insgesamt 1-mal geändert.
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: große JSON Datei

Beitrag von pluto »

Warum möchtest du die JSON Datei in die StringList einlesen?
Es gibt doch Fertige units dafür?

http://wiki.freepascal.org/fcl-json
http://wiki.freepascal.org/Streaming_JSON

Stichwörter: FPC JSON
MFG
Michael Springwald

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: große JSON Datei

Beitrag von sstvmaster »

Ich habe mir ein Programm geschrieben wo man sich die Wetterdaten anzeigen lassen kann (openweathermap.org api).

Aber um sie anzeigen lassen zu können brauchen ich die ID zu dem Ort wo sich derjenige befindet.
Ich will die id, name und country in eine Liste haben damit man sich seinen Ort auswählen kann.

Mein erster Gedanke war die Kombination aus name_country=id in eine Stringliste einzulesen und dann in
einer durchsuchbaren ComboBox anzeigen zu lassen, Stringlist name value pairs + OwnerDraw funktionalität der ComboBox.
Somit hätte ich dann die passende ID zum ausgewählten Ort.

Zweiter Gedanke war ein Edit als Sucheeingabe mit Rücklieferung der gefundenen Orte (zb London existiert 5x Weltweit), in einer ComboBox.

Ich bin schonmal froh das ich so weit gekommen bin, nun fehlt mir nur noch die ID, Namen Kombination(en).

Es gibt doch Fertige units dafür?

Für Dich mögen das fertige units sein, für mich ein Buch mit sieben Siegeln.

LG Maik
Dateianhänge
Unbenannt.JPG
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: große JSON Datei

Beitrag von pluto »

Für Dich mögen das fertige units sein, für mich ein Buch mit sieben Siegeln.

Zugegeben, sie sind nicht ganz einfach, aber das ist der elegantestes weg.... womit hast du genau Probleme?

Selbst ein JSON Parser zu schreiben, dürfte noch mehr Aufwand sein und bei größeren Datenmengen ist eine TStringList einfach zu langsam...
Weil sie in der Regel erst mal alle Daten aus der Datei Lädt.
MFG
Michael Springwald

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: große JSON Datei

Beitrag von mse »

Das interessiert mich, da ich auch einige JSON Funktionen gemacht habe.
Was ist langsam?

Code: Alles auswählen

 
  FS := TFileStream.Create('city.list.json',fmOpenRead);
  J := GetJSON(FS);
  FS.Free;
 

der Rest oder beides? Wie lange dauern die Abschnitte? Kannst du die JSON Datei für Tests zur Verfügung stellen?

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: große JSON Datei

Beitrag von Horst_h »

Hallo,

http://bulk.openweathermap.org/sample/
dort die erste
city.list.json.gz

Gruß Horst

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: große JSON Datei

Beitrag von Horst_h »

Hallo,

ich habe mal ein minimales Programm aus dem Schnipsel oben geschrieben.

Code: Alles auswählen

uses
  sysutils,classes,fpjson,jsonparser;
var
  FS : TFileStream;
  SL : TStringList;
  J  : TJSONData;
  T1,T0 :int64;
  i  : NativeInt;
begin 
  T0 := GetTickCount64;
  FS := TFileStream.Create('city.list.json',fmOpenRead);
  J := GetJSON(FS);
  T1 := GetTickCount64; 
  Writeln(' Einlesen in ',T1-T0,' ms');
  Writeln(' Zeilen ',J.count);
  T0:= GetTickCount64; 
  sl:=TStringList.Create;
  for i:=0 to j.count-1 do
  begin
    if (LowerCase(j.items[i].findpath('name').AsString) = 'hurzuf') then
    begin
      SL.Add(Format('%s=%d', [J.Items[i].FindPath('name').AsString,
                              J.Items[i].FindPath('id').AsInteger]));
    end;
  end;
  T1 := GetTickCount64; 
  Writeln(' Alles durchsuchen in ',T1-T0,' ms');
 
  readln;
  SL.Free
  FS.Free
 
end.


Code: Alles auswählen

 
 Einlesen in 1797 ms
 Zeilen 209579
 Alles durchsuchen in 151 ms
 
 belegt etwa 335 MB // deshalb das readln vor dem free


Ist das wirklich zu langsam?
Es ginge ja, das man ein eigene "Datenbank" einen eigenen record erstellen und als file of record schreiben.

Gruß Horst
P.S.:
Nur in eine Stringliste einlesen dauerte 250 ms ( Datei war natürlich schon im cache )

P.S.S.:
Mein Rechner Ryzen 5 1600 /aka 3.2/3.6 Ghz multi/single thread.Betriebsystem Linux 64-Bit fpc 3.0.4 kernel 4.18.12? tut nichts zur Sache.
Zuletzt geändert von Horst_h am Sa 20. Okt 2018, 18:09, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4864
Registriert: Fr 8. Apr 2011, 09:01

Re: große JSON Datei

Beitrag von wp_xyz »

Eine (es gibt wahrscheinlich noch mehr) Möglichkeit, die JSON-Datei aus horst_h's Beitrag mit "Hausmitteln" in ein Array einzulesen, ist dies:

Code: Alles auswählen

uses
  fpjson, jsonparser;
 
const
  FILENAME = 'city.list.json';
 
{ TForm1 }
 
type
  TCity = record
    Name: String;
    Country: String;
    Longitude: Double;
    Latitude: Double;
  end;
 
var
  Cities: array of TCity;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  stream: TFileStream;
  json: TJSONData;
  parser: TJSONParser;
  jArr: TJSONArray;
  j: Integer;
  jcoord: TJSONObject;
  t: TDateTime;
begin
  t := now;
  json := nil;
  try
    stream := TFileStream.Create(FILENAME, fmOpenRead or fmShareDenyWrite);
    try
      parser := TJSONParser.Create(Stream, []);
      try
        json := parser.Parse;
      finally
        parser.Free;
      end;
    finally
      stream.Free;
    end;
 
    SetLength(Cities, json.Count);
    j := 0;
    jArr := TJSONArray(json);
    for j := 0 to jArr.Count-1 do begin
      Cities[j].Name := TJSONObject(jArr[j]).Find('name').AsString;
      Cities[j].country := TJSONObject(jArr[j]).Find('country').AsString;
      jcoord := TJSONObject(jArr[j]).Find('coord') as TJSONObject;
      Cities[j].Longitude := jcoord.Find('lon').AsFloat;
      Cities[j].Latitude := jcoord.Find('lat').AsFloat;
    end;
  finally
    json.Free;
  end;
 
  caption := FormatDateTime('s.zzz"s"', now-t);
end;
 

Das ganze dauert auf meinem Rechner 3 Sekunden.
Hinweis: Da du in den (vielen!) eingelesenen Daten suchen willst, ist ein Array wahrscheinlich nicht die optimale Datenstruktur. Aber das Prinzip, wie man die Daten aus dem JSON herausholt, siehst du.

P.S.
Horst_h war schneller...

BeniBela
Beiträge: 306
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: große JSON Datei

Beitrag von BeniBela »

Schneller ginge es mit der jsonscanner unit.

Dann muss man nicht alles strukturiert in den Speicher laden, sondern bekommt jeden Wert nach einander, [ { "id": 707860, wird sozusagen zu einer Liste tkSquaredBraceOpen tkCurlyBraceOpen tkString(id) tkColon tkNumber(707860)

So ist der tjsonparser auch implementiert, intern erzeugt es den tjsonscanner, und erzeugt dann je ein Objekt für die Werte aus der Liste

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: große JSON Datei

Beitrag von Horst_h »

Hallo,

ich habe mal die json Datei pro Datum zu einem Einzeiler gemacht.

Code: Alles auswählen

[
{"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}}, <= Teil eines Staates/ Bundesland  lat ohne Nachkomma?
{"id":1270260,"name":"State of Haryāna","country":"IN","coord":{"lon":76,"lat":29}}, <= Staaten werden ohne Nachkomma angegeben ?
{"id":708546,"name":"Holubynka","country":"UA","coord":{"lon":33.900002,"lat":44.599998}},
{"id":1283710,"name":"Bāgmatī Zone","country":"NP","coord":{"lon":85.416664,"lat":28}},
{"id":529334,"name":"Mar’ina Roshcha","country":"RU","coord":{"lon":37.611111,"lat":55.796391}},
{"id":1269750,"name":"Republic of India","country":"IN","coord":{"lon":77,"lat":20}},

Das beschleunigte das einlesen um 25% und verkleinerte die Datei von 29 auf 19 Mb.
Die Daten in einen array of TCity zu packen macht sicher mehr Sinn, um sie so zu speichern und die weitere Verarbeitung zu beschleunigen.Dazu stabile Sortierroutinen und gut ist.
Erst nach Name, dann nach Staat sortieren und schon sind die Städte innerhalb der Staaten sortiert. 209000 Daten geht in Sekundenbruchteilen.
TCity würde ich aber anders aufbauen, um es file of TCity speichern zu können.ID fehlte.

Code: Alles auswählen

 
type
  TCity = record
    ctLongitude: Double;
    ctLatitude: Double;
    ctID : LongWord;
    ctCountry: String[3];// sind nur 2 Zeichen -> so 4 Byte
    ctName: String[79];
  end;
{
Maḩall-e Eḩdās̄-e Shahrak-e Shardārī-ye Būkān  55 ( Zeichen aber nur 45 Spalten belegt  )
Appiano sulla strada del vino - Eppan an der Weinstrasse  56
Posëlok Psikhonevrologicheskoy Bol’nitsy Imeni Karamzina  59
Provincia de Tierra del Fuego, Antártida e Islas del Atlántico Sur  68
Providencia y Santa Catalina, Departamento de Archipiélago de San Andrés  74 }

 

Scheinbar man da auch UTF8 reinpacken.
Damit kommt man auf etwa 21 Mb :-( .

Gruß Horst

wp_xyz
Beiträge: 4864
Registriert: Fr 8. Apr 2011, 09:01

Re: große JSON Datei

Beitrag von wp_xyz »

BeniBela hat geschrieben:Schneller ginge es mit der jsonscanner unit.

ja, dann hier noch eine Version mit dem Scanner. Sie braucht bei mir 2 sec statt 3 oben. Vielleicht kann man noch ein bisschen optimieren, aber viel sollte nicht mehr drin sein, denn der reine Durchlauf des Scanners (ohne das große "case") braucht allein schon 1.5 sec.

Code: Alles auswählen

procedure TForm1.Button3Click(Sender: TObject);
const
  BUFFER_SIZE = 1000;
  partName = 1;
  partValue = 2;
var
  fs: TFormatSettings;
  stream: TFileStream;
  scanner: TJSONScanner;
  t: TJSONToken;
  dt: TDateTime;
  j: Integer;
  s: String;
  part: Integer = 0;
  needName: boolean = false;
  needCountry: Boolean = false;
  needLong: Boolean = false;
  needLat: Boolean = false;
begin
  dt := Now;
  fs := DefaultFormatSettings;
  fs.DecimalSeparator := '.';
  j := -1;
  stream := TFileStream.Create(FILENAME, fmOpenRead);
  try
    scanner := TJSONScanner.Create(stream, []);
    try
      repeat
        t := scanner.FetchToken;
        case t of
          tkCurlyBraceOpen:
            part := partName;
          tkColon:
            part := partValue;
          tkCurlyBraceClose:
            part := 0;
          tkComma:
            part := partName;
          tkString, tkNumber:
            begin
              s := scanner.CurTokenString;
              case part of
                partName:
                  case s of
                    'id':
                      begin
                        inc(j);
                        if j mod BUFFER_SIZE = 0 then
                          SetLength(Cities, Length(Cities) + BUFFER_SIZE);
                      end;
                    'name': needName := true;
                    'country': needCountry := true;
                    'lon': needLong := true;
                    'lat': needLat := true;
                  end;
                partValue:
                  if needName then begin
                    Cities[j].Name := s;
                    needName := false;
                  end else
                  if needCountry then begin
                    Cities[j].Country := s;
                    needCountry := false;
                  end else
                  if needLong then begin
                    Cities[j].Longitude := StrToFloat(s, fs);
                    needLong := false;
                  end else
                  if needLat then begin
                    Cities[j].Latitude := StrToFloat(s, fs);
                    needLat := false;
                  end;
              end;
            end;
          tkEOF:
            break;
        end;
      until false;
      SetLength(Cities, j+1);
    finally
      scanner.Free;
    end;
  finally
    stream.Free;
  end;
  Caption := FormatDateTime('s.zzz"s"', now-dt);
end;
 

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: große JSON Datei

Beitrag von sstvmaster »

Vielen Dank erst mal an alle, komme Heute und Morgen noch nicht dazu alles auszuprobieren.

Ich fang mal hier an zu Antworten.
mse hat geschrieben:Das interessiert mich, da ich auch einige JSON Funktionen gemacht habe.
Was ist langsam?

Code: Alles auswählen

 
  FS := TFileStream.Create('city.list.json',fmOpenRead);
  J := GetJSON(FS);
  FS.Free;
 

der Rest oder beides? Wie lange dauern die Abschnitte? Kannst du die JSON Datei für Tests zur Verfügung stellen?

Code: Alles auswählen

Einlesen in 5335 ms
Zeilen 209579
Alles durchsuchen in 577 ms

Hier auf meinem Laptop: Pentium T4500, 4GB, SSD, W7_32
Laz 1.8.4 + Trunk
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: große JSON Datei

Beitrag von Horst_h »

Hallo,

ich würde die city.list in ein file of TCity umwandeln.
Das ist nur selten nötig und damit ist sie leichter handhabbar.

Steffen Polster hatte vor Jahren ein OpenSource-Programm dafür komponiert:
https://www.entwickler-ecke.de/viewtopic.php?t=111904
Aber die wollen jetzt einen persönlichen "API key" , einen öffentlichen API key gibt es wohl nicht.

Gruß Horst
EDIT:
Ich habe jetzt ein Datei mit records gemacht.
Die liest sich fast 5x schneller ein.

Code: Alles auswählen

program JsonTest;
uses
  SysUtils,
  Classes,
  strutils;
 
const
  ZeilenProJSONDatum = 9;
  cInName  = 'city.list.json';
  cOutName = 'City.dat';
 
type
  TCity = record
    ctLat, ctLon: double;
    ctId: longword;
    ctCty: string[3];
    ctName: string[79];
  end;
 
var
  T1, T0: int64;
  POIs : array of TCity;
 
  procedure Split(const s:string;
                    var POI:tCity);
  //-> {"id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}},
  var
  StartIdx,EndIdx: NativeInt;
  Begin
    with POI do
    Begin
      StartIdx:= Pos(':',s)+1;
      EndIdx  := PosEx(',', s,StartIdx);
      ctId:= StrToInt(copy(s,StartIdx,EndIdx-StartIdx));
 
      StartIdx:= PosEx(':',s,EndIdx+7)+2;
      EndIdx  := PosEx(',', s,StartIdx)-1;
      ctname:= copy(s,StartIdx,EndIdx-StartIdx);
 
      StartIdx:= PosEx(':',s,EndIdx+11)+2;
      EndIdx  := PosEx(',', s,StartIdx)-1;
      ctCty := copy(s,StartIdx,EndIdx-StartIdx);
 
      StartIdx:= PosEx(':',s,EndIdx+11)+1;
      EndIdx  := PosEx(',', s,StartIdx);
      ctLon   := StrToFloat(copy(s,StartIdx,EndIdx-StartIdx));
 
      StartIdx:= PosEx(':',s,EndIdx+5)+1;
      EndIdx  := PosEx('}', s,StartIdx);
      ctLat   := StrToFloat(copy(s,StartIdx,EndIdx-StartIdx));
    end;
  end;
 
  procedure removeSpaceAfter(var s: string);
  //': '-> ':'
  var
    i, j: NativeInt;
  begin
    j := 1;
    i := 1;
    while i <= length(s) do
    begin
      s[j] := s[i];
      if s[i] = ':' then
        Inc(i);
      Inc(j);
      Inc(i);
    end;
    setlength(s, j - 1);
  end;
 
  procedure ConvertJson(InName, OutName: string);
  var
    f : file of TCity;
    sl: TStringList;
    erg: string;
    POI: TCity;
    i, j : NativeInt;
  begin
    sl := TStringList.Create;
    sl.loadfromfile(InName);
    Assign(f,OutName);
    Rewrite(f);
    j := 0;
    i := 0;
    repeat
      if Pos('{', sl[i]) > 0 then
        break;
      Inc(i);
    until i >= sl.Count;
    while i < sl.Count - 1 do
    begin
      erg := '';
      for j := 0 to ZeilenProJSONDatum - 1 do
        erg := erg + trim(sl[i + j]);
      removeSpaceAfter(erg);//': '-> ':'
      Split(erg,POI);
      write(f,POI);
      Inc(i, ZeilenProJSONDatum);
    end;
    SL.Free;
    CloseFile(f);
  end;
 
  procedure ReadPOIs(OutName:String);
  var
    f:File of TCity;
    i,j : integer;
  Begin
    Assign(f,OutName);
    Reset(f);
    i := FileSize(f);
    writeln(i);
    setlength(POIs,i);
    dec(i);
    For j:= 0 to i do
      Read(f,POIs[j]);
    CloseFile(f);
  end;
 
  procedure OutPOI(POIidx:NativeInt);
  Begin
    with POIs[POIidx] do
      writeln(POIidx:8,ctID:8,ctCty:4,ctLon:12:6,ctLat:11:6,'  ',ctName);
  end;
 
  procedure CheckCountry(s:String);
  var
    i,cnt : NativeInt;
  Begin
    IF length(s)<> 2 then
      EXIT;
    s:= Uppercase(s);
    cnt := 0;
    For i := 0 to High(POIs) do
      If s = POIs[i].ctCty then
      Begin
        inc(cnt);
        OutPOI(i);
      end;
    writeln('Es wurden ',cnt,' Übereinstimmungen gefunden');   
  end
 
  procedure CheckName(s:String);
  var
    i,cnt : NativeInt;
  Begin
    s:= lowercase(s);
    cnt := 0;
    For i := 0 to High(POIs) do
      If POS(s,lowercase(POIs[i].ctName)) >0 then
      Begin
        OutPOI(i);
        inc(cnt);
      end
    writeln('Es wurden ',cnt,' Übereinstimmungen gefunden');   
  end;
 
begin
  IF NOt(FileExists(cOutName)) then
  Begin
    IF NOT(FileExists(cInName)) then
    Begin
      writeln('Keine passende Datei ',cInName,' vorhanden');
      HALT(-2);
    end;
    T0 := GetTickCount64;   
    ConvertJson(cInName, cOutName);
    T1 := GetTickCount64;
    Writeln(' Alles wegschreiben in ', T1 - T0, ' ms');
  end;
 
  IF NOT(FileExists(cOutName)) then
  Begin
    writeln('Keine passende Datei ',cOutName,' vorhanden');
    HALT(-3);
  end;
 
  T0 := GetTickCount64;
  ReadPOIs( cOutName);
  T1 := GetTickCount64;
  Writeln(' Alles einlesen in     ', T1 - T0, ' ms')
 
  T0 := GetTickCount64;
  CheckCountry('ZA');
  T1 := GetTickCount64;
  Writeln(' Suche in     ', T1 - T0, ' ms');   
 //Es wurden 555 Übereinstimmungen gefunden // "DE" über 27000 viele doppelt, oder ein paar m daneben
 //Suche in     11 ms
 
  T0 := GetTickCount64;
  CheckName('bOurG');
  T1 := GetTickCount64;
  Writeln('Suche in     ', T1 - T0, ' ms');   
  //Es wurden 156 Übereinstimmungen gefunden
  //Suche in     17 ms
 
  setlength(POIs,0)
end.
 
//erstmalig ohne cache eingelesen
 Alles wegschreiben in 1142 ms
209579
 Alles einlesen in     48 ms // ist dann im cache
 
//erstmalig ohne cache eingelesen
Alles einlesen in     280 ms // ~ 75 Mb/s
 
//viele Doppelte, waren wohl mal sortiert, die Daten
  Nummer  ID      Cty   Lon       Lat        Name
  138553 2959628  DE    9.966100  54.313179  Achterwehr
  138554 6553741  DE    7.066670  50.366699  Acht
  138555 2959658  DE    7.066670  50.366669  Acht
  138556 6556538  DE   12.933300  48.966702  Achslach
  138557 2959660  DE   12.935110  48.971710  Achslach
  138558 6553459  DE    7.650000  49.750000  Abtweiler
  138559 2959747  DE    7.650000  49.750000  Abtweiler
  138560 6555574  DE   10.001940  48.894958  Abtsgmünd
  138561 2959766  DE   10.001720  48.895031  Abtsgmund
  138562 2959771  DE   12.725260  51.889839  Abtsdorf
  138563 6549952  DE   10.766700  51.250000  Abtsbessingen
  138564 2959773  DE   10.766670  51.250000  Abtsbessingen
  138565 6552160  DE    9.283330  53.966702  Aebtissinwisch
  138566 2959779  DE    9.283330  53.966671  Aebtissinwisch
  138567 6554718  DE    6.600000  49.783298  Aach
  138568 2959944  DE    6.600000  49.783329  Aach
  138569 6552984  DE    7.816670  52.566700  Eggermühlen
  138570 3204947  DE    7.816670  52.566669  Eggermuhlen
  138571 6554377  DE    6.997500  49.760899  Thalfang
  138572 3204961  DE    7.000000  49.750000  Thalfang
...
  138843 7932486  DE    9.993610  53.531109  Kleiner Grasbrook
  138844 8051091  DE   11.193520  54.437801  Fehmarn
  155517 2940418  DE   10.381910  52.090271  Calbecht
  155518 2887164  DE   10.383330  52.066669  Kniestedt
 

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: große JSON Datei

Beitrag von mse »

sstvmaster hat geschrieben:

Code: Alles auswählen

Einlesen in 5335 ms
Zeilen 209579
Alles durchsuchen in 577 ms

Hier auf meinem Laptop: Pentium T4500, 4GB, SSD, W7_32
Laz 1.8.4 + Trunk

Mit den JSON Routinen von hier:
https://gitlab.com/mseide-msegui/mseide ... sejson.pas
komme ich auf 0.755s zum Einlesen, das Laden des Gitters dauert nochmals 45ms. FPC "getjson()" benötigt zum Vergleich 2.299s.
Das Projekt ist hier:
https://gitlab.com/mseide-msegui/mseuni ... /json/city
Vermutlich wäre es das Beste, aus der Datei eine Sqlite3 Datenbank zu machen und bei der Auswahl die Werte länderweise in einen Lookupbuffer zu laden. Bitte melde dich, wenn du daran interessiert bist.
Dateianhänge
json.png

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: große JSON Datei

Beitrag von Horst_h »

Hallo,

ich habe mein Programm wieder verändert und jsonparser wie wp_xyz eingesetzt hat, um einmalig ein File of TCity zu erstellen.
Diese ist schon entsprechend nach Land-> Name-> ID sortiert.
Nach einmaligem Einlesen kann man dann für die Länder ja Start und Endindices speichern.
Aber bei nur 16 ms für alle Springfield aus 209000 Einträgen muss man das ja nicht unbedingt.

Code: Alles auswählen

program JsonTest2;
uses
  SysUtils,
  Classes,
  fpjson, jsonparser;
 
const
  cInName  = 'city.list.json';
  cOutName = 'city.dat';
 
type
  TCity = record
    ctLat, ctLon: double;
    ctId: LongInt;
    ctCty: string[3];
    ctName: string[87];
  end;
  tpCity = ^TCity;
  TPointerList = array of Pointer;
  TCompFunc = function(A,B:Pointer):NativeInt;
 
var
  T1, T0: int64;
  POIs : array of TCity;
  pPOIs : TPointerList;
 
function CompID(A,B:Pointer):NativeInt;
begin
  result := tpCity(A)^.ctID-tpCity(B)^.ctID;
end;
 
function CompName(A,B:Pointer):NativeInt;
begin
  result := ORD(tpCity(A)^.ctName>=tpCity(B)^.ctName)-
            ORD(tpCity(A)^.ctName<=tpCity(B)^.ctName);
end;
 
function CompCty(A,B:Pointer):NativeInt;
begin
  result := ORD(tpCity(A)^.ctCty>=tpCity(B)^.ctCty)-
            ORD(tpCity(A)^.ctCty<=tpCity(B)^.ctCty);
end;
 
procedure mergesort(a: TPointerList;CompFunc:TCompFunc);
var
  B : TPointerlist;
 
  procedure merge(links,rechts:NativeInt);
  var
    pLinks : ^Pointer;
    i,j,k,mid :NativeInt;
  begin
    If links<rechts then
      begin
      // Mittleres Element bestimmen und rekursiver Abstieg
      mid := (rechts+links) div 2;
      merge(links, mid);
      merge(mid+1, rechts);
      //schon sortiert?
      IF CompFunc(A[Mid],A[Mid+1])<=0 then 
        EXIT;
      // Mischen der sortierten Unterfelder
      // untere Hälfte der Daten in Hilfsfeld kopieren
      move(A[links],B[0],(mid-links+1)*SizeOf(Pointer));
      i := 0;
      j := mid+1;
      k := links;
      pLinks := @A[k];
      //einsortieren der beiden Hälften
      while (k<j) AND (j<=Rechts) do
        begin
        IF CompFunc(B[i],A[j])<=0 then
          begin
          plinks^ := B[i];
          inc(i);
          end
        else
          begin
          pLinks^ := A[j];
          inc(j);
          end;
        inc(k);
        inc(pLinks);
        end;
//    Rest in B nach A kopieren
//    move(B[i],A[k],(j-k)*SizeOf(Pointer));     
      while (k<j) do
        begin
          pLinks^ := B[i];
          inc(pLinks);         
          inc(k);
          inc(i);
        end;
    end;   // Ende von "if" (Abbruchbedinung)
  end;     // Ende von "procedure"
 
begin
  setlength(B,(High(A)+1) div 2+1);
  merge(0,High(A));
  setlength(B,0);
end;
 
 
procedure ConvertJson(InName, OutName: string);
var
  f : file of TCity;
  stream: TFileStream;
  json: TJSONData;
  parser: TJSONParser;
  jArr: TJSONArray;
  jcoord: TJSONObject;
  t: TDateTime;
  j,i: Integer; 
begin
  t := now;
  json := nil;
  try
    stream := TFileStream.Create(InName, fmOpenRead or fmShareDenyWrite);
    try
      parser := TJSONParser.Create(Stream, []);
      try
        json := parser.Parse;
      finally
        parser.Free;
      end;
    finally
      stream.Free;
    end;
    j :=  json.Count;
    SetLength(POIs, j);
    SetLength(pPOIs,j);
 
    jArr := TJSONArray(json);
    for j := 0 to jArr.Count-1 do
    Begin
      with POIs[j],TJSONObject(jArr[j]) do
      begin
        ctID   := Find('id').AsInteger;
        ctName := Find('name').AsString;
        ctCty  := Find('country').AsString;
        jcoord := Find('coord') as TJSONObject;
        ctLon  := jcoord.Find('lon').AsFloat;
        ctLat  := jcoord.Find('lat').AsFloat;
      end;
      pPOIs[j] :=@POIs[j];
    end
    j := jArr.Count;       
 
  finally
    json.Free;
  end;
 
  writeln('Einlesen in ',FormatDateTime('s.zzz"s"', now-t));
  //Sortieren nach ID -> Name -> Land
  mergesort(pPOIs,compID)
  mergesort(pPOIs,compName)
  mergesort(pPOIs,compCty);
  //-> Innerhalb des Landes nach Name sortiert und bei gleichem Namen nach ID
  Assign(f,OutName);
  Rewrite(f);
  dec(j);
  For i := 0 to j do
    write(f,tpCity(pPOIs[i])^);   
  CloseFile(f);
end;
 
  procedure ReadPOIs(OutName:String);
  var
    f:File of TCity;
    i,j : integer;
  Begin
    Assign(f,OutName);
    Reset(f);
    i := FileSize(f);
    writeln(i);
    setlength(POIs,i);
    setlength(pPOIs,i);
    dec(i);
    For j:= 0 to i do
    Begin
      Read(f,POIs[j]);
      pPOIs[j] :=@POIs[j];
    end
    CloseFile(f);
  end;
 
  procedure OutPOI(POIidx:NativeInt);
  Begin
    with tpCity(pPOIs[POIidx])^ do
      writeln(POIidx:8,ctID:8,ctCty:4,ctLon:12:6,ctLat:11:6,'  ',ctName);
  end;
 
  procedure CheckCountry(s:String);
  var
    i,cnt : NativeInt;
  Begin
    IF length(s)<> 2 then
      EXIT;
    s:= Uppercase(s);
    cnt := 0;
    For i := 0 to High(pPOIs) do
      If s = tpCity(pPOIs[i])^.ctCty then
      Begin
        inc(cnt);
        OutPOI(i);
      end;
    writeln('Es wurden ',cnt,' Übereinstimmungen gefunden');   
  end
 
  procedure CheckName(s:String);
  var
    i,cnt : NativeInt;
  Begin
    s:= lowercase(s);
    cnt := 0;
    For i := 0 to High(pPOIs) do
      If POS(s,lowercase(tpCity(pPOIs[i])^.ctName)) = 1 then
      Begin
        OutPOI(i);
        inc(cnt);
      end
    writeln('Es wurden ',cnt,' Übereinstimmungen gefunden');   
  end;
 
begin
  IF FileExists(cOutName) then
  Begin
    T0 := GetTickCount64;
    ReadPOIs( cOutName);
    T1 := GetTickCount64;
    Writeln(' Alles einlesen in     ', T1 - T0, ' ms');
  end
  else
  Begin     
    IF NOT(FileExists(cInName)) then
    Begin
      writeln('Keine passende Datei ',cInName,' vorhanden');
      HALT(-2);
    end;
    T0 := GetTickCount64;   
    ConvertJson(cInName, cOutName);
    T1 := GetTickCount64;
    Writeln(' Alles wegschreiben in ', T1 - T0, ' ms');
  end;
 
  T0 := GetTickCount64;
  CheckCountry('YX');
  T1 := GetTickCount64;
  Writeln(' Suche in     ', T1 - T0, ' ms');   
 
  T0 := GetTickCount64;
  CheckName('Springfield');
  T1 := GetTickCount64;
  Writeln('Suche in     ', T1 - T0, ' ms');   
 
  setlength(pPOIs,0)
  setlength(POIs,0)
end.

Ausgabe

Code: Alles auswählen

Einlesen in 2.135s
Alles wegschreiben in 2519 ( -2135 )  ms  <= das ist auch das dreimalige sortieren und schreiben in die Datei drin
Es wurden 0 Übereinstimmungen gefunden
 Suche in     9 ms
  Nummer   ID     CTY  Lon        Lat          Name
   10907 2148650  AU  152.483337 -24.916670  Springfield
   10908 6693094  AU  152.924881 -27.667570  Springfield Lakes
   23246 6154178  CA  -64.865494  44.633450  Springfield
  203996 4173892  US  -85.611320  30.153259  Springfield <= Ab hier sieht man gut, das ID aufsteigend sind
  203997 4224162  US  -81.311501  32.372410  Springfield
  203998 4250542  US  -89.643707  39.801720  Springfield
  203999 4309329  US  -85.222183  37.685341  Springfield
  204000 4409896  US  -93.298241  37.215328  Springfield
  204001 4525353  US  -83.808823  39.924229  Springfield
  204002 4561407  US  -75.320190  39.930672  Springfield
  204003 4659557  US  -86.885002  36.509209  Springfield
  204004 4733920  US  -96.482758  31.659611  Springfield
  204005 4787117  US  -77.187202  38.789280  Springfield
  204006 4951788  US  -72.589813  42.101479  Springfield
  204007 5010917  US  -85.239159  42.326431  Springfield
  204008 5048394  US  -94.975822  44.238850  Springfield
  204009 5079488  US  -96.134460  41.081940  Springfield
  204010 5093030  US  -72.033417  43.495071  Springfield
  204011 5104952  US  -74.317230  40.704910  Springfield
  204012 5232077  US  -97.897293  42.854172  Springfield
  204013 5241423  US  -72.482307  43.298409  Springfield
  204014 5440032  US -102.614357  37.408352  Springfield
  204015 5754005  US -123.022034  44.046242  Springfield
Es wurden 23 Übereinstimmungen gefunden
Suche in     16 ms
 
real    0m2.593s
user    0m2.255s
sys     0m0.336s
 
//viele doppelte Daten unterschiedliche ID
  Nummer  ID      Cty   Lon       Lat        Name
   68386 2808243  DE   13.266670  48.799999  Winden
   68387 2808244  DE    8.183330  48.766670  Winden <=
   68388 6554111  DE    7.833330  50.333302  Winden
   68389 6555024  DE    8.116940  49.097801  Winden <=
 


Was interessant wäre ist die Koordinaten zu Nummern umzurechnen in "Quadrat" 180*(trunc(Lon+180) {-> 0..360°} )+(trunc(Lat+90) {->0°-180°} )
Das wäre 64800 Nummern und die Nachbarn liessen sich leicht bestimmen.Ein Längengrad wäre +- 180 ein Breitengrad +-1

Gruß Horst

Antworten