C# Datein Splitten in mehrere kleinere, geht aber mit falschen Zeichencode

roker002

Commander
Registriert
Dez. 2007
Beiträge
2.061
Ich habe ein Programm geschrieben, dass die Datei in mehrere kleinere Dateien splittet. Ich suche jeweils nach Zeilenende und splitte den den Text.

Wenn ich es Synchron mache, funktioniert alles wunderbar. Der Nachteil, der ist langsam wenn man große Dateien splitten will. Daher habe ich das ganze Asynchron geschrieben. Das splitten geht auch, aber ich bekomme nur in der letzte Datei lesbare Zeichen (Falscher Zeichencode?).

Code:
public static void SplittFile(SqlString path, long size)
{
    List<BackgroundWorker> list = new List<BackgroundWorker>();
    using (FileStream fs = new FileStream(path.Value, FileMode.Open, FileAccess.Read))
    {
        fs.Seek(0, SeekOrigin.Begin);
        using (StreamReader r = new StreamReader(fs, Encoding.Default))
        {
            /// Get min Count for splitting the file depends on File Stream Size and cut Size
            double l1 = (double)fs.Length / (double)size;
            long len = (long)Math.Round((l1) + .5);
            for (int i = 1; i <= len; i++)
            {
                BackgroundWorker bgw = new System.ComponentModel.BackgroundWorker();
                bgw.DoWork += new System.ComponentModel.DoWorkEventHandler(bgw_DoWork);

                bgw.RunWorkerAsync(new SplitFileInfo(path.Value, size, i, (i-1)*size, r.CurrentEncoding));
                list.Add(bgw);
            }
            while (true)
            {
                var l = new BackgroundWorker[list.Count];
                list.CopyTo(l);
                foreach (var w in l)
                {
                    if (w != null)
                    {
                        if (w.IsBusy == false)
                            list.Remove(w);
                    }
                }
                if (list.Count == 0)
                    break;
            }
        }
    }
}

static void bgw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    var info = e.Argument as SplitFileInfo;
    long size = info.Size;

    if (info.StartPosition ==0)
    {
    }
    using (FileStream fs = new FileStream(info.Path, FileMode.Open, FileAccess.Read))
    {
        long pre_write_pos = info.StartPosition;
        long nextbreak = 0;
        byte[] write;
        fs.Seek(0, SeekOrigin.Begin);
        System.Text.Encoding encoder = info.Encoder;
        using (var r = new StreamReader(fs, encoder))
        {

            if (pre_write_pos + size > fs.Length)
            {
                if (pre_write_pos > fs.Length)
                {
                    pre_write_pos = fs.Length - (pre_write_pos - fs.Length);
                    size = fs.Length - pre_write_pos - 1;
                }
                else
                {
                    size = fs.Length - pre_write_pos - 1;
                }
            }
            fs.Seek(pre_write_pos, SeekOrigin.Begin);
            String s = r.ReadLine();

            if (s != null)
            {
                long len = s.ToCharArray().LongLength;
                pre_write_pos += len;
                size -= len;
            }

            fs.Seek(pre_write_pos + size, SeekOrigin.Begin);

            s = r.ReadLine();
            if (s != null)
            {
                nextbreak = s.ToCharArray().LongLength;
                byte[] output = new byte[size + nextbreak];

                fs.Seek(pre_write_pos, SeekOrigin.Begin);

                if (fs.Read(output, 0, output.Length) != 0)
                {
                    String fn = Path.Combine(Path.GetDirectoryName(info.Path), Path.GetFileNameWithoutExtension(info.Path));
                    String p = String.Concat(fn, "_", info.Number, System.IO.Path.GetExtension(info.Path));
                    s = encoder.GetString(output, 0, 4);
                    if (s.Contains("\r\n"))
                    {
                        write = new byte[output.LongLength - 2];
                        write = output.Skip(2).ToArray();
                    }
                    else if (s.Contains("\r") || s.Contains("\n"))
                    {
                        write = new byte[output.LongLength - 4];
                        write = output.Skip(4).ToArray();
                    }
                    else
                    {
                        write = output;
                    }
                    using (var w = new FileStream(p, FileMode.Create, FileAccess.Write))
                    {
                        w.Write(write, 0, write.Length);
                    }
                    //File.WriteAllBytes(p, write);
                }
            }
            else
            {
                Console.WriteLine(info);
            }
        }
    }
}

Einige Sachen sind extra ausgeschrieben um besser zu debuggen!
Ich habe schon BinaryWriter versucht mit bestimmten encodings aber es geht immer noch nicht!
 
Gleich zu Begin, welche Framework Version benutzt du?

Dann zu deinem Problem. Wahrscheinlich liegt es am Encoding und an der Art und Weise wie du die Chunk-Größe berechnest.

Beispiel UTF8:

Unser Alphabet ohne Sonderzeichen lässt sich mit 1byte darstellen. Im Endeffekt kannst du daher Splitten wo immer du willst.

Seit 'nen paar Jahren haben wir den €. Um das Zeichen darzustellen brauchst du schon 2 byte.
Deine Chunk-Größe ist aber nun z.B. 13byte.
Hm, nunja, du kannst Glück haben, dass du 13 byte liest und mitten drin war der €. Du kannst aber auch Pech haben und an Position 12 ist das 1. byte und an 13. das 2.. Dann "zerpflückst du das Zeichen.
Wenn du dir nun beide bytes einzeln anschaust, kriegst du irgendwas, aber nicht €.
 
ich benutze .NET 3.5.

Ich übergebe eigentlich ja den richtigen Encoding Parameter. Dieser wird ja oben heraus gelesen und an die Threads weitergegeben.

Ich versuche nach einem Zeilenumbruch an eine bestimmte stelle zu filtern und diesen dann auch miteinbeziehen.

Ich habe überprüft. Der String in den ersten bis vorletzen Splitt hatte immer Terminierungszeichen "\0" zwischen den einzelnen Chars. Bei der letzte Datei hatte ich einen ganz normalen String herauslesen können. Es kann ja doch nicht sein, dass die Kodierung sich in der Zwischenzeit geändert hat.


Es wird am anfang und am Ende des Streams nach umbrüchen gesucht.

Code:
fs.Seek(pre_write_pos, SeekOrigin.Begin);
            String s = r.ReadLine();

            if (s != null)
            {
                long len = s.ToCharArray().LongLength;
                pre_write_pos += len;
                size -= len;
            }

Da wird am Anfang des Streams gesucht.

Code:
            fs.Seek(pre_write_pos + size, SeekOrigin.Begin);

            s = r.ReadLine();
            if (s != null)
            {
                nextbreak = s.ToCharArray().LongLength;
                byte[] output = new byte[size + nextbreak];

und hier wird am ende des Streams gesucht...

Es könnte auch sein das beim fs.Seek zufällig eine Position angesprungen wird, wo die Zeichen noch nicht kaputtgeschnitten wurden. Aber wie kann man den sonst vermeiden, dass der Stream an der richtige stelle anfängt?
 
Wenn du sagst, dass du immer \0 zwischen den Bytes liest, dann ist dein Encoding Unicode (UTF16).

Code:
                    s = encoder.GetString(output, 0, 4);
                    if (s.Contains("\r\n"))
                    {
                        write = new byte[output.LongLength - 2]; // überflüssig
                        write = output.Skip(2).ToArray();
                    }
                    else if (s.Contains("\r") || s.Contains("\n"))
                    {
                        write = new byte[output.LongLength - 4]; // überflüssig
                        write = output.Skip(4).ToArray();
                    }

Was ist der Sinn hier von?
Wenn du einen Linebreak findest (\r\n), überspringst du 2 byte - in UTF16: \r\n = 13, 00, 10, 00. D.h. \n bleibt bestehen.
Falls es nicht gefunden wird, schaust du ob entweder 13 oder 10 vorkommt, wenn ja, überspringst du gleich 4 byte.
Das verstehe ich nicht.
Im ersten Fall überspringst du 2 zu wenig, im 2. Fall, 2 zu viel.

Ps. das erste write = ... ist jeweils überflüssig.

edit.
Noch als Anhang.
Für den Fall, dass die zu lesende Datei nicht UTF16 codiert ist, ist der obige Code auch "falsch"/gefährlich. In ANSI oder UTF8 wird der Linebreak ohne die 0-bytes codiert. Du liest aber 4 bytes von output und checkst einfach nur, ob darin ein \r\n, \n oder \r vorkommt. Du bestimmst aber nicht die genau Position, sondern überspringst einfach ein paar Bytes.
Nur weil Contains true zurückgibt, heißt das nicht, dass die gefundenen Bytes auch am Anfang der Sequenz zu finden sind.
 
Zuletzt bearbeitet:
hatte den Text geschrieben, dann verklickt und alles war weg! :grr:

Nagut, wie du vielleicht sehen kannst lese ich nur die ersten 4 Zeichen aus dem ByteArray @holy), deswegen kann ich mir reinem gewissen sagen, dass es \r oder \n oder \r\n drinne sein kann. Ich muss die ersten Zeichen rausnehmen, da diese in der neue dateien auftauchen und wenn ich die Datei zusammenführe, habe ich halt eine Leerstelle.

Hmm ich versuche dann irgendwie den Text erkennen zu lassen... vielleicht klappt es dann mit der Codierung.


Edit

Ich habe es herausgefunden. Da ich .net 3.5 Verwende kann der StreamReader kein big endian encoding richtig erkennen. Daher kann man die Big Endian Dateien nicht richtig splitten.

Danke
 
Zuletzt bearbeitet:
Zurück
Oben