binäre Datei auslesen



  • Hallo zusammen,

    ich hätte ne frage: nehmen wir an, eine binäre Datei hat folgende Struktur:
    * sie besteht aus 17 Werten
    * die ersten 9 sind von Typ int
    * die nächsten 8 sind von Typ double

    Ich habe mit einem ganz primitiven Verfahren versucht, zuerst die ersten 9 auszulesen und auszugeben (mit dem Wissen, dass es neun sind).
    Danach waren die double-Werte dran.
    So.. also das Problem ist: ich bekomme ab der 8 Position falsche Werte geliefert. Meine Vermutung ist, dass es mit dem "byte order" zusammenhängt. Die Spezifikation von dieser Datei ist nämlich:

    Position  Type        Byte Order
    ----------------------------------
    Byte 0    Integer     Big
    Byte 4    Integer     Big
    Byte 8    Integer     Big   
    Byte 12   Integer     Big
    Byte 16   Integer     Big
    Byte 20   Integer     Big   
    Byte 24   Integer     Big
    Byte 28   Integer     Little
    Byte 32   Integer     Little
    Byte 36   Double      Little
    Byte 44   Double      Little
    Byte 52   Double      Little
    Byte 60   Double      Little
    Byte 68   Double      Little
    Byte 76   Double      Little
    Byte 84   Double      Little
    Byte 92   Double      Little
    
    import java.io.*;
    
    public class Shx {
        private String file;
    
        public Shx(String file) {
            this.file = file;
        }
    
        public void readFileHeader() throws IOException {
            InputStream in = new FileInputStream(file);
            System.out.println("available [bytes]: " + in.available());
            DataInputStream d = new DataInputStream(in);
    
            int i = 1;
            while(i < 9) {
                int result = d.readInt();
                System.out.println("int value: " + result);
                i++;
            }
    
            i = 0;
            while(i < 8) {
                double result = d.readDouble();
                System.out.println("double value: " + result);
                i++;
            }
    
            in.close();
        }
    
        public static void main(String[] args) {
            try {
                Shx s = new Shx("C:/intranet/Apache/htdocs/finder/ms_tmp/data/shapes/apotheken.shp");
                s.readFileHeader();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    Vielen Dank für Hilfe


  • Mod

    Du kannst mal folgendes bei den beiden ints testen, die du nicht richtig einliest. Keine Ahnung, ob es klappt. Ich habe es nicht getestet. 🙄
    [java]
    public int changeByteOrder (int number)
    {
    int outNumber = 0;
    for (int i = 0 ; i < 4 ; ++i)
    {
    outNumber = outNumber << 8;
    outNumber |= number & 0x000000ff;
    number = number >> 8;
    }
    return outNumber;
    }[/code]



  • Für Integer hilft Gregor dir aus, für Doubles liest du am besten die Zahl erst als long ein (selbe größe wie Double hoffe ich ;)). Dann drehst du die Byte-Order um (z.B. mit einer Variation von Gregors Code) und dann bedienst du dich
    Double.longBitsToDouble(long longBits), dass aus der Darstellung im Long einen Double macht.



  • hallo,

    erstmal vielen Dank für eure schnelle Antworten. Könnte mir vielleicht noch jemand erklären, was big-endian und little-endian bedeutet ? Und vor allem wie es in diesem Falle (sieh die Spezifikation der shp-Datei) zu interpretieren ist ?
    thnx


  • Mod

    Ein int besteht ja aus 4 Bytes. Little-Endian und Big-Endian geben an, wie diese Bytes im Speicher angeordnet sind.

    Einmal sind sie so angeordnet :

    1 - 2 - 3 - 4

    und einmal so :

    4 - 3 - 2 - 1



  • Danke Gregor !
    Alles klar!
    LG
    kati



  • Hi,

    also ich habe den Code mehrmals geändert und folgendes ist rausgekommen:

    import java.io.*;
    
    public class Shx {
        private String file;
    
        public Shx(String file) {
            this.file = file;
        }
    
        public void readFileHeader() throws IOException {
            InputStream in = new FileInputStream(file);
            System.out.println("available [bytes]: " + in.available());
            DataInputStream d = new DataInputStream(in);
                    /*die ersten 8 Integerwerte auslesen - Byte Order=Big*/
            int i = 0;
            while(i < 7) {
                int result = d.readInt();
                System.out.println("int value: " + result);
                i++;
            }
    
                    /*die nächsten 2 Integerwerte auslesen - Byte Order=Little*/  
            i = 0;
            while(i < 2) {
                int result = d.readInt();
                System.out.println("little vorher: " + result);
    
                            /*hier werden die Integerwerte konvertiert*/
                byte[] big = intConvert(result);
    
                int b = 0;
                            /*byte[] -> int*/
                for(int j=0; j<big.length; j++) {
                    b |= big[j];
                }
    
                System.out.println("big nachher: " + b);
                i++;
            }
    
            in.close();
        }
    
        /*konvertiert Big2Little, Little2Big (für beides geeignet)*/
        public byte[] intConvert(int big) {
            byte[] little = new byte[4];
            little[0] = (byte) (big & 0x000000ff);
            little[1] = (byte) ((big & 0x0000ff00) >> 8);
            little[2] = (byte) ((big & 0x00ff0000) >> 16);
            little[3] = (byte) ((big & 0xff000000) >> 24);
            return little;
        }
    
        public static void main(String[] args) {
            try {
                Shx s = new Shx("C:/intranet/Apache/htdocs/finder/ms_tmp/data/shapes/wien_bezirke.shp");
                s.readFileHeader();
    
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    Folgendes kommt da raus:

    C:\Java\j2sdk\Shapefiles>java Shx
    available [bytes]: 128972
    int value: 9994
    int value: 0
    int value: 0
    int value: 0
    int value: 0
    int value: 0
    int value: 64486
    little vorher: -402456576
    big nachher: -21      /*FALSCH*/
    little vorher: 83886080
    big nachher: 5        /*richtig*/
    

    Ich habe den Code mit mehreren Dateien getestet, überall bekomme ich an der mit /*richtig*/ markierten Stelle das Richtige zurück, an der als falsch markierten Stelle bekomme ich immer nur falsche Werte zurück, es sollte da lt. der Spezifikation 1000 immer stehen. Kann sein, dass es mit dem Vorzeichen zusammenhängt (in der Methode intConvert() habe ich auch schon >>> ausprobiert, haut nicht hin).

    thnx
    kati



  • Hi Kati,

    ich weiss nicht, ob das mit mit eurer Byte-Order alles so richtig ist,
    aber wenn das so passt, dann liegt der Fehler wohl hier:

    int b = 0;
                            /*byte[] -> int*/
                for(int j=0; j<big.length; j++) {
                    b |= big[j];
                }
    

    So wird ja nur immer nur das unterste Byte vom int geändert.
    Also bei jedem Schleifendurchlauf erst b zurecht shiften.

    Gruss Jockel



  • Hallo Jokel,

    du hast natürlich recht... da wird der unterste Byte immer überschrieben. Bei dem richtigen Ergebnis war zufällig nur das unterste Byte nicht NULL und daher war das Ergebnis auch richtig (weil die int-Zahl allzu klein war). Da bekomme ich aber weiterhin keine richtigen Zahlen raus... 😞 So habe ich den Code modifiziert:

    int b = 0;
                for(int j=0; j<big.length; j++) {
                    b |= big[j];
                    if(j == big.length-1) break;
                    b = b << 8;
                }
    

    thnx



  • Original erstellt von Kati:
    **```cpp
    b |= big[j];

    Die Zeile macht das Problem, da falls in big[j] ein Wert über 0x7f liegt, der negativ interpretiert wird und vor dem verORen auf einen int erweitert wird, also wieder auf einen negativen int...ich üebrlege noch was man tuun kann



  • Habs 🙂

    Also 1. Änderung: die intConvert-Funktion muss ein int[] zurückgeben. Damit vermeidest du, dass java Werte größer 0x7f als negative Zahlen interpretierst. Als zweite Änderung die Funktion intConvert nochmal im ganzen:

    public int[] intConvert(int big) {
            int[] little = new int[4];
            little[0] = (big & 0x000000ff);
            little[1] = ((big & 0x0000ff00) >> 8);
            little[2] = ((big & 0x00ff0000) >> 16);
            little[3] = ((big & 0xff000000) >> 24) & 0xff;
    
            return little;
        }
    

    Das wichtige ist
    a) die casts auf byte fallen weg
    b) der letzte wert wird nochmal mit 0xff verUNDet, damit ein eventuelles negatives Vorzeichen wegfällt.

    X:\>java Shx
    available [bytes]: 36
    int value: 9994
    int value: 0
    int value: 0
    int value: 0
    int value: 0
    int value: 0
    int value: 64486
    little vorher: -402456576
    big nachher: 1000
    little vorher: 83886080
    big nachher: 5
    


  • Hallo Triphoenix,

    *g* du bist der beste. *g*

    ok, also es klappts. Nur ein Paar fragen hätte ich, damit ich den Code auch wirklich verstehe:
    * also int[] gebe ich deswegen zurück, weil wenn der beliebige Bytewert

    ((big & 0x0000ff00) >> 8) etc.
    

    größer ist als 127 (signed ?), wass bei 1000 auch zutrifft, wird die Zahl negativ interpretiert.

    2^9   2^8   2^7   2^6   2^5   2^4   2^3   2^2   2^1   2^0
    512   256   128   64    32    16    8     4     2     1
    ----------------------------------------------------------
    1     1     1     1     1     0     1     0     0     0
    ----------------------------------------------------------
    |     |     |                 |     |                 |
    <------     -------------------     -------------------
                        240
    

    * wie ist es mit den Vorzeichen ? Wie hier:

    little[3] = ((big & 0xff000000) >> 24) & 0xff;
    

    Wie kann ich das Vorzeichen wegtun ? Ist es mit der Version ohne verUNDen nicht getan ?

    Vielen Dank nochmals 😉



  • Original erstellt von Kati:
    *** also int[] gebe ich deswegen zurück, weil wenn der beliebige Bytewert

    ((big & 0x0000ff00) >> 8) etc.
    

    größer ist als 127 (signed ?), wass bei 1000 auch zutrifft, wird die Zahl negativ interpretiert.
    **

    Genau. Denn sonst kannst du in den Bytes nur die ersten 128 Werte von 0..127 darstellen. Jeder Wert darüber wird von Java als negativ interpretiert und in dieser Zeile

    b |= big[j];
    

    dann falsch verrechnet. Das Problem tritt demnach immer dann auf, wenn in der Zahl IRGENDWO ein Byte ist, wo das höchste Bit gesetzt ist. Vor der Operation macht Java nämlich alle Operanden gleichgroß, in deiesem Fall würde z.B. ein Byte-Wert von 128 = 0x80 erweitert werden auf 0xffffff80 bevor das OR angewendet wird. Indem du gleich integer benutzt verhinderst du diese Konvertierung und hast mehr Kontrolle, was Java dann am Ende verrechnet.

    **
    * wie ist es mit den Vorzeichen ? Wie hier:

    little[3] = ((big & 0xff000000) >> 24) & 0xff;
    

    Wie kann ich das Vorzeichen wegtun ? Ist es mit der Version ohne verUNDen nicht getan ?
    **

    Das Problem liegt hier in der Art wie Java shiftet. Man kann logisch rechtsshiften und man kann arithmetisch rechtshiften. Logisch rechtsshiften ist wie mans erwartet, nämlich dass die Bits einfach nach rechts geschoben werden und alle "freien" Bits mit 0 aufgefüllt werden. Bei arithmetischen Rechtsshiften wird je nach Vorzeichen mit 0 oder 1 aufgefüllt, bei einer negativen Zahl also mit 1. Und genau so shiftet Java um die Möglichkeit zu erhalten mit shufts schnell zu dividieren. Das ist zwar oft praktisch, nicht aber mehr, wenn man mit den rohen Zahlen arbeitet. Da nach dem shiften deine Zahl ja quasi nur in den letzten beiden Hex-Stellen stimmt und alle Stellen davor je nach Vorzeichen mit 0 oder 1 belegt sind, ist es dann eben das einfachste per & 0xff alle Stellen bis auf die letzten 2 auszunullen. So hast du dann quasi logisch Shiften simuliert und kommst auf das richtige Ergebnis.

    Hoffe das hift 😉

    [ Dieser Beitrag wurde am 05.02.2003 um 15:19 Uhr von TriPhoenix editiert. ]



  • Hallo !

    ok, also jetzt habe ich es halbwegs kapiert. Was das unglückliche Vorzeichen angeht, könnte ich dann also auch das >>> Operator verwenden, das das Vorzeichen nicht berücksichtigt.

    public int[] intConvert(int big) {
            int[] little = new int[4];
            little[0] = (big & 0x000000ff);
            little[1] = ((big & 0x0000ff00) >> 8);
            little[2] = ((big & 0x00ff0000) >> 16);
            //little[3] = ((big & 0xff000000) >> 24) & 0xff;
            little[3] = (big & 0xff000000) >>> 24;
            return little;
        }
    

    Da kommt dann auch das richtige raus. So und jetzt schaue ich mir die Long(Double)-Werte an. 😉



  • Original erstellt von Kati:
    **ok, also jetzt habe ich es halbwegs kapiert. Was das unglückliche Vorzeichen angeht, könnte ich dann also auch das >>> Operator verwenden, das das Vorzeichen nicht berücksichtigt.
    **

    Ui, man lernt nie aus 🙂 Der schiebt die Bits dann roh durch die Gegend also quasi logisches Shiften? Feine Sache 🙂


Anmelden zum Antworten