PHP: Что эффективный путь состоит в том, чтобы проанализировать текстовый файл, содержащий очень длинные строки?

Я работаю над синтаксическим анализатором в php, который разработан для извлечения записей MySQL из текстового файла. Конкретная строка могла бы начаться со строкового соответствия, которые представляют записи в виде таблицы (строки), должен вставляться в, сопровождаться самими записями. Записи разграничены обратной косой чертой, и поля (столбцы) разделяются запятыми. Ради простоты давайте предположим, что у нас есть таблица, представляющая людей в нашей базе данных, при этом полями является Имя, Фамилия и Размещение. Таким образом одна строка файла могла бы быть следующие

[Люди] = "\Han, Соло, Smuggler\Luke, Skywalker, джедай..."

Где замещающие знаки (...) могли быть дополнительными людьми. Один простой подход мог бы быть должен использовать fgets() извлечь строку из файла и использование preg_match() извлечь имя таблицы, записи и поля от той строки.

Однако давайте предположим, что у нас есть очень много символов Star Wars для отслеживания. Так многие, на самом деле, что это концы строки, являющиеся 200,000 + символы/байты долго. В таком случае, проявляя вышеупомянутый подход для извлечения информации о базе данных кажется немного неэффективным. Необходимо сначала считать сотни тысяч символов в память, затем читать назад по тем тем же символам для нахождения соответствий regex.

Есть ли путь, подобный Java String next(String pattern) метод Scanner класс создал использование файла, который позволяет, Вы для соответствия шаблонам встраиваете при сканировании через файл?

Идея состоит в том, что Вы не должны сканировать через тот же текст дважды (чтобы считать его из файла в строку и затем соответствовать шаблонам) или хранить текст избыточно в памяти (и в строке строки файла и в подобранных шаблонах). Это даже привело бы к значительному увеличению производительности? Трудно сказать точно, что PHP или Java делают негласно.

На fgetcsv()
Эта функция делает очень легким разделить строки в файле на основе некоторого разделителя, и я уверен, что это проверяет на символ-разделитель символом, поскольку это сканирует через файл. Однако проблема состоит в том, что существует по существу два разделителя, которые я ищу, и fgetcsv() только принимает тот. Например:

Я мог использовать'' как разделитель. Если я изменил формат файла, чтобы также иметь запятые с обратной косой чертой, я мог считать всю строку в массив полей. Проблема, затем, я должен повторить по всем полям для определения, где записи запускаются и заканчиваются и подготовить sql. Точно так же, если я буду использовать '\' в качестве разделителя (единственная обратная косая черта, которой оставляют здесь), то затем я должен буду повторить по всем записям, чтобы извлечь поля и подготовить sql.

То, что я пытаюсь сделать, должно проверить и на запятые и на обратные косые черты (и возможно другие вещи, как [имя таблицы]) одним махом для максимальной производительности. Если fgetcsv() позволенный мне указывают несколько разделителей (или regex) или позволил мне изменять то, что он считает "концом строки" (от \n или \n\r только к \), затем он работал бы отлично, но это не кажется возможным.

8
задан cletus 1 April 2010 в 06:02
поделиться

2 ответа

Вы можете написать цикл накопления символов, который (а) помещает строки полей в массив, когда встречает запятые, и (б) вызывает функцию для сохранения накопленных строк полей в базу данных mysql, когда находит указатель записи:

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;
}

Это, вероятно, подойдет вам, если вы уверены, что ваши поля никогда не содержат разделителей полей или записей в качестве данных.

Если это возможно, вам нужно будет придумать управляющую последовательность для представления буквальных значений ваших разделителей полей и записей (и, возможно, вашей управляющей последовательности тоже). Предположим, что это так, и примем знак % в качестве символа эвакуации:

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;
}

т.е. любое появление % устанавливает переменную состояния, которая указывает, что при следующем проходе через цикл любой символ, который мы прочитаем, будет воспринят как буквальные данные, являющиеся частью поля, а не обозначением.

Это позволит свести использование памяти к минимуму.

[Update] Что насчет эффективности ввода-вывода?

Один из комментаторов правильно заметил, что эта иллюстрация довольно интенсивна в плане ввода-вывода, а поскольку ввод-вывод имеет тенденцию быть самой затратной операцией по времени, вполне возможно, что это не будет приемлемым решением.

На другом конце спектра у нас есть возможность буферизации всего файла в память, что включает в себя первоначальные решения, требующие большого объема памяти, о которых упоминал спрашивающий, но которых он хотел избежать. Вероятно, золотая середина лежит где-то посередине: мы можем использовать ограничение на чтение, которое можно передать в качестве второго аргумента в fgets(), чтобы получить несколько большое (но не смехотворно большое) количество символов за один ввод/вывод, а затем обрабатывать этот буфер символ за символом вместо потока ввода/вывода, пополняя его, когда мы исчерпаем буфер.

Однако это делает процесс чтения немного более трудоемким, чем $c = fgetc($fp), потому что вы должны отслеживать, где вы находитесь в буфере и насколько он заполнен, а также где вы находитесь в файле. Вы можете сделать это с помощью серии флагов и индексных переменных внутри цикла чтения, если хотите, но, возможно, будет удобнее иметь абстракцию, подобную этой:

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);
    }
}

Которую вы можете использовать в любом из приведенных выше циклов следующим образом:

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

Что-то вроде этого позволяет вам застолбить множество различных мест на континууме между решением с интенсивным использованием памяти и решением с интенсивным использованием ввода-вывода, изменяя $bufferSize. Больше $bufferSize - больше памяти, меньше операций ввода-вывода. Меньше $bufferSize - меньше памяти, больше операций ввода-вывода.

(Примечание: не думайте, что этот класс пригоден для производства. Он предназначен для иллюстрации возможной абстракции, может содержать ошибки off-by-one или другие ошибки. Может вызвать помутнение зрения, отсутствие сна, учащенное сердцебиение или другие побочные эффекты. Перед использованием проконсультируйтесь с врачом и проведите модульное тестирование.)

.
3
ответ дан 6 December 2019 в 00:55
поделиться

Может быть, использовать функцию strtok()?

$string = "Hello world. Прекрасный день сегодня"; $token = strtok($string, " ");

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

0
ответ дан 6 December 2019 в 00:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: