PHP: Çok uzun satırları içeren bir metin dosyasını ayrıştırmak için etkili yolu nedir?

2 Cevap php

Bir metin dosyası üzerinden MySQL kayıtları ayıklamak için tasarlanmış php Çözümleyici üzerinde çalışıyorum. Belirli bir satır kayıtları kendileri tarafından takip kayıtları (satırlar) sokulacak gereken hangi tablo karşılık gelen bir dize ile başlayabilir. Kayıtları bir ters eğik çizgi ile ayrılmış ve alanlar (sütunlar) virgül ile ayrılır. Basitlik aşkına, biz Adı, Soyadı ve Meslek varlık alanları ile, bizim veritabanında insanları temsil eden bir tablo olduğunu varsayalım. Aşağıdaki gibi Böylece, dosyanın bir hat olabilir

[İnsanlar] = "\ Han, Solo, Smuggler \ Luke Skywalker, Jedi ..."

Nerede elips (...) ek insanlar olabilir. Bir basit yaklaşım fgets() dosyasından bir çizgi ayıklamak için kullanmak, ve preg_match() bu hattan tablo adını, kayıtları ve alanları elde etmek için kullanmak olabilir.

Ancak, diyelim ki biz izlemek için Star Wars karakterleri bir çok şey var olduğunu varsayalım. / Bu satır 200.000 + karakter olmak biter, aslında, pek çok uzun bayt. Böyle bir durumda, veritabanı bilgiler elde etmek için, yukarıdaki yaklaşım biraz yetersiz görünmektedir. Önce, belleğe karakterlerin yüz binlerce okumak ardından back over bu aynı karakterleri regex eşleşmeleri bulmak için okumak zorunda.

Java dosyası aracılığıyla tararken sıralı desenleri maç sağlayan bir dosya kullanılarak inşa Scanner sınıfının String next(String pattern) yöntemi benzer bir yolu var mı ?

Fikir ((bir dizeye dosyadan okumak için, ve sonra desenleri maç) veya bellekte yedekli metnini saklamak kez aynı metin üzerinden taramak zorunda kalmamasıdır dosyası satır dize ve uyumlu hem de desen). Bu bile performansta önemli bir artış doğuracak? PHP veya Java perde arkasında tam olarak ne yaptığını söylemek zor.

On fgetcsv()
This function makes it very easy to split lines in a file based on some delimiter, and I'm sure it checks for the delimiter character by character as it scans through the file. However, the problem is that there's essentially two delimiters that I'm looking for, and fgetcsv() only accepts one. For example:

Ben sınırlayıcı olarak, 'kullanabilirsiniz. Ben de bir ters eğik çizgi ile virgül için dosya biçimini değiştirdi sağladı, ben alanların bir diziye bütün çizgi okuyabiliyordu. Sorun, daha sonra, ben ihtiyaç vardır reiterate kayıtları başlangıç ​​ve bitiş yerini belirlemek için ve sql hazırlamak için tüm alanları üzerinde. Ben sınırlayıcı (tek bir ters eğik çizgi, burada kaçtı) gibi '\' kullanmak Benzer şekilde, eğer, o zaman ben alanları ayıklamak ve sql hazırlamak için tüm kayıtları üzerinde reiterate gerekir.

Ne yapmaya çalışıyorum birinde ([tabloismi] gibi ve belki de diğer şeyler,) both virgül ve ters eğik kontrol etmek için maksimum performans için baskın düştü. fgetcsv() birden ayraçları (veya regex) belirtmek bana izin verilir ya da bana (\ n veya \ n \ r sadece \ için) "satır sonu" olarak değerlendirdiği ne değiştirmek için izin verdiyseniz, o mükemmel bir işe, ama bu mümkün görünmüyor.

2 Cevap

Bunu virgül karşılaşır ve (b) kayıt gösterilenden bulduğunda bir mysql veritabanına birikmiş saha dizeleri kaydetmek için bir işlev çağırdığında (a) Bir dizinin üzerine alan dizeleri iter bir karakter-karakter birikim döngü yazabilirsiniz:

while($c = fgetc($fp)) {
  if($c == ',') {
    $fields[] = implode(null,$accumulator);
    $accumulator = array();
  } else if($c == '\\') {
    save_fields_to_mysql($fields);
    $fields = array();
    $accumulator = array();
  } else
    $accumulator[] = $c;
}

Eğer alanlar alanı veya veri olarak kayıt ayırıcılar içeren asla emin iseniz bu muhtemelen sizin için çalışacaktır.

Bu bir olasılık varsa, edebi kendi alanında ve kayıt ayırıcı değerleri (ve muhtemelen kaçış dizisi de) temsil etmek için bir çıkış sırası ile gelmek gerekir. Kullanıcı bu durumda olduğunu varsayalım, ve bir kaçış karakteri olarak% işareti varsayalım:

define('ESCAPED',1);
define('NORMAL',0);

$readState = NORMAL;
while($c = fgetc($fp)) {
  if($readState == ESCAPED) {
    $accumulator[] = $c;
    $readState = NORMAL;
  } else if($c == '%') {
    $readState = ESCAPED;
  } else if($c == ',') {
    $fields[] = implode(null,$accumulator);
    $accumulator = array();
  } else if($c == '\\') {
    save_fields_to_mysql($fields);
    $fields = array();
    $accumulator = array();
  } else
    $accumulator[] = $c;
}

yani,% herhangi bir oluşumu bir alanın parçası yerine bir gösteren olduğu değişmez veri olarak alınacaktır olursa olsun karakter okuduğumuz döngü yoluyla sonraki geçişte gösteren bir devlet değişkeni ayarlar.

Bu en azından bellek kullanımı tutmalı.

[Update] What about I/O efficiency?

Bir yorumcu doğru bu illüstrasyon I / O yoğun güzel ve I / O zaman açısından en pahalı operasyon olma eğilimindedir beri, bunun kabul edilebilir bir çözüm olmaz tamamen mümkün olduğuna dikkat çekti.

Yelpazenin bir diğer ucunda biz Asker sözü ama önlemek istedim orijinal yoğun bellek çözümlerini içeren bir bellek, içine dosyanın tamamını belleğe alma seçeneği var. Mutlu orta muhtemelen bir yere ortasında yatıyor: Biz fgets(), tek bir karakter biraz büyük (ama gülünç büyük değil) sayıda çekmek için size ikinci argüman olarak geçebilir okuma limiti kullanabilirsiniz sonra I / O yutkunmak ve süreç bu tampon karakter-karakter yerine I / O dere, biz tamponu ile yakmak ne zaman dolumu.

Bu size tamponunda ve nasıl tam size dosyasında nerede tampon yanı sıra nerede izlemek zorunda çünkü, olsa da, okuma işlemini $c = fgetc($fp) daha yoğun biraz daha kod yapmak yok. İsterseniz bayrakları ve okuma döngü içinde indeks değişkenler bir dizi ile yapabilirsiniz, ama böyle bir soyutlama bir şey olması daha uygun olabilir:

class StrBufferedChrReader {

    private $_filename;
    private $_fp; 

    private $_bufferIdx;
    private $_bufferMax = 2048;
    private $_buffer;

    function __construct($filename=null,$bufferMax=null) {
        if($bufferMax) $this->_bufferMax = $bufferMax;
        if($filename) $this->open($filename);
    }

    function _refillBuffer() {
        if($this->_fp) {
            $this->_buffer = fgets($this->_fp,$this->_bufferMax + 1);
            $this->_bufferIdx = 0;
            return $this->_buffer;
        }
        return false;
    }

    function open($filename=null) {
        if($filename) $this->_filename = $filename;
        if($this->_fp = fopen($this->_filename)) 
            $this->_refillBuffer();
        return $this->_fp;
    }

    function getc() {
        if($this->_bufferIdx == $this->_bufferMax) 
            if(!$this->_refillBuffer())
                return false;
        return $this->_buffer[$this->_bufferIdx++];
    }

    function close() {
        $this->_buffer = null;
        $this->_bufferIdx = null;
        return fclose($this->_fp);
    }
}

Hangi nedenle gibi yukarıdaki iki döngüde kullanabilirsiniz:

$r = new StrBufferedChrReader($filename,$bufferSize);
while($c = $r->getc()) {
    ...

Böyle bir şey bir bellek-yoğun çözüm ve $ bufferSize değiştirerek bir I / O yoğun çözelti arasındaki sürem boyunca farklı noktalar bir sürü hissesini yapmanıza olanak sağlar. Bigger $ bufferSize, daha fazla bellek kullanımı, daha az I / O ops. Küçük $ bufferSize, daha az bellek kullanımı, daha fazla I / O ops.

(Not: Bu sınıf üretim-hazır olduğunu düşünmeyin Bu olası bir soyutlama bir örnek olarak pinti, off-by-one veya diğer hatalar içerebilir bulanık görme, uyku eksikliği, kalp çarpıntısı, ya da diğer yan etkilere neden olabilir.. etkiler. kullanmadan önce bir doktor ve birim test ile kontrol edin.)

Belki Strtok () fonksiyonunu kullanabilirsiniz?

$string = "Hello world. Beautiful day today."; $token = strtok($string, " ");

while ($token != false) { echo "$token
"; $token = strtok(" "); }