Forum: PC-Programmierung C# - Datei stellenweise auslesen --> Wie realisiert man das ordentlich ?


von Marco K. (mbc)


Lesenswert?

Hi Leute

Würde es gerne ordentlich machen, finde aber kein Beispiel dafür.

Also, ich habe eine Binärdatei aus welcher ich an bestimmten Stellen 
bestimmte Bytes benötige und in bestimmten Variablen abspeichern muß.

Wie man die Datei einliest weis ich, ich habe aber Probleme mit der 
Organisation der benötigten "Angaben" bzw. deren Umsetzung.

Also:
- Ich weis die Byte-Startposition
- Ich weis die Byte-Länge
- Ich weis den Variablennamen unter dem das/die ausgelesene/n Byte(s) 
gepeichert werden soll/en.

Wie realisiert man das nun "intelligent" ?

Über Serialisierung ist schwer, da zwischendrin "Lücken sind", außerdem 
will ich nicht erst die ganze Datei einlesen, sondern eben immer nur die 
Stellen, die ich benötige damits schneller geht.

Mein Problem ist eine Lösung zu finden, bei der man eben die Angaben zu 
"Variable", "Position" und "Länge" schön verbindet und darüber dann "in 
einem Rutsch" die Datei auslesen kann.

Als Array mit Schleife würds sicher gehen, ich frag mich aber obs da 
noch bessere Wege oder Möglichkeiten gibt ...

MFG
Marco

von Markus V. (Gast)


Lesenswert?

Hallo Marco,

es gibt das sowas, wird MSDN genannt. Mit sämtlichen Infos, die Du 
benötigst... ;-)

Hast Du schonmal in Erwägung gezogen
System.IO.BinayReader.Read(byte[] buffer,int index,int count)
zu bemühen?

Gruß
Markus

von Marco K. (mbc)


Lesenswert?

Ja, MSDN kenne ich ... grauenhaft unübersichtlich :-)

Aber das ist ja nicht mein Problem, denn:

> Wie man die Datei einliest weis ich, ich habe aber Probleme mit der
> Organisation der benötigten "Angaben" bzw. deren Umsetzung.

Mich interessiert die Organisation der Daten die dahinter steht, nicht 
das auslesen der Daten ansich, das kann ich schon einigermaßen.

Ich habe mehrere Variablen im Programm die ich mit Daten aus der 
einzulesenden Datei füttern möchte. Position und Länge innerhalb der 
Datei weiß ich für jede Variable einzeln.

Mein "Problem" ist einen schönen Weg zu finden das "übersichtlich" zu 
organisieren, denn ich muß jedesmal den Seek neu setzen und eine 
unterschledliche Länge an Bytes einlesen und speichern.

Zuerst dachte ich daran, lauter Structs zu definieren und in jedem 3 
Einträge zu machen:

- VALUE = speichert eingelesenen Wert
- readonly POSITION = Position innerhalb der Datei
- readonly LENGTH = Länge der Information innerhalb der Datei

Diese Structs (für jede Variable eine eigene) wollte ich dann in ein 
weiteres Struct packen und dann per "foreach" jedes Struct auslesen 
lassen und daraus dann auf die Datei zugreifen.

Dabei greife ich dann über die 3 verschiedenen Einträge auf alle 
Informationen zu.

Leider geht das nicht ganz so einfach weil mit Structs keine 
"foreach-Schleife" möglich ist, dann wirds da schon wieder (für 
Anfänger) umständlich.

So hätte ich alle Informationen zu einer einzigen Variable zentral an 
einem Ort (Name, ausgelesener Wert, und Position in der auszulesenden 
Datei).

Zugriff auf den eigentlichen Wert wäre dann auch sehr komfortabel über 
HAUPTSTUCT.VARIABLE.VALUE möglich, so hätte ich das "irgendwie" gerne.

Vielleicht wird jetzt deutlicher was ich meine. Eventl. gibts ja auch 
eine andere bessere Methode ...

von Markus (Gast)


Lesenswert?

Hi Marco,

haben die Daten, die Du aus der Datei auslesen willst, immer den selben 
Typ, oder ist das mal ein int, dann ein Byte-Array usw...? Dann stellt 
sich für einen Lösungsvorschlag noch die Frage, wie willst Du die Daten 
nach dem Lesen weiterverarbeiten?

Das was Du vor hast, ist aber prinzipiell schon möglich, wobei es immer 
mehr oder weniger große "Aber" gibt. Bevor ich Dir den Lösungsweg zeigen 
kann, sollte ich aber die obigen Infos haben.

Markus

von STK500-Besitzer (Gast)


Lesenswert?

>ich nicht erst die ganze Datei einlesen, sondern eben immer nur die
>Stellen, die ich benötige damits schneller geht.

Das wird vermutlich nichts werden, ausser C# kennt sowas wie "seek" (aus 
Pascal), mit dem man an bestimmte Stellen in der (Text-)Datei springen 
kann.

Ich gehe davon aus, dass du die Anzahl der zu lesenden Bytes und deren 
Position in der Datei kennst, und diese konstant ist.
Dann ist es am einfachsten, ein Array mit den Adressen und vielleicht 
auch noch der Anzahl hintereinander zu lesender Bytes) zu machen und die 
Datei dann einfach von vorne nach hinten einzulesen (vielleicht auch 
blockweise, was die Sache etwas verkompliziert), und die aktuelle 
Leseposition mit der nächsten aus der Adresstabelle vergleicht. Dann 
kopiert man die Bytes halt in eine Variable (wenn die Variablen feste 
Namen haben, braucht man noch ein switch-Case-Abfrage, die über den 
Adress-Array-Index arbeitet.

von Marco K. (mbc)


Lesenswert?

Schon einmal Danke für Eure Antworten.

Hab weiter über googlen versucht mal einen möglichen Weg zu finden, bin 
aber leider gescheitert.

Also, die Daten sind Binär, mal ist das Datum ein byte lang, mal zwei, 
drei oder vier Bytes. Typ ist immer derselbe, in der Binärdatei (keine 
Textdatei) halt Byte (HEX) und auslesen und "speichern" tue ich dann 
natürlich als 32bit-Integrer.

Speichern wollte ich sie ursprünglich gerne in einem struct, damit ich 
jedem Abschnitt einen struct zuordnen kann und die darin enthaltenen 
Infos dann als int zusammenfassen kann. Somit hat man (gerade als 
Anfänger wichtig) eine schöne Übersicht und hat die Daten "gesammelt" an 
einer Stelle (in dem Struct).

"SEEK" gibt es in C#, um beim Auslesen mehr Geschwindigkeit zu erhalten 
möchte ich deshalb die Stellen jedesmal gezielt anspringen um unnötiges 
auslesen von nicht benötigten Daten zu verhindern.

Anzahl der Bytes und deren Position kenne ich wie gesagt.
Dieses Muster wiederholt sich mehrmals in der gesamten Datei bei 
prinzipiell gleichem Abstand (2048 bytes). Es gibt also jedesmal einen 
Grundoffset und dann den individuellen Offset für jedes Datum. Das ist 
ja aber über ne einfache Schleife kein Problem.

@ STK500-Besitzer:
Prinzipiell hab ichs verstanden, aber auch das benötigt eine Menge Code 
und viele SWITCH-Abfragen, brauchte dann:
1 Positionsarray
1 Längenarray
1 Speicherarray

also alles verstreut und sehr fehleranfällig wie ich finde.

Bei meinem "Struct" wäre es ja dann quasi so:

STRUCTNAME DATUM
|-> DATUMSWERT int32
|-> POSITION int64
|-> LÄNGE byte

Alles ordentlich sortiert und direkt sichtbar zuordbar. Gefällt mir als 
Anfänger sehr gut, weil sehr übersichtlich und kaum fehleranfällig.

Was ich meine mal gesehen habe ist eine Art Multiarray, finde aber 
nichts dazu. Also quasi ein Multidimensionales Array, aber mit 
unterschiedlichen Typen. Diese würde ich dann in eine Arraylist setzen 
und gut wäre ...
Soweit ich weis kann man aber ein Multiarray nicht mit verschiedenen 
Typen kombinieren.

Beispiel wäre dann z.B.
1
 int[,] numbers = new int[3, 2] { {1, 2}, {3, 4}, {5, 6} };

Das wäre es ja schon fast, bläht aber den Speicher unnötig auf (wegen 
dem int64 (Position)). Zudem gibts keine Benennung der Variable in der 
der Wert dann gespeichert werden soll. Ließe sich zwar auch mit ner 
Schleife lösen, aber das finde ich sehr unschön und mehr als 
unprofessionell.

Hinkriegen würd ichs irgendwie, mir fehlt aber der Feinschliff, das geht 
bestimmt noch besser ... :-)

Es ist garnicht so leicht eine Variable zu erzeugen und dann eine 
entsprechende Position mit bekannter Länge aus einer Datei auszulesen.

von Ahem (Gast)


Lesenswert?

Ich sehe das Problem im Moment darin,
das Du nicht klar erklären kannst
was eigentlich Dein Ziel ist.

Einerseits magst Lösungen die:
intelligent, schön, übersichtlich sind,
aber das sind keine objektivierbaren Kriterien.
und Deine Kritik an den möglichen Lösungen
ist damit völlig undifferenziert.

Wenn Du meinst:
>STRUCTNAME DATUM
>|-> DATUMSWERT int32
>|-> POSITION int64
>|-> LÄNGE byte

ist eine sinnvolle Lösung, denn mach das doch so.

Ich meine, das Du noch nicht über genug Erfahrung verfügst, um 
verschiedene Lösungen zutreffend zu charakterisieren. Diese Erfahrung 
musst Du (wie jeder Andere auch) erwerben in dem Du erstmal 
verschiedenes probierst und vergleichst. Dann würdest Du auch Deine 
Frage selbst beantworten können.

von Markus (Gast)


Lesenswert?

Hi Marco,

mir ist zwar immer noch nicht ganz klar, wie Du die Daten weiter 
verarbeiten willst, aber ich habe Dir mal eine kleine Klasse (anstelle 
eines struct) als Beispiel. In C# gibt es nur geringfügige Unterschiede 
zwischen struct und class, die für Dein Vorhaben eher nicht relevant 
sein sollten. Wenn es Dich interessiert, wo der Unterschied liegt, dann 
google mal danach...

Hier jetzt das versprochene Beispiel:
1
    public class DataReader
2
    {
3
        public DataReader(BinaryReader reader, long position, int size)
4
        {
5
            if (reader == null)
6
                throw new ArgumentNullException("reader");
7
            if (size < 1 || size > 4)
8
                throw new ArgumentOutOfRangeException("size", size, "Wert muß im Bereich von 1..4 sein.");
9
            _position = position;
10
            _size = size;
11
            _fileData = 0;
12
            reader.BaseStream.Position = _position;
13
            for (int count = 0; count < _size; count++)
14
            {
15
                _fileData += (int)(reader.ReadByte() << (count * 8)); // little endian
16
                //_fileData = ((int)(_fileData << 8)) + reader.ReadByte(); // big endian
17
            }
18
        }
19
20
        private long _position;
21
        public long Position { get { return _position; } } // read only property
22
23
        private int _size;
24
        public int Size { get { return _size; } } // read only property
25
26
        private int _fileData;
27
        public int FileData { get { return _fileData; } } // read only property
28
    }
29
30
    class Program
31
    {
32
        static void Main(string[] args)
33
        {
34
            BinaryReader reader = new BinaryReader(new FileStream("filepath", FileMode.Open));
35
            List<DataReader> list = new List<DataReader>();
36
            try { new DataReader(null, 1, 1); }
37
            catch { Console.WriteLine("Exception hier erwartet."); }
38
            try { new DataReader(reader, 1, 0); }
39
            catch { Console.WriteLine("Exception hier erwartet."); }
40
            list.Add(new DataReader(reader, 4, 1));
41
            list.Add(new DataReader(reader, 5, 2));
42
            list.Add(new DataReader(reader, 7, 3));
43
            list.Add(new DataReader(reader, 10, 4));
44
            foreach (DataReader value in list)
45
            {
46
                int data = value.FileData;
47
                Console.WriteLine(
48
                    string.Format("data[{0},{1}]: {2}", value.Position, value.Size, data)
49
                    );
50
            }
51
        }
52
    }

Die Klasse DataReader speichert Position und Größe und ermittelt im 
Konstruktor den gewünschten Wert aus Deiner Datei. Du mußt noch 
herausfinden, wie Deine Daten in der Datei vorliegen (Little oder Big 
Endian) und die entsprechende Zeile im Konstruktor aktivieren und die 
andere Zeile löschen. Die Berechnung des int-Wertes habe ich nicht so 
ausführlich getestet, das überlasse ich dann Dir... ;-)

Viel Spaß
Markus

von Marco K. (mbc)


Lesenswert?

@Markus:
Anscheinend ist Dir aber doch klar was ich möchte :-)))))

!! DANKE !! Dein Beispiel ist absolut TOP !

Tut mir leid, aber ich habe Deine Frage bzgl. der Verwendung der Daten 
nicht verstanden. Im Grunde genommen möchte ich nachher nur die 
einzelnen _fileData abfragen, also der Binärwert (in int) der an der 
entsprechenden Position im File stand. Das dann natürlich in einer 
Schleife.
Mit Deinem Beispiel geht das ja genau so, wie ich mir das vorstelle.

Auch der Rest vom Code ist mehr als hilfreich für mich, er zeigt eine 
saubere Programmierung und mir werden dadurch jetzt viele Dinge klar, 
die ich vorher mal gelesen, aber aufgrund eines fehlenden Zusammenhanges 
nicht verstanden hatte. An Deinem praktischen Beispiel hier wirds mir 
sofort klar und ich kann es jetzt auch direkt einbinden.

@Ahem:
Muß Dir vollkommen Recht geben, das ist halt die Erfahrung die fehlt.
Bei Kleinigkeiten probier ist selber aus, aber das hier ist ein größerer 
Teil meines Programmes und da möchte ich sehr schlechten Code vermeiden 
und es schon möglichst optimal haben. Dafür sollte ein Forum da sein, 
auch wenn es wohl ne ziemlich blöde Frage eines Anfängers ist, die zudem 
wohl auch ziemlich undeutlich gestellt wurde.


MFG
Marco

von Markus (Gast)


Lesenswert?

Hi Marco,

meine Frage bezüglich des "Verwendungszwecks" habe ich deshalb gestellt, 
weil Du in dem Beispiel eigentlich nichts anderes als eine Liste oder 
eine Art Array mit int-Werten bekommst, in der wenig Informationen über 
die Quelle, oder genauer, den Typ der Quelle zur Verfügung steht. Man 
kann bei entsprechender Kenntnis wahrscheinlich noch mehr Logik in die 
Klasse(n) packen, was Programme wesentlich robuster macht.

So sauber, wie Du annimmst, ist mein Code übrigens nicht. Es fehlt zum 
Beispiel die Exception-Behandlung für die IO-Methoden. Außerdem habe ich 
mir wenig Gedanken gemacht, ob die Berechung des int-Wertes korrekt ist, 
...
Das ist übrigens nicht der Fall: byte ist unsigned, int ist signed. Also 
entweder mußt Du int und reader.ReadSByte() oder uint und 
reader.ReadByte() verwenden. Ich falle immer wieder darauf rein: int und 
uint, byte und sbyte, ist doch logisch, oder? ;-)

Dein Startposting zeigt übrigens, daß Du Dich schon mit Deinem Problem 
beschäftigt hast, aber über eine bestimmte Hürde nicht drüber kommst. 
Kein schreibt-mir-doch-mal-jemand-meine-Hausaufgaben. Es ist keine 
Schande, mal irgendwo nicht weiter zu kommen, als Anfänger sowieso 
nicht. Wie Du richtigt angemerkt hast, für sowas ist das Forum da.

Gruß
Markus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.