.csv Export aus einer list

Felix123

Mitglied
Hallo zusammen,

ich bastel gerade eine Methode, die mir aus einer HashMap mit Dateipfaden als Keys, und den Dateiinhalten als lists .csv Dateien erzeugen soll. Strengenomen handelt es sich nichtmal wirklich um .csv sondern .log Dateien.

Ich laufe durch die Map und erzeuge mir durch den Key den Dateipfaden bzw. die Datei in die ich schreiben will und schreibe dann die Liste (also den Wert zum Key) in die Datei.
Java:
   public static void ExportCSV(Map<Path, List<String>> hashMap) throws FileNotFoundException{
      
        for (Map.Entry<Path, List<String>> entry : hashMap.entrySet()) {
            System.out.println("Currently written file: " + entry.getKey());
            Path key = entry.getKey();
            List value = entry.getValue();
            PrintWriter pw = new PrintWriter(new File(key.toString() + "-Copy"));
            StringBuilder sb = new StringBuilder();
            sb.append(value);
            pw.write(value.toString());
            pw.close();
        }     
}

Dabei kommt dann in der erzeugten Datei folgendes raus:
[0.004606,14.58012568950653, 0.01466,14.58012568950653, 0.02.....

Was ich haben will sieht aber so aus:
0.004606,14.58012568950653
0.01466,14.58012568950653
0.02.....

Wie geht man denn bei sowas am blödesten vor?
Zum Beispiel könnte man ja nach Kommas suchen und jedes ungerade Komma durch eine neue Zeile ersetzen?
Oder ist es vll besser den Inhalt der Datei in der HashMap nicht als List zu speichern sondern unter etwas anderem?
Hinzu kommt noch, dass ich eigentlich nicht gleich die komplette Datei schreiben will, sondern erst eine bestimme Anzahl an Zeilen. Dann möchte ich für einen bestimmten Zeitwert warten und dann die nächste Ladung in die Datei exportieren
 
Wie geht man denn bei sowas am blödesten vor?
Zum Beispiel könnte man ja nach Kommas suchen und jedes ungerade Komma durch eine neue Zeile ersetzen?
Wenn das mit dem "am blödesten" ernst gemeint war, wäre die Lösung gut.😀
Spass beiseite:
Einfach eine List mit ihrer toString() Methode in einen String umzuwandeln, lässt dir halt keine Freiheitsgrade.
Leider schweigst du dich darüber aus, wie die Ausgangsdateien aufgebaut sind, und wie die einzelnen Elemente in der List aussehen. Aus dem Ergebnis der toString()-Methode lässt sich folgern, dass die List Zahlenwerte enthält (oder Strings, die wie Zahlenwert aussehen).
Wenn du die paarweise in einzelnen Zeilen einer Ausgabedatei schreiben möchtest, solltest du über die Liste iterieren, die Iterationen zählen, jedes Element rausschreiben, und dann prüfen, ob die biesherige Anzahl an Iterationen druch 2 teilbar ist. Falls ja, einen Zeilenumbruch rausschreiben, ansonsten eine Komma.
Mit dieser Methode sollte es auch keine großes Problem sein, zu warten, bis die Anzahl der Iterationen in einem bestimmten Zahlenbereich liegt.
 
Okay danke,
die Datei, die ich erzeuge, soll genauso aussehen wie die Original .csv Datei. Im Prinzip brauche ich nur eine Kopie, die aber halt nicht gleich vollständig gemacht wird sondern blockweise, weil ich damit reale log-files simulieren möchte, die Stück für Stück größer werden.
Also einfach zwei double Werte pro Zeile, mit einem Komma getrennt. Momentan lese ich aus der Originaldatei jede Zeile aus und schreibe diese dann nacheinander in eine list. Ich lese also, wie du gesagt hast, jede Zeile als String ein.
 
Oder ist es vll besser den Inhalt der Datei in der HashMap nicht als List zu speichern sondern unter etwas anderem?
MMn Ja.

Dein Inhalt ist schließlich keine Liste von Strings, sondern eine Liste von Tupeln von double-Werten.
Ich würde ein passende Klasse für die Daten nutzen, der Exporter hat dann eine Methode, um ein Wertepaar passend zu printen. Aus der Map dann jeweils eine Liste holen, daraus die SubListe der n jetzt zu printenden, die Tupel daraus jeweils zu 'nem String und mit eigener Schleife oder String:join oder Streams zusammenfassen bzw an die Datei anhängen
 
Habe es jetzt so gemacht wie Harry Kane es vorgeschlagen hat. Durch das Iterieren durch die Einträge der Liste und schreiben mit pw.println(theEntry) habe ich mit einer Zeile Code das Ausgabeformat, das ich brauche.

Im Prinzip sind das natürlich double-Werte, aber da ich die .csv-Datei nur einlese und wieder exportiere und mit den Werte nichts mache, ist es am einfachsten es einfach gleich bei Strings zu belassen.
 
Ich versuche jetzt gerade meine Methode so zu erweitern, dass blockweise bei jeder Datei 250 Einträge geschrieben werden. Sobald in alle Dateien 250 Einträge geschrieben wurden sollen dann die nächsten 250 folgen.
Der Code den ich unten gepostet habe, macht genau das.
Wenn aber alle Blöcke geschrieben sind bleibt ein Rest-Block übrig, welcher keine 250, sondern x Einträge hat (aber das ist ein kleines Problem).
Womit ich im Moment etwas überfordert bin ist, dass ich vor meine for-Schleife, die die einzelnen Dateien durchgeht, nochmal eine for-Schleife machen muss, die die Blöcke hochzählt bis die Listen abgearbeitet sind.
Dann muss ich mir ja am Anfang schon merken, wieviele Einträge jede Liste hat oder?
Die äußere for-Schleife läuft dann von 0 bis alle Blöcke durch sind.
Aber die Abbruchbedingung der äußersten for-Schleife ist ja dann auch noch abhängig von der aktuellen Listenlänge.

Java:
    public static void ExportCSV(Map<Path, List<String>> hashMap) throws FileNotFoundException {
       
        int exportblock = 250;
        for (Map.Entry<Path, List<String>> entry : hashMap.entrySet()) {
            int linecount = entry.getValue().size();
            int blocks = linecount / exportblock;
            int rest = linecount % exportblock;
            Path key = entry.getKey();
            List value = entry.getValue();
            PrintWriter pw = new PrintWriter(new File(key.toString() + "-Copy"));
           
            for (int i = 0; i <= exportblock; i++) {
                pw.println(value.get(i));
            }
            pw.close();
    }
}
 
Müssen die Einträge nach dem schreiben in den Listen bleiben?
Wenn nicht, dann einfach mit remove(0) die zu schreibenden entfernen.
Ist dann nur noch eine Iteration über die Map, und darin eine fori bis 250 oder leere Liste, in der geschrieben wird.
 
Länger werden ist ja kein Problem, relevant ist nur, ob die alten Einträge drin bleiben müssen, nach dem schreiben
 
Ja die bleiben drin. Ich schreibe jeweils 250 Zeilen in alle Dateien rein, wenn das fertig ist sollen die nächsten 250 dazu kommen. Und das so lange bis die Listen in meiner HashMap durchgelaufen sind.
 
So mittlerweile bin ich nun so weit, dass ich als allererstes einen zusätzlichen Ordner erstelle.
In diesen Ordner füge ich dann allen csv-Dateien alle 5 Sekunden blockweise 250 Einträge hinzu.
Habe es zuerst mit einer TimerTask probiert, die die Werte alle 5 Sekunden schreibt. Aber das Canceln der TimerTask funktioniert irgendwie nicht. Naja, jetzt benutze ich einfach Thread.sleep().
Ich merke mir, wieviele Blöcke ich schon geschrieben habe und greife so beim nächsten schreiben der Blöcke auf die Zeilennummer zu, bei der ich im Durchlauf davor aufgehört habe.
Was mir immernoch Kopfzerbrechen bereitet ist, wie ich die Abbruchbedingung für meine äußere for Schleife ermittle (Anzahl der zu schreibenden Blöcke).
Momentan schreibe ich immer nur 3 Blöcke. Habe mir überlegt, dass ich alle Listen durchgehe, und dann einfach mit der kürzesten Liste ausrechne wieviele Blöcke reinpassen. In der Praxis sollte das ausreichend gut funktionieren. Wenn aber im worst case eine Liste nur 5 Einträge hat und alle anderen 1000, dann wäre das doch ziemlich unschön. 😕

Meine übergeordnete Methode:
Java:
    public static void exportCSV(Map<Path, List<String>> hashMap) throws FileNotFoundException, IOException, InterruptedException {

        String newFolderName = "Live";
        createNewFolder(newFolderName);
        int exportblock = 250;
        int blocknumber = 3;
        int writtenblocks = 0;

        for (int i = 1; i <= blocknumber; i++) {
            exportOneBlock(exportblock, writtenblocks,blocknumber, newFolderName, hashMap);
            Thread.sleep(5000);
            System.out.println("geschriebene Blockanzahl:" + writtenblocks);
            writtenblocks++;
        }
    }

und die Methode die für jedes Listenelement 250 Einträge schreibt:
Java:
  public static void exportOneBlock(int exportblock, int writtenblocks, int blocknumber, String newFolderName, Map<Path, List<String>> hashMap) throws FileNotFoundException {

  for (Map.Entry<Path, List<String>> entry : hashMap.entrySet()) {
       Path key = entry.getKey();
       List value = entry.getValue();
       String currentFilename = getFileName(key);
       String currentSavePath = key.getParent().toString() + "\\" + newFolderName + "\\" +
       currentFilename;

       try (FileWriter fw = new FileWriter(currentSavePath, true);
       BufferedWriter bw = new BufferedWriter(fw);
       PrintWriter pw = new PrintWriter(bw)) {
           for (int i = 1; i <= exportblock; i++) {
                pw.println(value.get(i+writtenblocks*exportblock));
             }
            pw.close();
            } catch (IOException e) {
         System.err.println("Error: " + e.getMessage());
           }
  }
 
Ich würde sagen, überlege dir einfach einen maximalen Wertn für exportBlock, bspw. 250.
Wenn die Map aber weniger Einträge hat, dann schreibe einen kleineren Block, oder ist das ein Problem?
Müssen immer genau die gleiche Anzahl Einträge geschrieben werden?

Du könntest einfach intern ermitteln, ob "i+writtenblocks*exportblock" die Größe deiner Values übersteigt, bevor du out of bounce rennst. Demnach könntest du einen bool'schen Wert zurückgeben, der sagt, ob das Blockschreiben erfolgreich war oder nicht und je nachdem brichst du die for-Schleife ab.

Dadurch entstehen aber auch unterschiedlich große Blöcke..
 
Kommen währende exportCSV läuft neue Einträge in die Liste?
Da du eine Abbruchbedingung brauchst, gehe ich mal von Nein aus (ansonsten fehlt der Fall, nach Abbruch kommen neue Daten in die Liste, werden dann aber nie geschrieben...)


Ich würde die Map<Path,List> ein eine neue Map<Path, Queue> überführen, dann über die Map iterieren, für jede Queue einmal das schreiben aufrufen, und wenn nichts geschrieben wurde, aus der Iteration über die Map breaken.
Schreiben einer Queue ist dann nur in fori pollen, und wenn das null zurückgibt daraus breaken
 
@Kababär,
weniger Einträge als 250 ist eigentlich kein Problem. So muss es ja auch eigentlich sein wenn eine Liste z.B. 270 Einträge hat. Dann muss halt ein Block geschrieben werden und beim nächsten Durchlauf sollen eben nur noch die letzten 20 geschrieben werden. Die Listen sollten sich von der Länge her nicht großartig unterscheiden, d.h. normalerweise sollte sich dann nur im letzten Durchgang die Blockgröße je nach Liste ändern. Ich muss also schon wie du sagst überprüfen, ob es noch möglich ist einen kompletten Block zu schreiben. Und wenn ein ganzer Block nicht mehr möglich ist müsste ich ausrechnen wieviele Elemente noch fehlen und den exportBlock dementsprechend anpassen.

@mrBrown,
nein die Listen in der Map sind fix. Das mit Queue muss ich mir mal anschauen, habe ich noch nie benutzt.
 
Ich habe jetzt nochmal alles über den Haufen geworfen. Da ich jetzt auch noch in jede csv-Datei reinschauen muss, um die Auflösung von jedem Signal zu berechnen, habe ich vor, für jedes Schreiben einer Datei einen neuen Thread zu erzeugen.
Allerdings weiß ich nicht wie ich das mache.
Habe jetzt eine extra Klasse erzeugt, die nur dafür zuständig sein soll die csv-Dateien zu schreiben
D.h. ich bräuchte n-Threads die alle gleichzeitig starten und diese Klasse auzuführen:

Java:
public class BlockWriter extends Thread {

    GlobalVariables globalVariables = new GlobalVariables();
    int writtenBlocks = 0;
  
    public void writeCsvFile(int writeableBlocks, int blocksize,  int rest, Path path, List<String> list) throws InterruptedException {
        GlobalVariables globalVariables = new GlobalVariables();
        System.out.println("Write file: " + path.getFileName() + "with " + writeableBlocks + "Blocks");
         if (writeableBlocks==0){
                    writeableBlocks = 1;
                    writtenBlocks = 1;
                }
        for (int i = 1; i <= writeableBlocks; i++) {
            exportBlock(writeableBlocks, blocksize, rest, path, list);
            Thread.sleep(globalVariables.getSleeptime());
        }
     
}
 
   public void exportBlock(int writeableBlocks,  int blocksize,  int rest, Path path, List<String> list){
// Hier wird jeweils ein Block in die Datei geschrieben    
}
}

Und in dieser Methode einer anderen Klasse sollen die Threads erzeugt werden:
Java:
public class CsvRecreator {
public static void analyseContent(Map<Path, List<String>> hashMap) throws IOException, InterruptedException {

        GlobalVariables globalVariables = new GlobalVariables();
        int sleeptime = globalVariables.getSleeptime();

        //Hier gehe ich durch alle Dateien und deren Inhalt
        for (Map.Entry<Path, List<String>> entry : hashMap.entrySet()) {
          
       //  Mit dem aktuellen HashMap Key=Dateipfad die zugehörige Liste auslesen
      //      (Anzahl der Einträge, Auflösung usw.)
          
     // Mit den gewonnen Informationen möchte ich jetzt hier in jedem Durchlauf einen Thread erstellen,
    //der die Methode writeCsvFile() der Klasse BlockWriter ausführt.
    //Bzw. eigentlich erst nach der for-loop, wenn alle Threads erstellt wurden, um diese zeitgleich zu starten.
            BlockWriter exportCsv = new BlockWriter();
            exportCsv.writeCsvFile(writeableBlocks, blocksize, rest, path, currentList);

        }
    }
}
 
Lass deinen BlockWriter Runnable implementieren (statt extends Thread), und guck dir dann mal ExecutorService an.

Warum schleppst du die ganzen Block-Infos mit, sind die für irgendwas wichtig?
 
Anstatt die Block-Infos mitzuschleppen, ists vermutlich sinnvoller, nur den zu schreibenden Teil zu übergeben.
(Wobei ich bei statischen Listen eh nicht verstehe, warum man sie nicht ganz auf einmal schreibt...)
 
Das "mitschleppen" ist halt einfacher (finde ich). Ich muss sie in der Loop nur einmal für jede Datei sammeln und jeweils den Auftrag geben, dass die Infos verarbeitet werden sollen.
Ich muss die Listen Stück für Stück schreiben, weil ich log-files für mein GUI simuliere, die auch blockweise in csv Dateien geschrieben werden.
 
Okay jetzt scheint es zu funktionieren. Das Übergeben der Parameter war aber echt etwas umständlicher als gedacht.
In der CsvRecreator-Klasse bzw. analyseContent()-Methode mache ich in der for-Schleife folgendes:
Java:
public class CsvRecreator{

public static void analyseContent(Map<Path, List<String>> hashMap) throws IOException, InterruptedException {

 for (Map.Entry<Path, List<String>> entry : hashMap.entrySet()) {

// Dateiinformationen sammeln

  MyRunnable myRunnable = new BlockWriter().new MyRunnable( writeableBlocks,  blocksize,  rest,  path, currentList);
  new Thread(myRunnable).start();
}
}
}
Also für jede Datei eine neue Runnable erzeugen.


Die BlockWriter-Klasse ist etwas komplizierter geworden. Da habe ich nochmal eine Nested Class reingepackt:
Java:
public class BlockWriter {
  
    GlobalVariables globalVariables = new GlobalVariables();
    int writtenBlocks = 0;

public void writeCsvFile(int writeableBlocks, int blocksize,  int rest, Path path, List<String> list) throws InterruptedException {
//execute exportBlock() method
}

  public void exportBlock(int writeableBlocks,  int blocksize,  int rest, Path path, List<String> list){
//Write blocks into files
}

public class MyRunnable implements Runnable {

        private final int writeableblocks;
        private final int blockSize;
        private final int restLines;
        private final Path currentPath;
        private final List<String> currentList;

        public MyRunnable(int writeableblocks, int blockSize, int restLines, Path currentPath, List<String> currentList) throws InterruptedException {
            this.writeableblocks = writeableblocks;
            this.blockSize = blockSize;
            this.restLines = restLines;
            this.currentPath = currentPath;
            this.currentList = currentList;
        }

        @Override
        public void run() {
            try {
                writeCsvFile(writeableblocks, blockSize, restLines, currentPath, currentList);
            } catch (InterruptedException ex) {
                Logger.getLogger(BlockWriter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
 
MyRunnable hätte man sich auch sparen können, BlockWriter hätte Runnable auch direkt implementieren können (oder halt mit Lamdas arbeiten)
 
So hatte ich es auch zuerst versucht. Aber wenn ich meine File-Infos in der BlockWriter-Klasse deklarieren will, bekomme ich den Fehler "variable not initialized in the default constructor". Was ist da das Problem?
 

Zurück
Oben