Auf das Thema antworten  [ 5 Beiträge ] 
Strings in eine Datei speichern 
Autor Nachricht
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Hallo Leute!

Nach einigen Stunden Kampf bin ich über einen Anfängerfehler gestolpert den ich euch hiermit ersparen möchte. Ich behandle in den vier folgenden Snippets das Speichern von Strings in eine Datei in Klartext, komprimiert, verschlüsselt und komprimiert und verschlüsselt.

Using:
Code:
 
 using System;
 using System.Text; // Für Encoding bei Klartext
 using System.IO;
 using System.IO.Compression; // Nur fürs komprimieren
 using System.Security.Cryptography; // Nur für das Verschlüsseln
 using System.Windows.Forms; // Nur für die Ausgabe von Fehlermeldungen
 


Mi 6. Mär 2013, 22:04
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Beitrag 
In Klartext:
Code:
 
// Ein einzelner String:
 public static void SpeicherEinzelnenString(string pfad, string inhalt)
 {
        File.WriteAllText(pfad, inhalt, Encoding.UTF8);
 }
 
// Ein String-Array:
 public static void SpeicherStringArray(string pfad, params string[] inhalt)
 {
        File.WriteAllLines(pfad, inhalt, Encoding.UTF8);
 }
 
// Laden:
 
// Einen einzelnen String:
 public static string LadeEinzelnenString(string pfad)
 {
        return File.ReadAllText(pfad, Encoding.UTF8);
 }
 
// Einen String-Array:
 public static string[] LadeStringAray(string pfad)
 {
        return File.ReadAllLines(pfad, Encoding.UTF8);
 }
 

Sollte die Datei bereits existieren, dann wird diese überschrieben. Es ist immer sinnvoll das Encoding - also die verwendete Codepage für das Codieren des Strings - mit anzugeben. Der UTF8-Standard deckt auch kulturell spezifische Zeichen wie etwa Umlaute ab. ASCII hingegen nur Zeichen, die im angelsächsischen Raum üblich sind.

Zwar können hier eine Reihe von Ausnahmefehlern auftreten, allerdings betrifft das nur ungültige Parameter und Zugriffsbeschränkungen - also dass man nicht die Erlaubnis zum Schreiben hat. Beides sollte normalerweise nicht auftreten.


Mi 6. Mär 2013, 22:05
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Beitrag 
Komprimiert:
Code:
 
 public static void SpeicherStringArrayKomprimiert(string pfad, params string[] inhalt)
 {
        FileStream fs = null;
        GZipStream gzip = null;
        StreamWriter sw = null;
        try
        {
                fs = new FileStream(pfad, FileMode.Create);
                gzip = new GZipStream(fs, CompressionMode.Compress);
                sw = new StreamWriter(gzip);
                //foreach (string s in inhalt) { sw.WriteLine(s); } // schlecht für Komprimierung!
                sw.Write(string.Join("\r\n", inhalt)); // viel besser!
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); }
        finally
        {
                if (sw != null) { sw.Dispose(); }
                if (gzip != null) { gzip.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 }
 

Weil wir hier mit Streams arbeiten ist der Code etwas komplexer. Mit einem StreamWriter können wir auf einen Stream schreiben. Wenn wir nicht mithilfe des GZip-Streams die Daten komprimieren möchten, dann könnten wir den StreamWriter auch direkt auf den FileStream verweisen.

Damit auch eine möglichst hohe Komprimierung erreicht wird, sollte man an dieser Stelle den gesamten Inhalt in einem Rutsch schreiben. Denn komprimiert werden kann nur dort, wo Informationen doppelt auftreten. Wenn sie allerdings zusammenhanglos einzeln in den Stream geschrieben werden, dann können nur Duplikate innerhalb dieses jeweiligen Eintrages eingespart werden.

Mit string.Join(string separator, params string[] value) wird ein String erzeugt, der die Strings aus dem Array aneinander gekettet und mit dem "separator" voneinander trennt. Das ist in diesem Fall "\r\n". "\r" ist das Steuerungszeichen für den Wagenrücklauf (an Schreibmaschine denken) und "\n" Das Steuerungszeichen für einen Zeilenumbruch. UNIX-Systeme brauchen nur "\n" - Windows braucht beides. Allerdings würde "\n" hier vollkommen ausreichen, weil die Daten komprimiert gespeichert werden. Ein Trennzeichen ist allerdings später unbedingt notwendig. Sollte es in den zu speichernden Strings verwendet werden, dann kommt es zu Fehlern! Alternativ kann man auch "\0" für NULL verwenden oder ein beliebig anderes Trennzeichen, dass nicht in den zu speichernden Strings verwendet wird. Siehe: http://www.theasciicode.com.ar/

Für das Lesen:
Code:
 
 public static string[] LeseStringArrayKomprimiert(string pfad, string separator = "\r\n")
 {
        FileStream fs = null;
        GZipStream gzip = null;
        StreamReader sr = null;
        try
        {
                fs = new FileStream(pfad, FileMode.Open);
                gzip = new GZipStream(fs, CompressionMode.Decompress);
                sr = new StreamReader(gzip);
                sr.Peek(); // Ein Trick für die Initialisierung
                return sr.ReadToEnd().Split(new string[] { separator }, StringSplitOptions.None);
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; }
        finally
        {
                if (sr != null) { sr.Dispose(); }
                if (gzip != null) { gzip.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 }
 

Der StreamWriter wird durch einen StreamReader ersetzt, der FileStream offnet die Datei jetzt und der GZip-Stream dekomprimiert. Außerdem ist manchmal ein kleiner Trick notwendig. Zumindest war das die Lösung für mein Problem im vierten Teil, als ich mit einem MemoryStream arbeitete. Es scheint eine Initialisierung zu brauchen. Da es nix kostet, bekommt er eine. Die Methode gibt lediglich das nächst verfügbare Zeichen zurück ohne es zu verarbeiten.

Danach machen wir ein paar Dinge auf einmal: Mit ReadToEnd() liest der StreamReader den gesamten Inhalt des FileStreams (über den GZipStream), dann wird dieser String mit dem angegebenen Separator getrennt und zurückgegeben. Dabei muss man den String in einen String-Array umwandeln und StringSplitOptions angeben. Mit "None" verhindern wir das Löschen von leeren Zeilen. Da es noch einen finally-Block gibt, wird dieser - trotz der return-Anweisung - durchlaufen bevor das Ergebnis nun endgültig zurückgegeben wird.


Mi 6. Mär 2013, 22:06
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Beitrag 
Verschlüsselt:

Jetzt braucht es eine Instanz der Klasse Rijndael. Folgende Informationen braucht man für eine Wiederherstellung:
  1. byte[] Key
  2. byte[] IV
  3. int KeySize

Eine mögliche Lösung wäre die Informationen als String zu speichern:
Code:
 
 public static string RijndaelToString(Rijndael rijndael, string split = "|", string split2 = ",")
 {
        return rijndael.KeySize.ToString() + split + string.Join(split2, rijndael.Key) + split + string.Join(split2, rijndael.IV);
 }
 
 // Wiederherstellen
 public static Rijndael StringToRijndael(string s, string split = "|", string split2 = ",")
 {
        try
        {
                string[] split3 = s.Split(new string[] { split }, StringSplitOptions.None);
                Rijndael r = Rijndael.Create();
                r.KeySize = int.Parse(split3[0]);
                r.Key = StringToByte(split3[1], split2);
                r.IV = StringToByte(split3[2], split2);
                return r;
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; }
 }
 
 public static byte[] StringToByte(string s, string split = ",")
 {
        string[] values = s.Split(new string[] { split }, StringSplitOptions.None);
        List<byte> list = new List<byte>(values.Length);
        foreach (string v in values) { list.Add(Convert.ToByte(v)); }
        return list.ToArray();
 }
 



Speichern:
Code:
 
 public static void SpeicherStringArrayVerschlüsselt(string pfad, Rijndael rijndael, params string[] inhalt)
 {
        FileStream fs = null;
        CryptoStream cs = null;
        StreamWriter sw = null;
        try
        {
                fs = new FileStream(pfad, FileMode.Create);
                cs = new CryptoStream(fs, rijndael.CreateEncryptor(), CryptoStreamMode.Write);
                sw = new StreamWriter(cs);
                sw.Write(string.Join("\v", inhalt));
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); }
        finally
        {
                if (sw != null) { sw.Dispose(); }
                if (cs != null) { cs.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 }
 

An sich tausche ich den GZipStream durch einen CryptoStream aus und muss bei dessen Instanzierung andere Angaben machen.

Lesen:
Code:
 
 public static string[] LeseStringArrayVerschlüsselt(string pfad, Rijndael rijndael)
 {
        FileStream fs = null;
        CryptoStream cs = null;
        StreamReader sr = null;
        try
        {
                fs = new FileStream(pfad, FileMode.Open);
                cs = new CryptoStream(fs, rijndael.CreateDecryptor(), CryptoStreamMode.Read);
                sr = new StreamReader(cs);
                sr.Peek(); // Ein Trick für die Initialisierung
                return sr.ReadToEnd().Split(new string[] { "\v" }, StringSplitOptions.None);
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; }
        finally
        {
                if (sr != null) { sr.Dispose(); }
                if (cs != null) { cs.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 }
 


Ich habe mich diesmal für den Separator "\v" fest entschieden. Das ist ein vertikaler Tab.


Mi 6. Mär 2013, 22:40
Diesen Beitrag melden
Profil Website besuchen
Administrator
Benutzeravatar

Registriert: Sa 15. Dez 2012, 19:15
Beiträge: 137
Wohnort: Karlsruhe
Mit Zitat antworten
Beitrag 
Komprimiert und verschlüsselt

Witziger Weise hat es sich ergeben dass ich genau in diesem Moment mir nochmal meine Methode in der StoreClass angeschaut habe und ich sie nun nochmal optimieren konnte:

Code:
 
// Speichern:
 public static void SpeicherStringArrayKomprimiertUndVerschlüsselt(string pfad, Rijndael rijndael, params string[] inhalt)
 {
        FileStream fs = null;
        GZipStream gz = null;
        CryptoStream cs = null;
        StreamWriter sw = null;
 
        try
        {
                fs = new FileStream(pfad, FileMode.Create);
                cs = new CryptoStream(fs, rijndael.CreateEncryptor(), CryptoStreamMode.Write);
                gz = new GZipStream(cs, CompressionMode.Compress);
                sw = new StreamWriter(gz);
                sw.Write(string.Join("\v", inhalt));
                sw.Flush(); // GANZ wichtig !!!
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); }
        finally
        {
                if (sw != null) { sw.Dispose(); }
                if (gz != null) { gz.Dispose(); }
 
                if (cs != null) { cs.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 
 }
 
 
// Lesen:
 public static string[] LeseStringArrayKomprimiertUndVerschlüsselt(string pfad, Rijndael rijndael)
 {
        FileStream fs = null;
        GZipStream gz = null;
        CryptoStream cs = null;
        StreamReader sr = null;
 
        try
        {
                fs = new FileStream(pfad, FileMode.Open);
                cs = new CryptoStream(fs, rijndael.CreateDecryptor(), CryptoStreamMode.Read);
                gz = new GZipStream(cs, CompressionMode.Decompress);
                sr = new StreamReader(gz);
                sr.Peek();
                return sr.ReadToEnd().Split(new string[] { "\v" }, StringSplitOptions.None);
        }
        catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; }
        finally
        {
                if (sr != null) { sr.Dispose(); }
                if (gz != null) { gz.Dispose(); }
 
                if (cs != null) { cs.Dispose(); }
                if (fs != null) { fs.Dispose(); }
        }
 
 }
 

Es geht also auch ohne MemoryStream auf eine ganz einfache Weise. Allerdings ist das "GANZ wichtig !!!" nicht zu vernachlässigen, weil dieser Anfängerfehler tatsächlich viel Energie und einige kostbare Stunden meines Lebens gekostet hat. Den Trick mit dem "Peek" beim Lesen sollte man auch kennen. Auch wenn ich mir nicht wirklich erklären kann warum das so ist. Lässt man diese Zeile weg, dann gibt der StreamReader nämlich einen leeren String zurück. Verrückt so etwas.


Schöne Grüße,
Magony

_________________
Bei Fragen, Lob, Kritik, Vorschläge, hilfreiche Hinweise oder Alternativvorschläge: Beitrag, neues Thema oder PN.
Für Dinge die diskutiert werden sollten, bitte neues Thema im jeweiligen Forum.
Wenn du nicht weißt wohin: Forum Unsortiert.


Mi 6. Mär 2013, 23:05
Diesen Beitrag melden
Profil Website besuchen
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Auf das Thema antworten   [ 5 Beiträge ] 

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast


Du darfst neue Themen in diesem Forum erstellen.
Du darfst Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software
Deutsche Übersetzung durch phpBB.de

Impressum