Laden...

Datei einlesen, splitten und Speicher. Programm hängt nach 30 Dateien.

Erstellt von Pardasus vor 7 Jahren Letzter Beitrag vor 7 Jahren 4.883 Views
P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren
Datei einlesen, splitten und Speicher. Programm hängt nach 30 Dateien.

Moin,
ich habe hier etwas kurioses wo ich einfach nicht den Fehler finden kann.
Ich habe simple Text-Dateien. In einer Datei können mehrere Tage enthalten sein. Diese lasse ich komplett einlesen, auf einzelne Tage Splitten und neu Speichern.
Das Programm läuft wunderbar, ohne jegliche Fehlermeldung. Aber wenn er ca. 25-30 Dateien geschrieben hat, hört er einfach auf. Ich habe auch die Dateien, die eingelesen werden, ausgetauscht ohne das sich an dem verhalten etwas ändert.

Ich konnte es auf die stelle reduzieren, in der die Datei geschrieben wird. Irgendwo dort muss der Fehler sein. Ohne diese stelle, durchläuft er alle Dateien. Also irgendwo in der "foreach (var block in dateBlocks)" schleife passiert es.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace FileSplitter
{
	public class FileCheck
	{
		public void ReadFiles(List<FileInfo> files)
		{
			foreach (var file in files) {
				var dateBlocks = dateBlock(ReadFileLines(file.FullName));
				var fileName = Path.GetFileNameWithoutExtension(file.FullName);
				
				foreach (var block in dateBlocks) {
					var tag = block.Select(b => "(" + b.Substring(34,2) + "." + b.Substring(36,2) + "." + b.Substring(38,4) + ")");
					File.WriteAllLines(fileName + "_" + tag.First() + ".txt" , block);
				}
			}
		}
		
		private IEnumerable<string> ReadFileLines(string file)
		{
			using (var reader = File.OpenText(file)) {
				string currentLine = reader.ReadLine();
				while (currentLine != null) {
					yield return currentLine.Replace("\x1A", "");
					currentLine = reader.ReadLine();
				}
			}
		}
		
		private IEnumerable<IList<string>> dateBlock(IEnumerable<string> lines)
		{
			IList<string> currentBlock = null;
			string findDate = null;
			
			foreach (var line in lines) {
				if(currentBlock == null) {
					findDate = line.Substring(34,8);
					currentBlock = new List<string>();
				}
				
				if(findDate == line.Substring(34,8))
					currentBlock.Add(line);
				else {
					yield return currentBlock;
					currentBlock = new List<string>();
					findDate = line.Substring(34,8);
					currentBlock.Add(line);
				}
			}
			yield return currentBlock;
		}
	}
}

4.931 Beiträge seit 2008
vor 7 Jahren

Hallo,

ich denke das liegt am "yield return inside using" (such mal danach im Internet!).
Das Dispose auf File.OpenText wird erst aufgerufen, wenn der Enumerator komplett durchlaufen wurde (und dadurch hast du jetzt zu viele offene Datei-Handles).

Teste mal


var dateBlocks = dateBlock(ReadFileLines(file.FullName)).ToList();

(also erst komplett die Datei lesen und dann schreiben)

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@Th69
das mit .ToList() hat leider nicht geklappt.
Dann erzeugt er mir einfach nur Kopien der Dateien, ohne die Tage zu filtern. Aber durchläuft immer noch nicht alle Dateien.

Habe auch mal das "using" komplett weggelassen und ein "reader.Close()" am ende gehangen. Leider ohne erfolg. Das gleiche Ergebnis.
Habe aber auch das gefühl, das je mehr Dateien er bearbeite hat kreuz und quer durch die Dateien springt beim schreiben bzw. Lesen.

EDiT: habe nun mal komplett das Einlesen durch


var dateBlocks = dateBlock(File.ReadAllLines(file.FullName));

ersetzt. Aber das hat auch nichts geändert. Es Liegt wohl ausschließlich am schreiben oder an einlesen der Blöcke.

U
1.688 Beiträge seit 2007
vor 7 Jahren

Wo genau steht denn das Programm, wenn es hängt?

[Artikel] Debugger: Wie verwende ich den von Visual Studio?

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@ujr
das ist ja mein Problem. Es hängt nicht, macht keine Fehlermeldung sonder hört einfach auf Dateien zu erstellen.
Habe die Quelldateien schon gegen andere Daten ausgetauscht, aber er hört einfach mitten drinnen auf bzw. ist wohl fertig.

Habe nun festgestellt, dass er eine schon fertige Datei nochmals neu schreibt. Obwohl diese bereits schon fertig ist.

U
1.688 Beiträge seit 2007
vor 7 Jahren

Habe nun festgestellt, dass er eine schon fertige Datei nochmals neu schreibt. Obwohl diese bereits schon fertig ist.

Dann verwende den Debugger oder Logging, um den gesamten Ablauf nachzuvollziehen. Ist denn z. B. die ReadFiles übergebene Liste korrekt?

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@ujr
Ja ist sie, wie schon ganz oben geschrieben. Taucht der Fehler innerhalb der


foreach (var block in dateBlocks) {
   var tag = block.Select(b => "(" + b.Substring(34,2) + "." + b.Substring(36,2) + "." + b.Substring(38,4) + ")");
   File.WriteAllLines(fileName + "_" + tag.First() + ".txt" , block);
}

auf. Ohne diese, durchläuft er alle Dateien.

Habe nun festgestellt, dass er eine schon fertige Datei nochmals neu schreibt. Obwohl diese bereits schon fertig ist.

das spiegelt sich auch im Debugger wieder. Nur weis ich nicht, warum er diese macht. Es ergibt kein Sinn...

U
1.688 Beiträge seit 2007
vor 7 Jahren

Dann steck die Schleife mal in einen try/catch Block und lass Dir Exceptions ausgeben.

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@ujr
das mit den Try / Catch war eine gute Idee. Habe den Fehler nun finden können 😃

Diese Dateien enthalten hin und wieder ein EOF Zeichen "\x1A". welches ich mit


yield return currentLine.Replace("\x1A", "");

herausfilter. In älteren Version von dem Programm, hat diese auch immer gut Funktioniert. Jetzt filtert er aber das Zeichen nicht raus. Was Probleme im Nachgang gibt.

D
985 Beiträge seit 2014
vor 7 Jahren

Habe nun festgestellt, dass er eine schon fertige Datei nochmals neu schreibt. Obwohl diese bereits schon fertig ist.
das spiegelt sich auch im Debugger wieder. Nur weis ich nicht, warum er diese macht. Es ergibt kein Sinn...

Ob es Sinn macht kann ich nicht beurteilen, nur dass du es exakt so programmiert hast.

Enthält die Datei foo.txt den folgenden Inhalt:


1...01012017...
2...01012017...
3...02012017...
4...02012017...
5...01012017...
6...01012017...

dann erzeugst du daraus folgende Blöcke


1...01012017...
2...01012017...


3...02012017...
4...02012017...


5...01012017...
6...01012017...

und schreibst diese ncheinander in die Dateien foo_01.01.2017.txt, foo_02.01.2017.txt und (nochmal) foo_01.01.2017.txt.

Gesehen?

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@Sir Rufo
ups 😉 da hast du recht, aber bis jetzt ist mir eine Datei noch nicht unter gekommen wo ein Datum nach einem anderen, nochmals vorgekommen ist.

Hast du eine schnelle Lösung diese vielleicht komplett auszuschließen?

p.s.: suche das Datum nun mit einem RegEx => [0123][0-9][01]0-9[01][0-9] das sollte für dieses Jahrtausend reichen 😉 ich hoffe ja nicht, dass jemand in nächsten Jahrtausend das Programm noch nutzt...

T
2.219 Beiträge seit 2008
vor 7 Jahren

@Pardasus
Anstelle eines RegEx solltest du mit DateTime TryParseExact mit dem entsprechenden Pattern, ddMMyyyy, das Datum direkt parsen lassen.
Dann hast du dein Datum und musst nicht die einzelnen Teile des Datum per Substring auslesen sondern kannst den Part einmal auslesen und parsen lassen.

Nachtrag:
Anbei solltest du deine ReadFileLines Methode aus meiner Sicht verwerfen.
Wenn du deine Dateien sauber schreibst, reicht hier ein File.ReadAllLines.

Du solltest deine Daten in eine saubere Struktur packen.
Aktuell hast du auch eine Methode dataBlock.
Die Benennung solltest du dabei überarbeiten, da diese falsch und nichts sagend ist.
Hier wäre es Sinnvoll wenn du die Methode z.B. ParseDataBlock nennen würdest und dann ein entsprechendes Objekt lieferst.

Nach dem parsen der Datei solltest du eine Lsite von DataBlock Objekten haben.
Dann kannst du diese Liste über das Datum gruppieren und in die jeweiligen Dateien schreiben.
Dann machst du nicht für jede Zeile einen Schreibvorgang sondern pro Datum einen.
Wenn der Fall von Sir Rufo doch eintrieft, musst du die selbe Datei mehrfach beschreiben, was nicht sinnvoll ist.

Je nach Menge der Dateien und Zeilen die du verarbeiten musst, kann dies dann auch eine größe IO Last produzieren ist sinnvoll wäre.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@T-Virus
das mit den DatumParsen hatte ich schon umgestellt (siehe weiter oben). Suche mir das Datum nun mit


var tag = block.Select(b => Regex.Match(b, "[0123][0-9][01][0-9](20)[01][0-9]").Value);

Jetzt wo ich drüber nachdenke, wäre es ja wirklich viel leichter, würde ich mit ReadAllLines alles einlesen und dann mit Linq Datums-Gruppen Bilden und diese dann einfach Speicher.

Was mich aber dennoch Interessiert, warum das


yield return currentLine.Replace("\x1A", "");

nicht funktioniert. In einem anderen Programm, lässt sich damit das EOF Zeichen aus einem string einwandfrei entfernen...

p.s.: @T-Virus, ich hatte es nicht DataBlock sonder DateBlock genannt 😉

5.657 Beiträge seit 2006
vor 7 Jahren

Hi Pardasus,

warum verwendest du nicht den DateTime-Datentyp und die TryParseExact-Methode wie von T-Virus vorgeschlagen. Dafür braucht man kein RegEx.

Und wozu meinst du, das EOF-Byte auslesen zu müssen? Der FileReader weiß doch, wann die Datei zu Ende ist...

Weeks of programming can save you hours of planning

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

@MrSparkle / @T-Virus
da habt ihr recht. Aber muss ich dann nicht wieder in ein String zurück Konventieren, wenn ich diese als Dateinamen verwenden will?

Für das EOF Interessiere ich mich nicht direkt. Die Dateien, die ich verarbeite, werden von einen anderen Programm oft aneinander gehängt. Dann gibt es immer mal eine Zeile in der Datei, wo das EOF Zeichen die ganze Zeile nach rechts verschiebt.

16.806 Beiträge seit 2008
vor 7 Jahren

Regex ist >100 mal langsamer als ein TryParse.
Man konvertiert dann auch kein DateTime in ein String mehr, sondern man formatiert. Immer noch schneller.

P
Pardasus Themenstarter:in
63 Beiträge seit 2016
vor 7 Jahren

Ich habe diese nun so gelöst


public class FileSplitter
{	
	public void ReadFiles(List<FileInfo> files)
	{
		foreach (var file in files) {
			var filialName = Path.GetFileNameWithoutExtension(file.FullName);
			var datenWithoutEOF = new List<string>();
			foreach (var line in File.ReadAllLines(file.FullName)) {
				if(line.Length > 1)
					datenWithoutEOF.Add(line.Replace("\x1A", ""));
			}
			var tage = datenWithoutEOF.GroupBy(line => line.Substring(34,2) + "." + line.Substring(36,2) + "." + line.Substring(38,4));
			foreach (var tag in tage) {
				File.WriteAllLines(filialName + "_" + tag.Key + ".txt", tag);
			}
		}
	}
}

Dieser Code läuft nun wunderbar, aber ich bin mir sicher, ihr habt noch ein paar Tips für mich.
Ich muss zugeben, wie ich das mit dem DateTime einbinden soll, nicht ganz verstanden zu haben.

Dadurch, dass ich nun das EOF Zeichen aus der Datei entfernt habe, kann ich mit Substring auch wieder exakt Arbeiten.

Warum EOF-Zeichen?
Ein anderes Programm, kopiert mehrerer Dateien aneinander. Komischerweise taucht danach das EOF Zeichen in der Datei auf.
Es verschiebt mir dann ein paar Zeilen um ein Zeichen nach rechts und am ende der Datei findet sich auch noch ein einzelnes EOF Zeichen.
Ich habe eine ganze weile gebraucht, um heraus zu bekommen, was das überhaupt für ein Zeichen ist.