Как заменить / игнорировать недопустимые символы Unicode / UTF8 C из C stdio.h getline ()? - PullRequest
2 голосов
/ 14 июня 2019

В Python есть опция errors='ignore' для функции open Python:

open( '/filepath.txt', 'r', encoding='UTF-8', errors='ignore' )

При этом чтение файла с недопустимыми символами UTF8 заменит их ничем, то есть они игнорируются. Например, файл с символами Føö»BÃ¥r будет читаться как FøöBår.

Если строка как Føö»BÃ¥r читается с getline() из stdio.h, она будет читаться как Føö�Bår:

FILE* cfilestream = fopen( "/filepath.txt", "r" );
int linebuffersize = 131072;
char* readline = (char*) malloc( linebuffersize );

while( true )
{
    if( getline( &readline, &linebuffersize, cfilestream ) != -1 ) {
        std::cerr << "readline=" readline << std::endl;
    }
    else {
        break;
    }
}

Как я могу заставить stdio.h getline() читать его как FøöBår вместо Føö�Bår, т.е. игнорировать недопустимые символы UTF8?

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

FILE* cfilestream = fopen( "/filepath.txt", "r" );
int linebuffersize = 131072;
char* readline = (char*) malloc( linebuffersize );
char* fixedreadline = (char*) malloc( linebuffersize );

int index;
int charsread;
int invalidcharsoffset;

while( true )
{
    if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
    {
        invalidcharsoffset = 0;
        for( index = 0; index < charsread; ++index )
        {
            if( readline[index] != '�' ) {
                fixedreadline[index-invalidcharsoffset] = readline[index];
            } 
            else {
                ++invalidcharsoffset;
            }
        }
        std::cerr << "fixedreadline=" << fixedreadline << std::endl;
    }
    else {
        break;
    }
}

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

  1. Исправление недопустимых символов UTF8
  2. Замена не-UTF8 символов
  3. Python заменяет символы Юникода
  4. Юникод Python: как заменить символ, который не может быть декодирован с помощью utf8, пробелом?

Ответы [ 3 ]

3 голосов
/ 15 июня 2019

Как хорошо объясняет @rici в своем ответе, в последовательности байтов может быть несколько недопустимых последовательностей UTF-8.

Возможно, стоит взглянуть на iconv (3), например, см https://linux.die.net/man/3/iconv_open.

Когда строка "// IGNORE" добавляется к для кодирования , символы, которые не могут быть представлены в целевом наборе символов, будут отбрасываться без уведомления.

Пример

Эта байтовая последовательность, если она интерпретируется как UTF-8, содержит недопустимый UTF-8:

"some invalid\xFE\xFE\xFF\xFF stuff"

Если вы отобразите это, вы увидите что-то вроде

some invalid���� stuff

Когда эта строка проходит через функцию remove_invalid_utf8 в следующей программе на C, недействительные байты UTF-8 удаляются с помощью функции iconv, упомянутой выше.

Итак, результат:

some invalid stuff

C Программа

#include <stdio.h>
#include <iconv.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>

char *remove_invalid_utf8(char *utf8, size_t len) {
    size_t inbytes_len = len;
    char *inbuf = utf8;

    size_t outbytes_len = len;
    char *result = calloc(outbytes_len + 1, sizeof(char));
    char *outbuf = result;

    iconv_t cd = iconv_open("UTF-8//IGNORE", "UTF-8");
    if(cd == (iconv_t)-1) {
        perror("iconv_open");
    }
    if(iconv(cd, &inbuf, &inbytes_len, &outbuf, &outbytes_len)) {
        perror("iconv");
    }
    iconv_close(cd);
    return result;
}

int main() {
    char *utf8 = "some invalid\xFE\xFE\xFF\xFF stuff";
    char *converted = remove_invalid_utf8(utf8, strlen(utf8));
    printf("converted: %s to %s\n", utf8, converted);
    free(converted);
    return 0;
}
3 голосов
/ 14 июня 2019

Вы путаете то, что видите, с тем, что на самом деле происходит.Функция getline не выполняет никакой замены символов.[Примечание 1]

Вы видите заменяющий символ (U + FFFD), потому что ваша консоль выводит этот символ, когда ее просят отобразить недопустимый код UTF-8.Большинство консолей будут делать это, если они находятся в режиме UTF-8;то есть текущим языковым стандартом является UTF-8.

Кроме того, сказать, что файл содержит «символы Føö»BÃ¥r», в лучшем случае неточно.Файл на самом деле не содержит символов.Он содержит последовательности байтов, которые могут интерпретироваться как символы - например, консолью или другим программным обеспечением для представления пользователя, которое преобразует их в глифы - в соответствии с некоторой кодировкой.Разные кодировки дают разные результаты;в данном конкретном случае у вас есть файл, который был создан программным обеспечением с использованием кодировки Windows-1252 (или, что примерно эквивалентно ISO 8859-15), и вы отображаете его на консоли с использованием UTF-8.

Это означает, что данные, прочитанные getline, содержат недопустимую последовательность UTF-8, но они (вероятно) не содержат код символа замены.Основываясь на представленной вами символьной строке, она содержит шестнадцатеричный символ \xbb, который является guillemot (») в кодовой странице Windows 1252.

Поиск всех недопустимых последовательностей UTF-8 в считанной строкепо getline (или любой другой функции библиотеки C, которая читает файлы) требует сканирования строки, но не для конкретной кодовой последовательности.Скорее, вам нужно декодировать последовательности UTF-8 по одной, ища те, которые недопустимы.Это не простая задача, но может помочь функция mbtowc (если вы включили локаль UTF-8).Как вы увидите на связанной справочной странице, mbtowc возвращает количество байтов, содержащихся в допустимой «многобайтовой последовательности» (которая является UTF-8 в локали UTF-8), или «-1», чтобы указать недопустимую или неполную последовательность,При сканировании вы должны пройти через байты в правильной последовательности или удалить / проигнорировать один байт, начинающий недопустимую последовательность, а затем продолжить сканирование, пока не достигнете конца строки.

Вот некоторые слегкапример кода (на C):

#include <stdlib.h>
#include <string.h>

/* Removes in place any invalid UTF-8 sequences from at most 'len' characters of the
 * string pointed to by 's'. (If a NUL byte is encountered, conversion stops.)
 * If the length of the converted string is less than 'len', a NUL byte is
 * inserted.
 * Returns the length of the possibly modified string (with a maximum of 'len'),
 * not including the NUL terminator (if any).
 * Requires that a UTF-8 locale be active; since there is no way to test for
 * this condition, no attempt is made to do so. If the current locale is not UTF-8,
 * behaviour is undefined.
 */
size_t remove_bad_utf8(char* s, size_t len) {
  char* in = s;
  /* Skip over the initial correct sequence. Avoid relying on mbtowc returning
   * zero if n is 0, since Posix is not clear whether mbtowc returns 0 or -1.
   */
  int seqlen;
  while (len && (seqlen = mbtowc(NULL, in, len)) > 0) { len -= seqlen; in += seqlen; }
  char* out = in;

  if (len && seqlen < 0) {
    ++in;
    --len;
    /* If we find an invalid sequence, we need to start shifting correct sequences.  */
    for (; len; in += seqlen, len -= seqlen) {
      seqlen = mbtowc(NULL, in, len);
      if (seqlen > 0) {
        /* Shift the valid sequence (if one was found) */
        memmove(out, in, seqlen);
        out += seqlen;
      }
      else if (seqlen < 0) seqlen = 1;
      else /* (seqlen == 0) */ break;
    }
    *out++ = 0;
  }
  return out - s;
}

Примечания

  1. Помимо возможного преобразования конца строки базовой библиотеки ввода-вывода, которая заменитCR-LF с одним \n в таких системах, как Windows, где двухсимвольная последовательность CR-LF используется в качестве индикации конца строки.
1 голос
/ 16 июня 2019

Мне также удалось исправить это, завершив / вырезав все символы не ASCII.

Этот анализ занимает около 2.6 секунд для анализа 319 МБ:

#include <stdlib.h>
#include <iostream>

int main(int argc, char const *argv[])
{
    FILE* cfilestream = fopen( "./test.txt", "r" );
    size_t linebuffersize = 131072;

    if( cfilestream == NULL ) {
        perror( "fopen cfilestream" );
        return -1;
    }

    char* readline = (char*) malloc( linebuffersize );
    char* fixedreadline = (char*) malloc( linebuffersize );

    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    if( fixedreadline == NULL ) {
        perror( "malloc fixedreadline" );
        return -1;
    }

    char* source;
    if( ( source = std::setlocale( LC_ALL, "en_US.utf8" ) ) == NULL ) {
        perror( "setlocale" );
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }
    int index;
    int charsread;
    int invalidcharsoffset;
    unsigned int fixedchar;

    while( true )
    {
        if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
        {
            invalidcharsoffset = 0;
            for( index = 0; index < charsread; ++index )
            {
                fixedchar = static_cast<unsigned int>( readline[index] );
                // std::cerr << "index " << std::setw(3) << index
                //         << " readline " << std::setw(10) << fixedchar
                //         << " -> '" << readline[index] << "'" << std::endl;

                if( 31 < fixedchar && fixedchar < 128 ) {
                    fixedreadline[index-invalidcharsoffset] = readline[index];
                }
                else {
                    ++invalidcharsoffset;
                }
            }

            fixedreadline[index-invalidcharsoffset] = '\0';
            // std::cerr << "fixedreadline=" << fixedreadline << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "fixedreadline=" << fixedreadline << std::endl;
    free( readline );
    free( fixedreadline );

    fclose( cfilestream );
    return 0;
}

Альтернативная и более медленная версия с использованием memcpy

Использование menmove не сильно увеличивает скорость, поэтому вы можете выбрать один из них.

Этот процесс занимает около 3.1 секунддля анализа 319 МБ:

#include <stdlib.h>
#include <iostream>
#include <cstring>
#include <iomanip>

int main(int argc, char const *argv[])
{
    FILE* cfilestream = fopen( "./test.txt", "r" );
    size_t linebuffersize = 131072;

    if( cfilestream == NULL ) {
        perror( "fopen cfilestream" );
        return -1;
    }

    char* readline = (char*) malloc( linebuffersize );
    char* fixedreadline = (char*) malloc( linebuffersize );

    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    if( fixedreadline == NULL ) {
        perror( "malloc fixedreadline" );
        return -1;
    }
    char* source;
    char* destination;
    char* finalresult;

    int index;
    int lastcopy;
    int charsread;
    int charstocopy;
    int invalidcharsoffset;

    bool hasignoredbytes;
    unsigned int fixedchar;

    if( ( source = std::setlocale( LC_ALL, "en_US.utf8" ) ) == NULL ) {
        perror( "setlocale" );
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }

    while( true )
    {
        if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
        {
            hasignoredbytes = false;
            source = readline;
            destination = fixedreadline;
            lastcopy = 0;
            invalidcharsoffset = 0;
            for( index = 0; index < charsread; ++index )
            {
                fixedchar = static_cast<unsigned int>( readline[index] );
                // std::cerr << "fixedchar " << std::setw(10)
                //           << fixedchar << " -> '"
                //           << readline[index] << "'" << std::endl;

                if( 31 < fixedchar && fixedchar < 128 ) {
                    if( hasignoredbytes ) {
                        charstocopy = index - lastcopy - invalidcharsoffset;
                        memcpy( destination, source, charstocopy );

                        source += index - lastcopy;
                        lastcopy = index;
                        destination += charstocopy;

                        invalidcharsoffset = 0;
                        hasignoredbytes = false;
                    }
                }
                else {
                    ++invalidcharsoffset;
                    hasignoredbytes = true;
                }
            }
            if( destination != fixedreadline ) {
                charstocopy = charsread - static_cast<int>( source - readline )
                               - invalidcharsoffset;

                memcpy( destination, source, charstocopy );
                destination += charstocopy - 1;

                if( *destination == '\n' ) {
                    *destination = '\0';
                }
                else {
                    *++destination = '\0';
                }
                finalresult = fixedreadline;
            }
            else {
                finalresult = readline;
            }

            // std::cerr << "finalresult=" << finalresult << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "finalresult=" << finalresult << std::endl;

    free( readline );
    free( fixedreadline );

    fclose( cfilestream );
    return 0;
}

Оптимизированное решение с использованием iconv

Это займет около 4.6 секунд для анализа 319 МБ текста.

#include <iconv.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>

// Compile it with:
//     g++ -o main test.cpp -O3 -liconv
int main(int argc, char const *argv[])
{
    FILE* cfilestream = fopen( "./test.txt", "r" );
    size_t linebuffersize = 131072;

    if( cfilestream == NULL ) {
        perror( "fopen cfilestream" );
        return -1;
    }

    char* readline = (char*) malloc( linebuffersize );
    char* fixedreadline = (char*) malloc( linebuffersize );

    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    if( fixedreadline == NULL ) {
        perror( "malloc fixedreadline" );
        return -1;
    }
    char* source;
    char* destination;

    int charsread;
    size_t inchars;
    size_t outchars;

    if( ( source = std::setlocale( LC_ALL, "en_US.utf8" ) ) == NULL ) {
        perror( "setlocale" );
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }

    iconv_t conversiondescriptor = iconv_open("UTF-8//IGNORE", "UTF-8");
    if( conversiondescriptor == (iconv_t)-1 ) {
        perror( "iconv_open conversiondescriptor" );
    }
    while( true )
    {
        if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
        {
            source = readline;
            inchars = charsread;

            destination = fixedreadline;
            outchars = charsread;

            if( iconv( conversiondescriptor, &source, &inchars, &destination, &outchars ) )
            {
                perror( "iconv" );
            }

            // Trim out the new line character
            if( *--destination == '\n' ) {
                *--destination = '\0';
            }
            else {
                *destination = '\0';
            }

            // std::cerr << "fixedreadline='" << fixedreadline << "'" << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "fixedreadline='" << fixedreadline << "'" << std::endl;
    free( readline );
    free( fixedreadline );

    if( fclose( cfilestream ) ) {
        perror( "fclose cfilestream" );
    }

    if( iconv_close( conversiondescriptor ) ) {
        perror( "iconv_close conversiondescriptor" );
    }

    return 0;
}

Самое медленное решение, когда-либо использующее mbtowc

Это займет около 24.2 секунд для анализа 319 МБ текста.

Если вы закомментируете строку fixedchar = mbtowc(NULL, source, charsread); и раскомментируете строку charsread -= fixedchar; (прерывая удаление недопустимых символов), это займет 1.9 секунды вместо 24.2 секунд (также скомпилировано с -O3 уровнем оптимизации).

#include <stdlib.h>
#include <string.h>

#include <iostream>
#include <cstring>
#include <iomanip>

int main(int argc, char const *argv[])
{
    FILE* cfilestream = fopen( "./test.txt", "r" );
    size_t linebuffersize = 131072;

    if( cfilestream == NULL ) {
        perror( "fopen cfilestream" );
        return -1;
    }

    char* readline = (char*) malloc( linebuffersize );
    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    char* source;
    char* lineend;
    char* destination;
    int charsread;
    int fixedchar;

    if( ( source = std::setlocale( LC_ALL, "en_US.utf8" ) ) == NULL ) {
        perror( "setlocale" );
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }
    while( true )
    {
        if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
        {
            lineend = readline + charsread;
            destination = readline;
            for( source = readline; source != lineend; )
            {
                // fixedchar = 1;
                fixedchar = mbtowc(NULL, source, charsread);
                charsread -= fixedchar;

                // std::ostringstream contents;
                // for( int index = 0; index < fixedchar; ++index )
                //         contents << source[index];

                // std::cerr << "fixedchar=" << std::setw(10)
                //         << fixedchar << " -> '"
                //         << contents.str().c_str() << "'" << std::endl;

                if( fixedchar > 0 ) {
                    memmove( destination, source, fixedchar );
                    source += fixedchar;
                    destination += fixedchar;
                }
                else if( fixedchar < 0 ) {
                    source += 1;
                    // std::cerr << "errno=" << strerror( errno ) << std::endl;
                }
                else {
                    break;
                }
            }
            // Trim out the new line character
            if( *--destination == '\n' ) {
                *--destination = '\0';
            }
            else {
                *destination = '\0';
            }

            // std::cerr << "readline='" << readline << "'" << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "readline='" << readline << "'" << std::endl;

    if( fclose( cfilestream ) ) {
        perror( "fclose cfilestream" );
    }

    free( readline );
    return 0;
}

Самая быстрая версия из всех моих предыдущих с использованием memmove

YВы не можете использовать memcpy здесь, потому что области памяти перекрываются!

Это займет около 2.4 секунд для анализа 319 МБ.

Если вы закомментируете строки *destination = *source и memmove( destination, source, 1 ) (нарушение удаления недопустимых символов) производительность по-прежнему почти такая же, как при вызове memmove.Здесь вызов memmove( destination, source, 1 ) немного медленнее, чем прямой *destination = *source;

#include <stdlib.h>
#include <iostream>
#include <cstring>
#include <iomanip>

int main(int argc, char const *argv[])
{
    FILE* cfilestream = fopen( "./test.txt", "r" );
    size_t linebuffersize = 131072;

    if( cfilestream == NULL ) {
        perror( "fopen cfilestream" );
        return -1;
    }

    char* readline = (char*) malloc( linebuffersize );
    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    char* source;
    char* lineend;
    char* destination;

    int charsread;
    unsigned int fixedchar;

    if( ( source = std::setlocale( LC_ALL, "en_US.utf8" ) ) == NULL ) {
        perror( "setlocale" );
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }

    while( true )
    {
        if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
        {
            lineend = readline + charsread;
            destination = readline;
            for( source = readline; source != lineend; ++source )
            {
                fixedchar = static_cast<unsigned int>( *source );
                // std::cerr << "fixedchar=" << std::setw(10)
                //         << fixedchar << " -> '" << *source << "'" << std::endl;

                if( 31 < fixedchar && fixedchar < 128 ) {
                    *destination = *source;
                    ++destination;
                }
            }

            // Trim out the new line character
            if( *source == '\n' ) {
                *--destination = '\0';
            }
            else {
                *destination = '\0';
            }

            // std::cerr << "readline='" << readline << "'" << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "readline='" << readline << "'" << std::endl;
    if( fclose( cfilestream ) ) {
        perror( "fclose cfilestream" );
    }

    free( readline );
    return 0;
}

Bonus

Вы также можете использовать расширения Python C (API).

Для анализа 319 МБ без преобразования их в кэшированную версию требуется около 2.3 секунд UTF-8 char*

И для анализа 319 МБ требуется около 3.2 секунд для преобразования их в UTF-8символ *.А также занимает около 3.2 секунд, чтобы проанализировать 319 МБ, преобразовав их в кэшированные ASCII char *.

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <iostream>

typedef struct
{
    PyObject_HEAD
}
PyFastFile;

static PyModuleDef fastfilepackagemodule =
{
    // https://docs.python.org/3/c-api/module.html#c.PyModuleDef
    PyModuleDef_HEAD_INIT,
    "fastfilepackage", /* name of module */
    "Example module that wrapped a C++ object", /* module documentation, may be NULL */
    -1, /* size of per-interpreter state of the module, or 
                -1 if the module keeps state in global variables. */

    NULL, /* PyMethodDef* m_methods */
    NULL, /* inquiry m_reload */
    NULL, /* traverseproc m_traverse */
    NULL, /* inquiry m_clear */
    NULL, /* freefunc m_free */
};

// initialize PyFastFile Object
static int PyFastFile_init(PyFastFile* self, PyObject* args, PyObject* kwargs) {
    char* filepath;

    if( !PyArg_ParseTuple( args, "s", &filepath ) ) {
        return -1;
    }

    int linecount = 0;
    PyObject* iomodule;
    PyObject* openfile;
    PyObject* fileiterator;

    iomodule = PyImport_ImportModule( "builtins" );
    if( iomodule == NULL ) {
        std::cerr << "ERROR: FastFile failed to import the io module '"
                "(and open the file " << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    PyObject* openfunction = PyObject_GetAttrString( iomodule, "open" );

    if( openfunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module open "
                << "function (and open the file '" << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    openfile = PyObject_CallFunction( 
            openfunction, "ssiss", filepath, "r", -1, "ASCII", "ignore" );

    if( openfile == NULL ) {
        std::cerr << "ERROR: FastFile failed to open the file'"
                << filepath << "'!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    PyObject* iterfunction = PyObject_GetAttrString( openfile, "__iter__" );
    Py_DECREF( openfunction );

    if( iterfunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator" 
                << "function (and open the file '" << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    PyObject* openiteratorobject = PyObject_CallObject( iterfunction, NULL );
    Py_DECREF( iterfunction );

    if( openiteratorobject == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator object"
                << " (and open the file '" << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    fileiterator = PyObject_GetAttrString( openfile, "__next__" );
    Py_DECREF( openiteratorobject );

    if( fileiterator == NULL ) {
        std::cerr << "ERROR: FastFile failed get the io module iterator "
                << "object (and open the file '" << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }

    PyObject* readline;
    while( ( readline = PyObject_CallObject( fileiterator, NULL ) ) != NULL ) {
        linecount += 1;
        PyUnicode_AsUTF8( readline );
        Py_DECREF( readline );
        // std::cerr << "linecount " << linecount << " readline '" << readline
        //         << "' '" << PyUnicode_AsUTF8( readline ) << "'" << std::endl;
    }
    std::cerr << "linecount " << linecount << std::endl;

    // PyErr_PrintEx(100);
    PyErr_Clear();
    PyObject* closefunction = PyObject_GetAttrString( openfile, "close" );

    if( closefunction == NULL ) {
        std::cerr << "ERROR: FastFile failed get the close file function for '"
                << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }

    PyObject* closefileresult = PyObject_CallObject( closefunction, NULL );
    Py_DECREF( closefunction );

    if( closefileresult == NULL ) {
        std::cerr << "ERROR: FastFile failed close open file '"
                << filepath << "')!" << std::endl;
        PyErr_PrintEx(100);
        return -1;
    }
    Py_DECREF( closefileresult );

    Py_XDECREF( iomodule );
    Py_XDECREF( openfile );
    Py_XDECREF( fileiterator );

    return 0;
}

// destruct the object
static void PyFastFile_dealloc(PyFastFile* self) {
    Py_TYPE(self)->tp_free( (PyObject*) self );
}

static PyTypeObject PyFastFileType =
{
    PyVarObject_HEAD_INIT( NULL, 0 )
    "fastfilepackage.FastFile" /* tp_name */
};

// create the module
PyMODINIT_FUNC PyInit_fastfilepackage(void)
{
    PyObject* thismodule;

    // https://docs.python.org/3/c-api/typeobj.html
    PyFastFileType.tp_new = PyType_GenericNew;
    PyFastFileType.tp_basicsize = sizeof(PyFastFile);
    PyFastFileType.tp_dealloc = (destructor) PyFastFile_dealloc;
    PyFastFileType.tp_flags = Py_TPFLAGS_DEFAULT;
    PyFastFileType.tp_doc = "FastFile objects";
    PyFastFileType.tp_init = (initproc) PyFastFile_init;

    if( PyType_Ready( &PyFastFileType) < 0 ) {
        return NULL;
    }

    thismodule = PyModule_Create(&fastfilepackagemodule);
    if( thismodule == NULL ) {
        return NULL;
    }

    // Add FastFile class to thismodule allowing the use to create objects
    Py_INCREF( &PyFastFileType );
    PyModule_AddObject( thismodule, "FastFile", (PyObject*) &PyFastFileType );
    return thismodule;
}

Чтобы создать его, создайте файл source/fastfilewrappar.cpp с содержимым вышеуказанного файла и setup.py со следующим содержимым:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup, Extension

myextension = Extension(
    language = "c++",
    extra_link_args = ["-std=c++11"],
    extra_compile_args = ["-std=c++11"],
    name = 'fastfilepackage',
    sources = [
        'source/fastfilewrapper.cpp'
    ],
    include_dirs = [ 'source' ],
)

setup(
        name = 'fastfilepackage',
        ext_modules= [ myextension ],
    )

Для запуска примера используйте следующий скрипт Python:

import time
import datetime
import fastfilepackage

testfile = './test.txt'
timenow = time.time()
iterable = fastfilepackage.FastFile( testfile )

fastfile_time = time.time() - timenow
timedifference = datetime.timedelta( seconds=fastfile_time )
print( 'FastFile timedifference', timedifference, flush=True )

Пример:

user@user-pc$ /usr/bin/pip3.6 install .
Processing /fastfilepackage
Building wheels for collected packages: fastfilepackage
  Building wheel for fastfilepackage (setup.py) ... done
  Stored in directory: /pip-ephem-wheel-cache-j313cpzc/wheels/e5/5f/bc/52c820
Successfully built fastfilepackage
Installing collected packages: fastfilepackage
  Found existing installation: fastfilepackage 0.0.0
    Uninstalling fastfilepackage-0.0.0:
      Successfully uninstalled fastfilepackage-0.0.0
Successfully installed fastfilepackage-0.0.0

user@user-pc$ /usr/bin/python3.6 fastfileperformance.py
linecount 820800
FastFile timedifference 0:00:03.204614

Использование std :: getline

Это займет около 4.7 секунд для анализа 319 МБ.

Если вы удалите алгоритм удаления UTF-8, заимствованный из самого быстрого теста с использованием stdlib.h getline(), потребуется 1.7 секунд до запуска.

#include <stdlib.h>
#include <iostream>
#include <locale>
#include <fstream>
#include <iomanip>

int main(int argc, char const *argv[])
{
    unsigned int fixedchar;
    int linecount = -1;

    char* source;
    char* lineend;
    char* destination;

    if( ( source = setlocale( LC_ALL, "en_US.ascii" ) ) == NULL ) {
        perror( "setlocale" );
        return -1;
    }
    else {
        std::cerr << "locale='" << source << "'" << std::endl;
    }

    std::ifstream fileifstream{ "./test.txt" };
    if( fileifstream.fail() ) {
        std::cerr << "ERROR: FastFile failed to open the file!" << std::endl;
        return -1;
    }
    size_t linebuffersize = 131072;
    char* readline = (char*) malloc( linebuffersize );

    if( readline == NULL ) {
        perror( "malloc readline" );
        return -1;
    }

    while( true )
    {
        if( !fileifstream.eof() )
        {
            linecount += 1;
            fileifstream.getline( readline, linebuffersize );
            lineend = readline + fileifstream.gcount();
            destination = readline;

            for( source = readline; source != lineend; ++source )
            {
                fixedchar = static_cast<unsigned int>( *source );
                // std::cerr << "fixedchar=" << std::setw(10)
                //         << fixedchar << " -> '" << *source << "'" << std::endl;

                if( 31 < fixedchar && fixedchar < 128 ) {
                    *destination = *source;
                    ++destination;
                }
            }
            // Trim out the new line character
            if( *source == '\n' ) {
                *--destination = '\0';
            }
            else {
                *destination = '\0';
            }

            // std::cerr << "readline='" << readline << "'" << std::endl;
        }
        else {
            break;
        }
    }
    std::cerr << "linecount='" << linecount << "'" << std::endl;

    if( fileifstream.is_open() ) {
        fileifstream.close();
    }

    free( readline );
    return 0;
}

Resume

  1. 2.6 секунд обрезки UTF-8 с использованием двух буферов с индексированием
  2. 3.1 секунд, обрезка UTF-8 с использованием двух буферов с memcpy
  3. 4.6 секунд, удаление некорректного UTF-8 с помощью iconv
  4. 24.2 секунд, удаление недействительного UTF-8 с помощью mbtowc
  5. 2.4 секунд обрезки UTF-8 с использованием одного буфера с прямым назначением указателя

Bonus

  1. 2.3 секунд, удаляя недействительный UTF-8 без преобразования их в кэшированный UTF-8 char*
  2. 3.2 секунд, удаляя недействительный UTF-8, преобразуя их в кешированный UTF-8 char*
  3. 3.2 секундная обрезка UTF-8 и кэширование как ASCII char*
  4. 4.7 секундная обрезка UTF-8 с std::getline() с использованием одного буфера с прямым назначением указателя

Используемый файл ./text.txt имел 820.800 строк, где каждая строка была равна:

id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char&id-é-char\r\n

И все версии были скомпилированы с

  1. g++ (GCC) 7.4.0
  2. iconv (GNU libiconv 1.14)
  3. g++ -o main test.cpp -O3 -liconv && time ./main
...