In einem meiner älteren PHP-Projekte verwende ich ASCII/Binär-Kompression. Wenn der Benutzer seine Datei hochlädt, muss er angeben, ob es sich um eine ASCII- oder Binärdatei handelt. Ich beschloss, meinen Code so zu ändern, dass der Server automatisch über den Dateimodus entscheidet, denn wenn man sich auf die Entscheidung des Benutzers verlässt, könnte die Komprimierung fehlschlagen. Ich beschloss, dass mein Code absolut sein musste und keine Tricks verwenden durfte, die mein Programm möglicherweise zum Scheitern bringen würden. Ich habe mir schnell einen Code ausgedacht, einige Geschwindigkeitstests durchgeführt und dann beschlossen, im Internet zu suchen, um zu sehen, ob es ein schnelleres Codebeispiel für diese Aufgabe gibt.
Die sehr vage Antwort von Devin bezieht sich auf den ersten Code, den ich für diese Aufgabe geschrieben habe. Die Ergebnisse waren durchwachsen. Ich fand heraus, dass die Suche nach Byte für Byte in vielen Fällen bei Binärdateien schneller war. Findet man ein Byte, das größer als 127 ist, kann der Rest der Datei ignoriert werden, und die gesamte Datei wird als Binärdatei betrachtet. Allerdings müsste man dann jedes einzelne Byte einer Datei lesen, um festzustellen, ob es sich um eine ASCII-Datei handelt. Bei vielen binären Dateien scheint es schneller zu gehen, da ein binäres Byte wahrscheinlich früher als das letzte Byte der Datei kommt, manchmal ist sogar das erste Byte binär.
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'ASCII',
2 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$handle = fopen($filename, 'rb');
for($i = 0; $i < $size; ++$i) {
$byte = fread($handle, 1);
if(ord($byte) > 127) {
fclose($handle);
return 2; // Binary
}
}
fclose($handle);
return 1; // ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);
for($i = 0; $i < $loops; ++$i)
$x = filemode($filename);
$stop = microtime(true);
$duration = $stop - $start;
echo
'Filename: ', $filename, "\n",
'Filemode: ', $filemodes[filemode($filename)], "\n",
'Duration: ', $duration;
Mein Prozessor ist nicht gerade modern, aber ich habe herausgefunden, dass eine 600 KB große ASCII-Datei etwa 0,25 Sekunden braucht, um fertig zu werden. Wenn ich dies für Hunderte oder Tausende von großen Dateien verwenden würde, könnte es sehr lange dauern. Ich beschloss, die Dinge etwas zu beschleunigen, indem ich meinen Puffer größer als ein einzelnes Byte machte, um die Datei in Blöcken zu lesen, anstatt ein Byte nach dem anderen. Durch die Verwendung von Chunks kann ich mehr von der Datei auf einmal verarbeiten, aber nicht zu viel in den Speicher laden. Wenn eine Datei, die wir testen, riesig ist und wir die gesamte Datei in den Speicher laden würden, könnte das viel zu viel Speicherplatz beanspruchen und das Programm zum Scheitern bringen.
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'ASCII',
2 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$buffer_size = 256;
$chunks = ceil($size / $buffer_size);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
$buffer_length = strlen($buffer);
for($byte = 0; $byte < $buffer_length; ++$byte) {
if(ord($buffer[$byte]) > 127) {
fclose($handle);
return 2; // Binary
}
}
}
fclose($handle);
return 1; // ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);
for($i = 0; $i < $loops; ++$i)
$x = filemode($filename);
$stop = microtime(true);
$duration = $stop - $start;
echo
'Filename: ', $filename, "\n",
'Filemode: ', $filemodes[filemode($filename)], "\n",
'Duration: ', $duration;
Der Geschwindigkeitsunterschied war ziemlich signifikant, da ich nur 0,15 Sekunden statt der 0,25 Sekunden der vorherigen Funktion benötigte, also fast eine Zehntelsekunde schneller, um meine 600Kb-ASCII-Datei zu lesen.
Da ich meine Datei nun in Chunks unterteilt habe, dachte ich, es wäre eine gute Idee, alternative Möglichkeiten zu finden, um meine Chunks auf binäre Zeichen zu testen. Mein erster Gedanke war, einen regulären Ausdruck zu verwenden, um Nicht-Ascii-Zeichen zu finden.
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'ASCII',
2 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$buffer_size = 256;
$chunks = ceil($size / $buffer_size);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
fclose($handle);
return 2; // Binary
}
}
fclose($handle);
return 1; // ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);
for($i = 0; $i < $loops; ++$i)
$x = filemode($filename);
$stop = microtime(true);
$duration = $stop - $start;
echo
'Filename: ', $filename, "\n",
'Filemode: ', $filemodes[filemode($filename)], "\n",
'Duration: ', $duration;
Erstaunlich! 0,02 Sekunden, um meine 600Kb-Datei als ASCII-Datei zu betrachten, und dieser Code scheint 100% zuverlässig zu sein.
Jetzt, wo ich hier angekommen bin, habe ich die Möglichkeit, verschiedene andere Methoden zu inspizieren, die von anderen Nutzern eingesetzt werden.
Die heute am meisten akzeptierte Antwort, geschrieben von davethegr8, verwendet die Mimetype-Erweiterung. Zunächst musste ich diese Erweiterung in der Datei php.ini aktivieren. Dann habe ich diesen Code mit einer ASCII-Datei ohne Dateierweiterung und einer Binärdatei ohne Dateierweiterung getestet.
So habe ich meine beiden Testdateien erstellt.
<?php
$handle = fopen('E:\ASCII', 'wb');
for($i = 0; $i < 128; ++$i) {
fwrite($handle, chr($i));
}
fclose($handle);
$handle = fopen('E:\Binary', 'wb');
for($i = 0; $i < 256; ++$i) {
fwrite($handle, chr($i));
}
fclose($handle);
So habe ich beide Dateien getestet...
<?php
$filename = 'E:\ASCII';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';
Welche Ausgaben:
Binär
und...
<?php
$filename = 'E:\Binary';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';
Welche Ausgaben:
Binär
Dieser Code zeigt an, dass sowohl meine ASCII- als auch meine Binärdateien binär sind, was natürlich nicht stimmt, also musste ich herausfinden, warum der Mimetyp "Text" ist. Für mich war klar, dass Text vielleicht nur druckbare ASCII-Zeichen sind. Also habe ich den Bereich meiner ASCII-Datei eingeschränkt.
<?php
$handle = fopen('E:\ASCII', 'wb');
for($i = 32; $i < 127; ++$i) {
fwrite($handle, chr($i));
}
fclose($handle);
Und wieder getestet.
<?php
$filename = 'E:\ASCII';
$finfo = finfo_open(FILEINFO_MIME);
echo (substr(finfo_file($finfo, $filename), 0, 4) == 'text') ? 'ASCII' : 'Binary';
Welche Ausgaben:
ASCII
Wenn ich den Bereich verringere, wird er als binär behandelt. Erhöhe ich den Bereich, wird er wieder als binär behandelt.
Die am meisten akzeptierte Antwort sagt Ihnen also nicht, ob Ihre Datei ASCII ist, sondern eher, ob sie nur lesbaren Text enthält oder nicht.
Schließlich muss ich die andere Antwort, die ctype_print verwendet, mit meinen Dateien testen. Ich habe beschlossen, dass es am einfachsten ist, den von mir erstellten Code zu verwenden und MarcoAs Code zu ergänzen.
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'ASCII',
2 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$buffer_size = 256;
$chunks = ceil($size / $buffer_size);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
$buffer = str_ireplace("\t", '', $buffer);
$buffer = str_ireplace("\n", '', $buffer);
$buffer = str_ireplace("\r", '', $buffer);
if(ctype_print($buffer) === false) {
fclose($handle);
return 2; // Binary
}
}
fclose($handle);
return 1; // ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);
for($i = 0; $i < $loops; ++$i)
$x = filemode($filename);
$stop = microtime(true);
$duration = $stop - $start;
echo
'Filename: ', $filename, "\n",
'Filemode: ', $filemodes[filemode($filename)], "\n",
'Duration: ', $duration;
Autsch! 0,2 Sekunden, um mir mitzuteilen, dass meine 600Kb-Datei ASCII ist. Meine große ASCII-Datei enthält, wie ich weiß, nur sichtbare ASCII-Zeichen. Es scheint zu wissen, dass meine binären Dateien binär sind. Und meine reine ASCII-Datei... Binär!
Ich habe mich entschlossen, die Dokumentation für ctype_print und sein Rückgabewert ist definiert als:
Gibt TRUE zurück, wenn jedes Zeichen im Text tatsächlich eine Ausgabe erzeugt (einschließlich Leerzeichen). Gibt FALSE zurück, wenn der Text Steuerzeichen enthält oder Zeichen enthält, die überhaupt keine Ausgabe- oder Kontrollfunktion haben.
Diese Funktion, wie auch die Antwort von davethegr8, sagt Ihnen nur, ob Ihr Text druckbare ASCII-Zeichen enthält, aber nicht, ob Ihr Text tatsächlich ASCII ist oder nicht. Das bedeutet nicht unbedingt, dass MacroA völlig falsch ist, sie sind nur nicht ganz richtig. str_ireplace ist langsam im Vergleich zu str_replace, und nur diese drei Steuerzeichen zu ersetzen, um ctype_print zu testen, ist nicht genug, um zu wissen, ob der String ASCII ist oder nicht. Damit dieses Beispiel für ASCII funktioniert, müssen wir jedes Steuerzeichen ersetzen!
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'ASCII',
2 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$buffer_size = 256;
$chunks = ceil($size / $buffer_size);
$replace = array(
"\x00", "\x01", "\x02", "\x03",
"\x04", "\x05", "\x06", "\x07",
"\x08", "\x09", "\x0A", "\x0B",
"\x0C", "\x0D", "\x0E", "\x0F",
"\x10", "\x11", "\x12", "\x13",
"\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1A", "\x1B",
"\x1C", "\x1D", "\x1E", "\x1F",
"\x7F"
);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
$buffer = str_replace($replace, '', $buffer);
if(ctype_print($buffer) === false) {
fclose($handle);
return 2; // Binary
}
}
fclose($handle);
return 1; // ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
Es dauerte 0,04 Sekunden, um mir zu sagen, dass meine 600 KB große Datei ASCII ist.
Ich glaube, all diese Tests waren nicht völlig nutzlos, denn sie haben mich auf eine weitere Idee gebracht. Warum nicht einen druckbaren Dateimodus zu meiner ursprünglichen Funktion hinzufügen! Es scheint zwar 0,018 Sekunden langsamer zu sein bei meiner 600Kb druckbaren ASCII-Datei, aber hier ist sie.
<?php
$filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'Printable',
2 => 'ASCII',
3 => 'Binary'
);
function filemode($filename) {
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
$printable = true;
$buffer_size = 256;
$chunks = ceil($size / $buffer_size);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
fclose($handle);
return 3; // Binary
}
else
if($printable === true)
$printable = ctype_print($buffer);
}
fclose($handle);
return $printable === true ? 1 : 2; // Printable or ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
$loops = 1;
$x = 0;
$i = 0;
$start = microtime(true);
for($i = 0; $i < $loops; ++$i)
$x = filemode($filename);
$stop = microtime(true);
$duration = $stop - $start;
echo
'Filename: ', $filename, "\n",
'Filemode: ', $filemodes[filemode($filename)], "\n",
'Duration: ', $duration;
Ich habe auch ctype_print gegen einen regulären Ausdruck getestet und festgestellt, dass ctype_print ein bisschen schneller ist.
$printable = preg_match('/[^\x20-\x7E]/', $buffer) === 0;
Hier ist meine letzte Funktion, bei der die Suche nach druckbarem Text optional ist, ebenso wie die Puffergröße.
<?php
const filemodes = array(
-2 => 'Unreadable',
-1 => 'Missing',
0 => 'Empty',
1 => 'Printable',
2 => 'ASCII',
3 => 'Binary'
);
function filemode($filename, $printable = false, $buffer_size = 256) {
if(is_bool($printable) === false || is_int($buffer_size) === false)
return false;
$buffer_size = floor($buffer_size);
if($buffer_size <= 0)
return false;
if(is_file($filename)) {
if(is_readable($filename)) {
$size = filesize($filename);
if($size === 0)
return 0; // Empty
if($buffer_size > $size)
$buffer_size = $size;
$chunks = ceil($size / $buffer_size);
$handle = fopen($filename, 'rb');
for($chunk = 0; $chunk < $chunks; ++$chunk) {
$buffer = fread($handle, $buffer_size);
if(preg_match('/[\x80-\xFF]/', $buffer) === 1) {
fclose($handle);
return 3; // Binary
}
else
if($printable === true)
$printable = ctype_print($buffer);
}
fclose($handle);
return $printable === true ? 1 : 2; // Printable or ASCII
}
else
return -2; // Unreadable
}
else
return -1; // Missing
}
// ==========
$filename = 'e:\test.txt';
echo
'Filename: ', $filename, "\n",
'Filemode: ', filemodes[filemode($filename, true)], "\n";
0 Stimmen
Dies wurde bereits zuvor gefragt, aber ich frage mich immer wieder, warum es dir wichtig ist, ob es ASCII oder binär ist?
0 Stimmen
Ähnlich, aber kein Duplikat. Diese hat eine einfache, technische Antwort, während die vermeintlich identische Frage ziemlich schwierig ist. Es besteht ein großer Unterschied darin, zu fragen, ob eine Datei in Codierung X ist oder überhaupt in irgendeiner Codierung.
0 Stimmen
Nein, lies es noch einmal, diese Typen waren nur Beispiele. Er sucht nach demselben Ding binär vs. Text.
0 Stimmen
Es ist kein Duplikat, da dies eine allgemeine Frage ist (eher eine theoretische Frage), und dies ist für eine spezifische Sprache (praktische Verwendung). Auf jeden Fall, was ich letztendlich gemacht habe, ist unten.
0 Stimmen
@Pyrolistical: Um zu überprüfen, ob uploaded.avi etwas anderes ist, als das Überprüfen des MIME nicht gut genug zu funktionieren scheint.
0 Stimmen
@davethegr8 Ich weiß, dass dies eine sehr alte Frage ist, aber besteht die Möglichkeit, dass Sie Ihre ausgewählte Antwort überprüfen würden?