Binärdateien
LSpreee
- php
Hallo,
ich möchte Binärdateien mit php lesen. Bin noch bei den Grundlagen.
Habe im Forum einen Beitrag von vor einem Jahr gefunden.
Ich versuche anhand dieser Beispiele und der Dokumentation von pack() mir alles herzuleiten.
Ok.
// BSP 1
$test_int = pack('VVV', 1262698832, 44927, 128 );
$out_int = unpack('V1myvar/V1yourvar/V1ourvar', $test_int);
var_dump($out_int);
// BSP 2
$test_mix = pack('a56@56VV', 'irgendeinstring', 44927, 128);
$pos = strpos($data, chr(0));
$out_mix = unpack('a' . ( $pos ? (string)$pos : '56' ) . 'var1/@56/V1var2/V1var3', $test_mix);
var_dump($out_mix);
Output:
~~~php
array
'myvar' => int 1262698832
'yourvar' => int 44927
'ourvar' => int 128
array
'var1' => string 'irgendeinstring' (length=15)
'var2' => int 44927
'var3' => int 128
Fragen:
1) was ist das NUL-Element von dem bei [pack()](http://de.php.net/manual/de/function.pack.php) (unter a und @) die Rede ist?
2) warum gibt es beim Mixbeispiel im pack im Prinzip vier Formatanzeiger: a56, @56 und zwei mal V und nur drei Werte ("irgendeinstring", 44927, 128). Was habe ich da missverstanden?
3) Warum komme ich nicht mit
~~~php
$out_mix2 = unpack('a56V2', $test_mix);
var_dump($out_mix2);
Output
~~~php
'V2' => string 'irgendeinstring' (length=15)
oder:
`$out_mix2 = unpack('a56/V2', $test_mix);`{:.language-php}
Output:
~~~php
array
1 => int 44927
2 => int 128
an den Inhalt von $test_mix?
Durch die Antworten hoffe ich ein besseres Verständnis für Binärformate zu erhalten.
Liebe Grüße,
LSpreee
Hi!
- was ist das NUL-Element von dem bei pack() (unter a und @) die Rede ist?
NUL ist ein Byte mit dem Wert 0. Das Element a füllt, wenn der String kleiner war als die Längenangabe, die restlichen Bytes mit 0 auf, das @ positioniert den Schreibzeiger absolut vom Anfang an auf die angegebene Position und füllt vom derzeitigen Zeiger bis dorthin mit 0-Bytes auf.
a42@42 ist eine sinnlose Aneinanderreihung, wenn das a bereits am Anfang steht. Denn einerseits füllt das a bereits bis zu 42 Byte und das @ macht dasselbe nochmal. Wenn die Längenangaben unterschiedlich sind oder das a bei gleicher Längenangabe nicht das erste Element ist, sieht das etwas anders aus.
- warum gibt es beim Mixbeispiel im pack im Prinzip vier Formatanzeiger: a56, @56 und zwei mal V und nur drei Werte ("irgendeinstring", 44927, 128). Was habe ich da missverstanden?
@ positioniert und füllt nur, es fügt keine Nutzdaten hinzu.
- Warum komme ich nicht mit
$out_mix2 = unpack('a56V2', $test_mix);
var_dump($out_mix2);
>
> Output
> 'V2' => string 'irgendeinstring' (length=15)
Das V2 wird als Key-Name interpretiert.
> oder:
> `$out_mix2 = unpack('a56/V2', $test_mix);`{:.language-php}
>
> Output:
> array
> 1 => int 44927
> 2 => int 128
>
> an den Inhalt von $test\_mix?
Hier legt V2 die beiden Schlüssel 1 und 2 an und sein 1 überschreibt das Element 1 von a56. Du solltest Namen vergeben. Auf der englischen Handbuchseite zu unpack() steht auch noch eine zweite gelbe Box, die explizit diese Problematik erwähnt. Die deutsche Seite hat nur den ziemlich kurzen Hinweis auf das Benennen im einleitenden Text.
> Durch die Antworten hoffe ich ein besseres Verständnis für Binärformate zu erhalten.
Eher von der Arbeitsweise von pack()/unpack(). Zu Binärformaten hast du nichts gefragt.
Lo!
VIELEN DANK, für diese konstruktiven Antworten! Vielen Dank auch für den Hinweis auf den englischen Handbucheintrag.
- was ist das NUL-Element von dem bei pack() (unter a und @) die Rede ist?
NUL ist ein Byte mit dem Wert 0. Das Element a füllt, wenn der String kleiner war als die Längenangabe, die restlichen Bytes mit 0 auf, das @ positioniert den Schreibzeiger absolut vom Anfang an auf die angegebene Position und füllt vom derzeitigen Zeiger bis dorthin mit 0-Bytes auf.
Ist NUL dann gleichbedeutend mit einem (bspw.) Float, der den Wert 0 enthält?
Und: Blöde Frage evtl., aber positioniert @ auf eine Byteposition oder auf eine Bitposition? Sind die Stellenangaben überhaupt immer Bytelängen oder Bitlängen? Ich vermute eher Byte, was dann bedeuten würde, dass es die kleinste Einheit ist (die zu adressieren geht)?
Liebe Grüße,
LSpreee
Hi!
NUL ist ein Byte mit dem Wert 0. [...]
Ist NUL dann gleichbedeutend mit einem (bspw.) Float, der den Wert 0 enthält?
Eigentlich nicht. In ASCII (und darauf aufbauenden Zeichensätzen) haben die Steuerzeichen 0..0x1F Namen und das Zeichen an Position heißt NUL. Außerdem gilt (zumindest für ASCII) 1 Zeichen = 1 Byte, und das Byte mit dem Wert 0 wird daraufhin auch als NUL bezeichnet. Ein Float ist aber je nach Ausführung 4 oder 8 Byte und die interne Darstellung von Zahlen etwas komplexer. Du kannst von Float-0 auf NUL und zurück in einer Hochsprache nur dann kommen, wenn du einen Typecast vornimmst.
Und: Blöde Frage evtl., aber positioniert @ auf eine Byteposition oder auf eine Bitposition?
Byte.
Sind die Stellenangaben überhaupt immer Bytelängen oder Bitlängen? Ich vermute eher Byte, was dann bedeuten würde, dass es die kleinste Einheit ist (die zu adressieren geht)?
Byte. Ja, das ist die kleinste Einheit aus Sicht des Pack-Formats. Auf Bits zu gehen wäre ziemlich ungünstig. Üblicherweise orientiert sich ja alle Verarbeitung und alle Datentypen an Byte-Grenzen, sind also Vielfache von 8 Bit. Auch Dateien werden byteweise geschrieben und gelesen. Wenn du jetzt einen Wert mit zum Beispiel 3 Bits dazwischenschiebst, musst du nachfolgend bei 8-Bit-Strukturen immer "halbe" Bytes zusammenstückeln. Es ist in der Verarbeitung deutlich einfacher für einzelne boolsche Werte ein ganzes Byte zu "verschwenden", als mit einem byteorientierten System Bits einzeln zu anzusprechen.
Lo!
Vielen Dank!
LSpreee
hi,
vielleicht noch ein paar ergänzende Worte, was Binärdateien betrifft. Altvater Nicolas Wirth bezeichnet Dateien als Sequenzen. Von "echten" Random-Access-Dateien mal abgesehen, werden Binärdateien auch sequentiell geschrieben und gelesen, wobei nur mit Offset (Position ab Dateibeginn) und Length operiert wird.
Die Angaben Offset und Length sind stets als Anzahl der Bytes zu betrachten (danke dedlfix). Konsequenterweise gibt es in Binärdateien keine textlichen Strukturen wie Delimiter, Boundaries o.ä und es gibt auch keine Zeilenumbrüche im Sinne von Textdateien.
Zum Erzeugen oder Lesen wird ein Algorithmus gebraucht, welcher umkehrbar ist und aus Datenstrukturen (Arrays, Hashes, Objects...) diese Binärsequenzen erzeugt und auch wieder lesen kann. Ein besonderes Augenmerk gilt dabei den Längenangaben, genau an dieser Stelle setzt die pack()-Funktion an:
Mit einer geeigneten (Byte)Schablone werden aus Zahlen die Längenangaben in Bytesequenzen (Teile einer Binärdatei) gepackt, die _immer_ eine konstante Anzahl von Bytes haben. So wird aus einem 32-Bit-Integer mit pack("N", $i) eine Bytefolge von genau 4 Bytes erzeugt.
Der Algorithmus umschreibt den Vorgang der Serialisierung und Deserialize. Am einfachsten sind Arrays zu serialisieren, Algorithmus Beispiel:
0 lege eine Schleife um jedes Array-Element, Schleifenkörper:
1 bestimme die Länge eines Elements
2 schreibe die Länge mit pack("N", $length) in die Sequenz (4 bytes)
3 schreibe die Bytes des Array-Elements in die Sequenz (x bytes)
<-- Loop
Dieser Algorithmus lässt sich umkehren, zum Lesen wird ein Handler aufgemacht und aus dem Handler werden die Bytes gelesen wie folgt:
1 Lese 4 bytes, unpack("N", $bytes) ergibt die Länge des nachfolgenden Array-Elements
2 Lese das Array-Element
3 Lese wieder 4 bytes ... loop bis EOF
Das war einfach ;)
Gute Übung für lange Winterabende...
Freilich gibt es weitere Algorithmen, die nicht so einfach sind. So richtig kompliziert wird es mit komplexen Datenstrukturen, die rekursiv durchlaufen werden müssen.
Binaries sind plattformunabhängig, es ist jedoch zu beachten, dass gewisse OS'se beim Öffnen eines Descriptors mitgeteilt bekommen wollen, dass es eine Binärdatei ist.
Durch die Vielzahl möglicher Algorithmen bedingt, ist der Datenaustausch auf einen engen Anwendungskreis beschränkt, für einen anwendungsunabhängigen Datenaustausch hingegen konnten sich Binärfomate bisher nicht durchsetzen. Ein solcher ist nur dann möglich, wenn den beteiligten Programmen/Partnern der Algorithmus nicht nur bekannt sondern auch anwendbar ist.
Es liegt jedoch auf der Hand, das Binaries sehr schlank sind und bis auf die Längenangaben gibt es kaum "Tara" im Gegensatz zu XML wo alleine für die Tags schonmal einige Megabyte zusammenkommen können ;)
Binaries sind von daher auch nicht nur als Dateien sondern auch für Datenfernübertragungen bestens geeignet.
O'Hotti
Vielen Dank für den aufschlussreichen Text. Hat mir geholfen.
Was ich nicht ganz verstehe:
Der Algorithmus umschreibt den Vorgang der Serialisierung und Deserialize. Am einfachsten sind Arrays zu serialisieren, Algorithmus Beispiel:
0 lege eine Schleife um jedes Array-Element, Schleifenkörper:
1 bestimme die Länge eines Elements
2 schreibe die Länge mit pack("N", $length) in die Sequenz (4 bytes)
3 schreibe die Bytes des Array-Elements in die Sequenz (x bytes)
<-- Loop
Sind in Deinem Array die Werte, die in die Binary sollen?
Was ist der Unteschied zw. 2 und 3?
Wenn Du es umdrehst:
1 Lese 4 bytes, unpack("N", $bytes) ergibt die Länge des nachfolgenden Array-Elements
2 Lese das Array-Element
3 Lese wieder 4 bytes ... loop bis EOF
... klingt das so, als ob ich das mit get_file_contents() nicht so schlau mache. Klingt eher so, als wenn Du fread() mit einer bestimmten Länge anwenden möchtest. Wie kriegt man dann im Vorhinein die Länge raus?
Vielen Dank für den Austausch.
Liebe Grüße,
LSpreee
hi,
Sind in Deinem Array die Werte, die in die Binary sollen?
Ja. ('Otto','Hans','Anton','Emil','Linda','Ulla','Änne')
Auch wenn das hier lesbarer Text ist: Es sind Bytes. Wobei das 'Ä' in UTF-8 natürlich 2 Bytes hat, das ist nur zu beachten beim Ermitteln der Länge mit length().
Was ist der Unteschied zw. 2 und 3?
In der Sequenz gibt es für jedes Array-Element zwei Einträge, einmal 4 byte mit der Längenangabe und dann die Bytes selbst (siehe oben).
Wenn Du es umdrehst:
... klingt das so, als ob ich das mit get_file_contents() nicht so schlau mache.
Du brauchst eine Funktion, die Bytes aus Handles lesen kann. Was PHP betrifft, kenne ich mich da leider überhaupt nicht aus, eine solche Funktion gibt es aber bestimmt auch in PHP, eine Funktion, die als Argument das Handle bekommt und die Anzahl der Bytes, die gelesen werden sollen.
Klingt eher so, als wenn Du fread() mit einer bestimmten Länge anwenden möchtest.
Das könnte die gesuchte Funkton sein, in Perl heißt die Funktion read().
Wie kriegt man dann im Vorhinein die Länge raus?
Die Längenangabe hast Du vorher in die Datei geschrieben, das sind 4 Bytes, die zu lesen sind (pack-Schablone V oder N).
Hotti
Pack mal ein Array von Zahlen ein ;)
#!/usr/bin/perl
use strict;
use warnings;
use IO::File;
my $handle = IO::File->new_tmpfile;
binmode $handle;
print $handle pack("N*", (0..99));
$handle->seek(0,0);
while( read($handle, my $buffer, 4) ){
printf "%u\n", unpack "N", $buffer; # Zahlen von 0..99
}
Auch wenn das hier lesbarer Text ist: Es sind Bytes. Wobei das 'Ä' in UTF-8 natürlich 2 Bytes hat, das ist nur zu beachten beim Ermitteln der Länge mit length().
Frage mich, wie die Funktion in php heisst, die die Bytelänge aus einem String ("Ä") errechnet?
BTW: was bedeutet im engl. phpnet-Eintrag von pack() eigentlich
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
The resulting binary string will be 6 bytes long and contain the byte sequence 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
die 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Das könnte die gesuchte Funkton sein, in Perl heißt die Funktion read().
Die Längenangabe hast Du vorher in die Datei geschrieben, das sind 4 Bytes, die zu lesen sind (pack-Schablone V oder N).
Das macht Sinn :)
beste Grüße.
LSpreee
Könntest Du auch noch was zu meinem anderen Posting sagen?
Hi,
Frage mich, wie die Funktion in php heisst, die die Bytelänge aus einem String ("Ä") errechnet?
Frage mich, warum du dir nicht selber die Übersicht über die Stringfunktionen im Manual anschaust.
BTW: was bedeutet im engl. phpnet-Eintrag von pack() eigentlich [...]
die 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
0x steht ganz allgemein in der Programmierung dafür, dass ein Hexadezimalwert gemeint ist.
Hältst du es nicht vielleicht für eine gute Idee, dich so einem Thema erst mal zu nähern, in dem du dir Grundkenntnisse aneignest …?
MfG ChrisB
0x steht ganz allgemein in der Programmierung dafür, dass ein Hexadezimalwert gemeint ist.
Danke für die Information.
Hältst du es nicht vielleicht für eine gute Idee, dich so einem Thema erst mal zu nähern, in dem du dir Grundkenntnisse aneignest …?
Wenn Du eine gute Lektüre kennst...
Ich habe einige Wikipediaartikel gelesen, die Seiten (und Kommentare) der php-Dokumentation zu binär-Funktionen im Manual gelesen und nach php + binärdaten etc. gesucht und entsprechende Forenbeiträge gelesen. Auch im SelfForum, auf welchen Thread ich eingangs verwies. Mein Posting war hoffentlich nicht überstürzt.
Wenn Du der Meinung bist, dass insbesondere meine Fragen aus diesem Posting newbiemäßig sind, kannst Du es mir ja nochmal sagen. Ansonsten würde ich mich über eine Antwort sehr!!! freuen.
LG
LSpreee
moin,
The resulting binary string will be 6 bytes long and contain the byte sequence 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Das ist in der Tat verwirrend, weil:
Es gibt keine binary strings! Ein "string" ist eine Folge von "Zeichen", eine binary hingegen enthält keine Zeichen sondern Bytes. Es sollte also besser "binary sequence" heißen und dieser kleine aber feine Unterschied sorgt seit der Unicode-Implementierung verschiedener Programmiersprachen immer wieder für Verwirrungen.
Unterscheide in Deinen Scripts zwischen bytesemantics und charactersemantics.
Schönen Tag,
Hotti
Hi!
Frage mich, wie die Funktion in php heisst, die die Bytelänge aus einem String ("Ä") errechnet?
Grundsätzlich geht PHP derzeit immer noch davon aus, dass 1 Zeichen gleich 1 Byte ist. Es sei denn, du hast Funktionen, bei denen extra eine spezielle Zeichenkodierung angegeben werden kann. strlen() liefert die Anzahl in Bytes, egal ob der String in einer Mehrbytekodierung vorliegt oder nicht, denn strlen() bietet keine Möglichkeit, etwas anderes anzugeben. So geht es den meisten String-Funktionen. Mehrbyte-Kodierungen können nur mit den mb_*-Funktionen bearbeitet werden. (Man kann zwar einige der normalen Stringfunktionen mit ihrem mb-Pendant überladen, aber das macht eigentlich keiner.)
Da pack() auch auf Byte-Ebene arbeitet und nichts von Mehrbyte-Zeichen weiss, arbeitet es für eine Längenermittlung perfekt mit strlen() zusammen.
Lo!
Hi!
Was ich nicht ganz verstehe:
Der Algorithmus umschreibt den Vorgang der Serialisierung und Deserialize. Am einfachsten sind Arrays zu serialisieren, Algorithmus Beispiel:
0 lege eine Schleife um jedes Array-Element, Schleifenkörper:
1 bestimme die Länge eines Elements
2 schreibe die Länge mit pack("N", $length) in die Sequenz (4 bytes)
3 schreibe die Bytes des Array-Elements in die Sequenz (x bytes)
<-- Loop
Sind in Deinem Array die Werte, die in die Binary sollen?
Was ist der Unteschied zw. 2 und 3?
Das mit dem Array hat anscheinend mehr verwirrt. Es geht im Grunde genommen darum, mehrere unterschiedlich lange Werte in der Datei abzulegen. Diese Werte müssen dabei nicht einem Array stehen, sie können auch einzeln dem Stream hinzugefügt werden. Da die Länge der Elemente variabel ist - wie es bei String der Fall ist, Zahlentypen haben in der Regel eine feste Länge - muss man für den Lesenden mitteilen, wie lang das nächste Element ist. Feste Feldgrößen wären eine Möglichkeit, da muss das Ende des Inhalts besonders gekennzeichnet werden, wenn er nicht die gesamte Länge ausfüllt. Dazu nimmt man das NUL. Feste Längen vergeuden aber Platz. Deswegen schreibt man effizienter: "Jetzt kommen x Byte" (2) und dann die x Bytes (3), "nun folgen y Bytes" (2) und die y Bytes (3). Bei einem Array läuft das im Gegensatz zu einzelnen Elementen in einer Schleife ab.
... klingt das so, als ob ich das mit get_file_contents() nicht so schlau mache. Klingt eher so, als wenn Du fread() mit einer bestimmten Länge anwenden möchtest. Wie kriegt man dann im Vorhinein die Länge raus?
Wenn das Format mit variablen Längen arbeitet, kann man das häppchenweise lesen. Nun kann man aber noch Performance-Überlegungen anstellen. Dateisystemzugriffe sind möglicherweise langsam (heutzutage vielleicht nicht mehr, weil eventuell ein Cache mitspielt). Jedenfalls wäre es dann besser, einen großen Block zu lesen, was vergleichsweise weniger aufwändig ist, als zwei halb so große Blöcke einzeln zu lesen. Diesen Block im Speicher kann man dann wieder häppchenweise auswerten. Man kann dazu Streams nehmen, die kümmern sich im Verborgenen um das eigentliche Lesen vom Medium, puffern das und liefern genau die gewünschten Häppchen aus dem Puffer, wenn man einzelne kleine Lesezugriffe darauf absetzt.
PHP kennt zwar auch Streams, die arbeiten jedoch nicht in Richtung Dateisystem sondern vermitteln zwischen herkömmlichen Dateisystemfunktionen und diversen externen Ressourcen. Für das Lesen von Binärdateien kann man also nur (wenn ich nichts übersehen habe) entweder fread()-Häppchen lesen oder mit file_get_contents() im Ganzen und dann den als Puffer verwendeten String häppchenweise abklappern. Soweit zur Theorie. In deinem Fall mit den Zahlenwerten mit fester Länge bringt ein häppchenweises Lesen kaum Vorteile. Vielmehr kann man unpack() den ganzen Batzen übergeben, der kann mit V* (oder so ähnlich) die Auftrennung sehr gut selbständig vornehmen.
Lo!
Fragen sind nummeriert ?#1
, ?#2
, ...
Ich verwende jetzt keine Dummydaten mehr, sondern habe eine Binärdatei[1] geöffnet[2]. Ich habe parallel eine ASCII Datei[1] erhalten, die den Inhalt der Binärdatei enthält, um zu testen, ob ich die Binärdatei richtig entpacke.
Der Aufbau ist so: erst kommt ein Integerwert und dann folgen in Anzahl dieses Integers Floats.
Mit unpack("L*", $data_bin)
sehe ich
1 => int 4
2 => int 600
3 => int 4
4 => int 8
wobei ich die 600 lt. Ascii-Datei erwartet habe. Die "4"er sind Phantasie.
Wenn ich mir die gleiche Datei mit unpack("d*", $data_bin)
ansehe, erhalte ich:
1 => float 1.2731974745811E-311
2 => float 1.6975966329698E-313
3 => float 0.68123407969334
4 => float 1.6975966331675E-313
5 => float 0.58200506209549
wobei ich die Werte unter 3 und 5,7,9,... als erste Werte nach der 600 erwartet habe, lt. Ascii-Datei (4,6,8,... sind Phantasie). Das ist schonmal super und ein Teilerfolg.
Die Phantasiewerte erkläre ich mir momentan damit, dass evtl 64Bit == 8Byte für die Floats und Integers reserviert wurden, L aber nur 4Byte abfragt. (?#2
: Richtig?)
Alle anderen Formate bringen mir momentan nicht meine erwarteten Floats, also nur d. ?#3
: Wie lang ist d genau? Auch 4Bytes?
---
Nun würde ich gerne eine Maske schreiben, die mir die Daten perfekt entpackt.
Ganz gut passt:
$out = unpack('L2ints/d*floats', $data_bin);
Ergebniss:
array
'ints1' => int 4
'ints2' => int 600
'floats1' => float 1.6975966329698E-313
'floats2' => float 0.68123407969334
'floats3' => float 1.6975966331675E-313
'floats4' => float 0.58200506209549
oder
$out = unpack("L4ints/d*floats", $data_bin);
array
'ints1' => int 4
'ints2' => int 600
'ints3' => int 4
'ints4' => int 8
'floats1' => float 0.68123407969334
'floats2' => float 1.6975966331675E-313
'floats3' => float 0.58200506209549
'floats4' => float 1.6975966331675E-313
'floats5' => float 0.64692148179895
'floats6' => float 1.6975966331675E-313
'floats7' => float 0.72734202673086
Ähnliches mit L3 oder L5 zerhaut mir die Daten und ich erhalte keinen Wert mehr von meinen erwarteten Floats.
?#4
: Warum wandert der erste korrekte Floatwert von Position 5 bei L2 auf Position 4 bei L4? Ich hätte erwartet, dass er bei Position 5 bleibt und nur mehr Phantasiewerte dazwischen stehen...
Und schließlich:
?#5
: Wie kann ich durch einen geeigneten Formatanweiser 64Bit lesen, da ich annehme der Autor hat die Floats in 64 kodiert. Gibt es da was in php 5.3.5? Oder kann ich die Lücken bedenkenlos wegschmeißen, auch in Hinblick auf ein späteres evtl. neg. Vorzeichen?
Liebe Grüße,
LSpreee
##################################################
[1] Habe ich mal bei Dropbox hochgeladen.
[2] ?#1
: Was ist besser?
$file_hdl = fopen($filename,"rb");
$data_bin = fread($file_hdl, filesize($filename));
fclose($file_hdl);
oder
$data_bin = file_get_contents($filename);
----------------------------------------------------------
Mit diesem Code habe ich mal die Datei grob untersucht und nach geeigneten Formatanweisern gesehen. Für die Ints kommen mehrere in Frage, für die Floats passt nur d*
function getunpack($format,$data){
$results = unpack($format, $data); $i=0;
echo "<br><br>results with format $format<br><hr>";
foreach($results as $key=>$result) {
if($i == 5) break; $i++;
echo $key;
var_dump($result);
}
return $results;
}
echo $filename = "./BULKOUT_1_GMT2000.BULKBINOUT";
$data_bin = file_get_contents($filename);
$try = array("a","A","h","H","c","C","s","S","n","v","i","I","l","L","N","V","f","d","x");
foreach($try as $trythis) $out = getunpack("$trythis*", $data_bin);
Hi!
Ich verwende jetzt keine Dummydaten mehr, sondern habe eine Binärdatei[1] geöffnet[2]. Ich habe parallel eine ASCII Datei[1] erhalten, die den Inhalt der Binärdatei enthält, um zu testen, ob ich die Binärdatei richtig entpacke.
Der Aufbau ist so: erst kommt ein Integerwert und dann folgen in Anzahl dieses Integers Floats.
Mit
unpack("L*", $data_bin)
sehe ich1 => int 4
2 => int 600
3 => int 4
4 => int 8wobei ich die 600 lt. Ascii-Datei erwartet habe. Die "4"er sind Phantasie.
Die 8 gehört bereits zum ersten Wert. Wenn du dir mal die Datei in einem Hex-Editor anschaust, siehst du x04, x00, x00, x00, gefolgt von x58, x02, x00, x00 und nochmal x04, x00, x00, x00. Das x58 x02 ergibt richtigrum gedreht 258 hex = 600 dez. Mein Editor zeigt 16 Byte pro Zeile und eine Zeilennummer, nein, eigentlich eine Adresse an. Die Adresse der letzten Zeile ist 00002580, passt also zu dem 258/600. Zu sehen ist außerdem, dass in der Zeile 4 Byte fehlen, also besteht ein Eintrag aus den letzten 4 Byte einer Zeile plus 12 Byte der nachfolgenden Zeile. Auf die erste Zeile angewendet, gehört also deine 8 bereits zum ersten Wert.
Und nun versagen meine Erklärungsversuche teilweise. Ein Float hat entweder 32 oder 64 Bit, also 4 oder 8 Byte. Ein Wert in deiner Datei sind aber 16 Byte und wenn man da hinschaut, sind die 8 Bytes eines Float vorn und hinten mit 08 00 00 00 eingerahmt. Und der eigentliche Sinn davon erschließt sich mir nicht.
Wenn ich mir die gleiche Datei mit
unpack("d*", $data_bin)
ansehe, erhalte ich:1 => float 1.2731974745811E-311
2 => float 1.6975966329698E-313
3 => float 0.68123407969334
4 => float 1.6975966331675E-313
5 => float 0.58200506209549
Der erste Eintrag ist die Floatinterpretation von 04 00 00 00 58 02 00 00 und ist nicht sinnvoll, weil das ja zwei Integer sind und kein Float. Der zweite Eintrag ist der Float von 04 00 00 00 08 00 00 00 und ebenfalls nicht sinnvoll. Die 4 ist der Nachspann der Längenangabe, die 8 der Vorspann vom ersten Float. Und dieser folgt an Position 3. Verglichen mit der Auflösung (6.81234e-001) stimmt der. Dann kommt eine 8 als Nachspann und noch eine als nächster Vorspann, die sich im Element 4 wiederfinden, gefolgt von der nächsten sinnvollen Zahl in Element 5 (Auflösung: 5.82005e-001).
Die Phantasiewerte erkläre ich mir momentan damit, dass evtl 64Bit == 8Byte für die Floats und Integers reserviert wurden, L aber nur 4Byte abfragt. (
?#2
: Richtig?)
Nein, schau in den Hexeditor und probier auch mal unpack('d', "\x89\x83\xa6\x69\xab\xcc\xe5\x3f"), was die 8 Byte an Adresse 10 sind, also dein erster sinnvoller Float-Wert. Die Phantasiewerte scheinen mir eine Längenangabe zu sein, die aber immer doppelt auftritt, einmal als Vorspann und nochmal als Nachspann. Und das auch bei der Angabe der Anzahl der Floatwerte. Diese Vor- und Nachspänne bringen dir keine Punkte, denn die Längen des Zählwertes und der Nutzwerte sind fest. Es sei denn, das ändert sich von Datei zu Datei, aber auch dann wäre nur ein Vorspann nötig. Jedenfalls kannst und musst du diese "Phantasiewerte" überspringen.
Nun würde ich gerne eine Maske schreiben, die mir die Daten perfekt entpackt.
Ganz gut passt:
$out = unpack('L2ints/d*floats', $data_bin);
Ergebniss:
array
'ints1' => int 4
'ints2' => int 600
'floats1' => float 1.6975966329698E-313
Das floats1 ist der Wert aus dem Nachspann der 600 und dem Vorspann des ersten Wertes.
'floats2' => float 0.68123407969334
'floats3' => float 1.6975966331675E-313
'floats4' => float 0.58200506209549oder
$out = unpack("L4ints/d*floats", $data_bin);
array
'ints1' => int 4
'ints2' => int 600
'ints3' => int 4
'ints4' => int 8
Damit teilst du den 600er Nachspann und den ersten Vorspann in zwei Integer-Werte auf.
'floats1' => float 0.68123407969334
Die Unterschiede deiner beiden Versuche ist somit, dass die Nutzwerte entweder in geraden oder ungeraden Feldnummern stehen. Sowohl das eine als auch das andere kann nützlicher sein, je nachdem, wie du im weiteren Verlauf auf jeden zweiten Eintrag zuzugreifen gedenkst.
Ähnliches mit L3 oder L5 zerhaut mir die Daten und ich erhalte keinen Wert mehr von meinen erwarteten Floats.
Damit zerstückelst du dir deine Floats, weil der Lesezeiger immer genau auf ihrer Mitte landet.
?#4
: Warum wandert der erste korrekte Floatwert von Position 5 bei L2 auf Position 4 bei L4? Ich hätte erwartet, dass er bei Position 5 bleibt und nur mehr Phantasiewerte dazwischen stehen...
Dürfte bereits geklärt sein.
?#5
: Wie kann ich durch einen geeigneten Formatanweiser 64Bit lesen, da ich annehme der Autor hat die Floats in 64 kodiert. Gibt es da was in php 5.3.5? Oder kann ich die Lücken bedenkenlos wegschmeißen, auch in Hinblick auf ein späteres evtl. neg. Vorzeichen?
Es sind keine 64-Bit-Floats. Es bleiben 32 Bit mit 32 Bit Müll dazwischen.
[2]
?#1
: Was ist besser?
$file_hdl = fopen($filename,"rb");
$data_bin = fread($file_hdl, filesize($filename));
fclose($file_hdl);
>
> oder
>
> `$data_bin = file_get_contents($filename);`{:.language-php}
Da du unpack() den gesamten Batzen übergibst, ist letzeres effizienter zu notieren. fread() ist nur bei häppchenweisem Lesen von Vorteil.
Lo!
Wow. Deine Hilfe ist unbezahlbar. Soviele Sachen, für die ich ewig gebraucht hätte. Ich bin Dir sehr dankbar!
Die 8 gehört bereits zum ersten Wert. Wenn du dir mal die Datei in einem Hex-Editor anschaust, siehst du x04, x00, x00, x00, gefolgt von x58, x02, x00, x00 und nochmal x04, x00, x00, x00. Das x58 x02 ergibt richtigrum gedreht 258 hex = 600 dez. Mein Editor zeigt 16 Byte pro Zeile und eine Zeilennummer, nein, eigentlich eine Adresse an. Die Adresse der letzten Zeile ist 00002580, passt also zu dem 258/600. Zu sehen ist außerdem, dass in der Zeile 4 Byte fehlen, also besteht ein Eintrag aus den letzten 4 Byte einer Zeile plus 12 Byte der nachfolgenden Zeile. Auf die erste Zeile angewendet, gehört also deine 8 bereits zum ersten Wert.
Das ist eine sehr gute Herleitung.
Und nun versagen meine Erklärungsversuche teilweise. Ein Float hat entweder 32 oder 64 Bit, also 4 oder 8 Byte. Ein Wert in deiner Datei sind aber 16 Byte und wenn man da hinschaut, sind die 8 Bytes eines Float vorn und hinten mit 08 00 00 00 eingerahmt. Und der eigentliche Sinn davon erschließt sich mir nicht.
Irgendwas in der Art scheint da los zu sein. Wenn ich es mir überlege, scheint die Regel zu sein, dass jeder Wert von Interesse (die 600 und die folgenden Doubles) geklammert ist von der Bytelänge als Integer.
4 600 4
8 irgendein Double 8
8 irgendein Double 8
8 irgendein Double 8
...
Dann gehts ja auch auf?!? Komische Formatierung. Danke für die Investigation!
Und das auch bei der Angabe der Anzahl der Floatwerte.
Sagtest Du ja sogar schon... :)
Diese Vor- und Nachspänne bringen dir keine Punkte, denn die Längen des Zählwertes und der Nutzwerte sind fest. Es sei denn, das ändert sich von Datei zu Datei, aber auch dann wäre nur ein Vorspann nötig.
Genau so mache ich das wohl. Prüfen, und wenn ungleich 8, dann abbrechen. Oder kann ich dann Format f* benutzen? In der Dokumentation steht leider "machine dependent size and representation", weswegen ich nicht ganz sicher bin, ob d immer 64bit und f immer 32bit bedeutet. Das ganze wird später auf evtl. anderer Plattform laufen!
?#4
: Warum wandert der erste korrekte Floatwert von Position 5 bei L2 auf Position 4 bei L4? Ich hätte erwartet, dass er bei Position 5 bleibt und nur mehr Phantasiewerte dazwischen stehen...Dürfte bereits geklärt sein.
?#5
: Wie kann ich durch einen geeigneten Formatanweiser 64Bit lesen, da ich annehme der Autor hat die Floats in 64 kodiert. Gibt es da was in php 5.3.5? Oder kann ich die Lücken bedenkenlos wegschmeißen, auch in Hinblick auf ein späteres evtl. neg. Vorzeichen?Es sind keine 64-Bit-Floats. Es bleiben 32 Bit mit 32 Bit Müll dazwischen.
Hm. Habs jetzt aber eher so verstanden, dass alle meine Floats 64bit sind :) Muss der Satz nicht heissen (abgesehen davon, dass meine Frage davor falsch war): "Es gibt keine 128bit FLoats, es bleiben 64 bit mit 2*16bit Müll
:)
[2]
?#1
: Was ist besser?
$file_hdl = fopen($filename,"rb");
$data_bin = fread($file_hdl, filesize($filename));
fclose($file_hdl);
> >
> > oder
> >
> > `$data_bin = file_get_contents($filename);`{:.language-php}
>
> Da du unpack() den gesamten Batzen übergibst, ist letzeres effizienter zu notieren. fread() ist nur bei häppchenweisem Lesen von Vorteil.
Hier habe ich jetzt mal als kleinen Beitrag einen Performance-Test gemacht. Da ich ja eigentlich sowas wie unpack("I4/(d1/I2)\*, $data\_bin) gebraucht hätte (also mit einer etwas komplizierteren Maske) habe ich das auch mal mit fread umgesetzt. Hier der Code:
~~~php
<?php
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
echo $filename = "./BULKOUT_1_GMT2000.BULKBINOUT";
$time_start = microtime_float();
for($i = 0; $i <=2000; $i++){
$data_bin = file_get_contents($filename);
$out = unpack("I4ints/d*floats", $data_bin);
$out = array_values($out); // delete alphanum keys
$out = array_splice($out, 4); // delete first 4 elements
foreach($out as $key=>$o){ // delete every second element
if(($key%2)!=0) unset($out[$key]);
}
}
echo "<br>process lasted ".(round(microtime_float() - $time_start,4))." seconds";
var_dump($out);
$time_start = microtime_float();
for($i = 0; $i <=2000; $i++){
$out = array();
$file_hdl = fopen($filename,"rb");
$data_bin = fread($file_hdl, 16);
$head = unpack("I*", $data_bin);
for($j = 1; $j<=600; $j++){
$data_bin = fread($file_hdl, 8);
$back = unpack("d1", $data_bin);
$out[] = current($back);
$data_bin = fread($file_hdl, 8);
}
fclose($file_hdl);
}
echo "<br>process lasted ".(round(microtime_float() - $time_start,4))." seconds";
var_dump($out);
?>
$out sind beides mal gleich.
Ergebnis: ratet mal. 4,5 Sekunden zu 14 Sekunden. Und vorne liegt file_get_contents!!!
LG,
LSpreee
Hi!
Es sind keine 64-Bit-Floats. Es bleiben 32 Bit mit 32 Bit Müll dazwischen.
Hm. Habs jetzt aber eher so verstanden, dass alle meine Floats 64bit sind :) Muss der Satz nicht heissen (abgesehen davon, dass meine Frage davor falsch war): "Es gibt keine 128bit FLoats, es bleiben 64 bit mit 2*16bit Müll
Ja, so ähnlich. Ich wollte ja nur mal testen, ob du noch aufmerksam bist ... Natürlich hätte das 64 Bit Float mit 64 Bit Müll (2 x 32) dazwischen lauten sollen.
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
Diese Funktion ist schon lange überflüssig, denn man kann microtime() mit einem Parameter aufrufen, woraufhin es gleich einen ordentlichen Float liefert.
Ergebnis: ratet mal. 4,5 Sekunden zu 14 Sekunden. Und vorne liegt file_get_contents!!!
Das wundert mich nicht. Der Unterschied in der Geschwindigkeit wird hauptsächlich durch den Overhead der PHP-Funktionsaufrufe zustande kommen.
Lo!