LINUX.ORG.RU

Чтение из UTF-8 файла в массив на языке Rust

 ,


0

5

Господа, имеется такой код:

use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

fn main() {
    let args : Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        println!("Wrong! Use: {} filename", args[0]);
        return;
    }
    
    let file_name = args[1].to_string();
    let input_file = match File::open(&file_name) {
        Ok(file) => file,
        Err(_) => {
            println!("File {} not found.", file_name);
            return;
        }
    };

    let mut reader = BufReader::new(input_file);

    let mut buf = [0u8; 512];
    loop {
        let length = reader.read(&mut buf).ok().unwrap();
        if length == 0 {
            break;
        }
        let _s = match std::str::from_utf8(&buf[0..length]) {
            Ok(string) => string,
            Err(e) => panic!("{}", e),
        };  
    }

}

Проблема в том, что в файлах вперемешку идут символы разной ширины (ASCII и кириллица), поэтому иногда в буфер знак целиком не влезает и я получаю что-то вроде:

thread '<main>' panicked at 'invalid utf-8: invalid byte near index 511'

Собственно вопрос: как эффективно разрулить эту ситуацию? Дочитывать по байту, пока декодирование не пройдёт успешно? Или использовать что-то ещё?

Решение такого типа:

let mut s = std::string::String::new();
    loop {
        let length = reader.read_to_string(&mut s).ok().unwrap();
        if length == 0 {
            break;
        }
    }
не подходит, файл может быть очень большой, нужно экономить память. То же самое касается и read_line.

★★★

файл может быть очень большой, нужно экономить память. То же самое касается и read_line.

И файл может быть больше памяти, и отдельная строка?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

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

Weres ★★★
() автор топика
Ответ на: комментарий от MyTrooName

Парсить буду, посимвольно. unchecked превратит пограничные символы в тыкву и я об этом даже не узнаю. Сейчас посмотрю на пакет, предложенный выше, там есть интересные EncoderTrap.

Ещё была бы возможность получить индекс массива, на котором функция сбойнула без парсинга строки ошибки, было бы хорошо. Хотя, возможно я не понимаю обработку ошибок в расте.

Weres ★★★
() автор топика

Ну в принципе раз уж ты знаешь, что в файле должно быть utf8, можно попробовать найти первый байт многобайтового символа (он начинается с бит 0b11xxxxxx, либо с 0b0xxxxxxx, но в этом случае это должен быть единственный байт символа и ничего делать больше не надо), по нему определить длину символа в байтах (= количеству единиц в старших разрядах), и дочитать недостающие.

Kiborg ★★★
()
Последнее исправление: Kiborg (всего исправлений: 1)

А разве read_char вернет не utf8 символ?

П.С.

    loop {
        let length = reader.read(&mut buf).ok().unwrap();
        if length == 0 {
            break;
        }

в С++ это пишется лучше:

while( int length = reader.read( buf ) )
anonymous
()
Ответ на: комментарий от anonymous

Я хочу развидеть этот нечитаемый понос.

Лжешь. Тебе нравится, иначе ты бы сюда не зашел.

tailgunner ★★★★★
()

вообще есть

fn chars(self) -> Chars<Self> 
, но он Unstable.

maloi ★★★★★
()
Ответ на: комментарий от Kiborg

Да вот зря я так на utf8 сосредоточился. Скорее всего кодировок будет несколько, так что не хотелось бы привязываться к конкретной.

Weres ★★★
() автор топика
Ответ на: комментарий от anonymous

Эта документация к версии 0.11 не? С тех пор все сильно поменялось.

Weres ★★★
() автор топика
Ответ на: комментарий от anonymous

а теперь положи машину времени на место и возвращайся из 2014 года в 2015

maloi ★★★★★
()
Ответ на: комментарий от quantum-troll

Как и с unchecked (в треде выше), пограничные, относительно буфера, символы превратятся в тыкву.

Weres ★★★
() автор топика
Ответ на: комментарий от Weres

Скорее всего кодировок будет несколько, так что не хотелось бы привязываться к конкретной.

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

maloi ★★★★★
()
Ответ на: комментарий от Weres

Их можно потом скорректировать, дочитав недостающее. Хотя, это довольно костыльный способ.

quantum-troll ★★★★★
()
Ответ на: комментарий от Weres

так и читай до тыквы. нашел тыкву - догружай следующий блок данных и парси по новой

MyTrooName ★★★★★
()
Ответ на: комментарий от maloi

Библиотека, что выше предложили, хороша, сейчас с ней вожусь, но вот обработка ошибок так себе. Не могу получить индекс, на котором парсер упал. Получается вот что.

    let mut reader = BufReader::new(input_file);
    let mut buf = [0u8; 3];
    
    loop {
        let length = reader.read(&mut buf).ok().unwrap();
        if length == 0 {
            break;
        }

        fn test(_decoder : &mut RawDecoder, input : &[u8],
                _output : &mut StringWriter) -> bool {
            println!("{:?}", input);
            true
        };

        let _s = UTF_8.decode(&buf[0..length], DecoderTrap::Call(test)).ok().unwrap();
    }
Внутри функции test я знаю, на каком символе парсер рухнул. Всё, что мне нужно, это взять input и сохранить его, чтобы приклеить в начало следующего буфера. В итоге я на текущей итерации получу корректную строку, а всё что в неё не влезло уйдёт в следующую.

Но, передать input из функции у меня не получается. В DecoderTrap::Call нельзя передать closure, который бы работал с захваченным вектором, потому что он ждёт fn pointer, туда нельзя передать указатель на метод структуры, потому что в метод надо передавать self. Я даже попытался сделать статический вектор, но во-первых это нельзя (так как он имеет деструктор), а во-вторых хотелось бы корректной работы при многопоточности. В общем, моих знаний языка не хватает, чтобы решить эту проблему.

Weres ★★★
() автор топика
Ответ на: комментарий от entefeed

По моему коду его судить не стоит.

Weres ★★★
() автор топика
Ответ на: комментарий от Weres

Можно построить итератор на основе https://lifthrasiir.github.io/rust-encoding/encoding/types/trait.RawDecoder.html. Храним внутри себя владеющую строку и декодер, читаем блоками, возвращаем по запросу символы или одну из возможных ошибок.

quantum-troll ★★★★★
()
Ответ на: комментарий от quantum-troll

Спасибо, утром попробую. Эх, возвращали бы они при ошибке CodecError, всё бы было куда легче.

Weres ★★★
() автор топика
Ответ на: комментарий от greatchina

Вместо

use std::io::prelude::*;
стоит предпочесть
use std:::io:::prelude:::*;
Так прикольней

menangen ★★★★★
()
Ответ на: комментарий от greatchina

Этот относится сразу к обоим.

anonymous
()
Ответ на: комментарий от Weres

насчет получения fn вместо FnMut и кложы - открой Issue, я думаю поменяют.

а вообще почему тебе так нравится пользоваться низкоуровневыми интерфейсами?
во-первых можно взять RawDecoder и скармливать ему в raw_feed байты, получая то что уже декодировано в output, а остальное будет в RawDecoder буферизировано
во-вторых можно взять rust-iconv и не заниматься байтоебством (иначе называть чтение из стандартного BufReader в ещё один буфер язык не поворачивается), а просто читать из Reader-а utf-8 символы.

maloi ★★★★★
()
Ответ на: комментарий от maloi

А! Как же я натупил. Можно же использовать RawDecoder напрямую. А я обертку вокруг нагородил. Спасибо! Вот так всё работает:

let mut reader = BufReader::new(input_file);
let mut buf = [0u8; 512];
let mut dec= UTF8Encoding.raw_decoder();

loop {
    let length  = reader.read(&mut buf).ok().unwrap();
    if length == 0 {
        break;
    }

    let mut inner_str = String::new();
    dec.raw_feed(&buf[0..length], &mut inner_str);
    print!("{}", inner_str);
}

let mut inner_str = String::new();
dec.raw_finish(&mut inner_str);
print!("{}", inner_str);
Впрочем rust-iconv всё равно посмотрю.

Спасибо всем, кто отписался по делу.

Weres ★★★
() автор топика
Ответ на: комментарий от Weres

я ещё раз напомню, что стоит все-таки вместо выделения своего буфера и тасования байтов туда-сюда начать использовать fill_buf и consume.

maloi ★★★★★
()
Ответ на: комментарий от maloi

Переписал, спасибо.

let mut reader = BufReader::new(input_file);
let mut dec = UTF8Encoding.raw_decoder();
    
loop {
    let length;
    match reader.fill_buf() {
        Ok(buf) => {
            length = buf.len();
            if length == 0 {
                break;
            }
            let mut inner_str = String::new();
            dec.raw_feed(&buf, &mut inner_str);
            print!("{}", inner_str);
        }

        Err(_) => break,
    };
    reader.consume(length);
}

Weres ★★★
() автор топика
Ответ на: комментарий от Weres
let mut reader = BufReader::new(input_file);
let mut dec = UTF8Encoding.raw_decoder();
    
loop {
    let length;
    match reader.fill_buf() {
        Ok(buf) => {
            length = buf.len();
            if length == 0 {
                break;
            }
            let mut inner_str = String::new();
            dec.raw_feed(&buf, &mut inner_str);
            print!("{}", inner_str);
        }

        Err(_) => break,
    };
    reader.consume(length);
}

ОМГ, это что аналог:

QTextStream ts( f );
while( !ts.atEnd() && ts.status() != QTextStream::ReadCorruptData )
    qDebug() << ts.read( 4096 );

?!

anonymous
()
Ответ на: комментарий от anonymous

Хотя нет, судя по всему это вообще аналог:

QTextStream ts( f );
while( !ts.atEnd() )
    qDebug() << ts.read( 4096 );

Жесть.

anonymous
()
Ответ на: комментарий от anonymous

Кстати и Qt тут не нужен, все есть в С++11:

wifstream f( "data.txt" );
f.imbue( { locale(), new codecvt_utf8<wchar_t> } );
wcout << f.rdbuf();

В общем радости от rust не видно. Как минимум кода больше, причем нечитабельного.

anonymous
()
Ответ на: комментарий от anonymous

В общем радости от rust не видно

Какой кошмар, у Rust библиотеки сырые!

Как минимум кода больше, причем нечитабельного.

Читать нужно учиться.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Какой кошмар, у Rust библиотеки сырые!

Взять тот же iostream, его внесли в стандарт до того, как utf8 массово пришел на десктопы. Что нужно было, чтоб научить его utf8 в С++11? Добавить codecvt_utf8, легко, просто, удобно. Как часто ломали iostream? Ни разу. Вот поэтому С++ все еще и используется, а всякие более продвинутые D - на обочине.

Читать нужно учиться.

Писать, слова => если, так не! {} принято языке нормальном в как: то понять, (_) можно; проблем без let труднее но.

anonymous
()
Ответ на: комментарий от anonymous

Взять тот же iostream, его внесли в стандарт до того, как utf8 массово пришел на десктопы

фейспалм.жпг

Как часто ломали iostream?

Школота не помнит, но сначала в C++ были streams, просто streams, без io. Почитай первую редакцию «The C++ programming language».

Писать, слова => если, так не! {} принято языке нормальном в как: то понять, (_) можно; проблем без let труднее но.

отличная_шутка.жпг

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

фейспалм.жпг

А что не так? В 98-м практически везде были cp*, koi* и т.п. Собс-но поддержка unicode потому и задержалась в С и С++, потому как не была востребована в предыдущих стандартах.

Школота не помнит, но сначала в C++ были streams, просто streams, без io. Почитай первую редакцию «The C++ programming language».

Школота не помнит, но знает, что первый стандарт С++ имел iostreams. Если rust еще не дорос до стандарта и стабильности, то нет проблем. Не дорос, так не дорос.

отличная_шутка.жпг

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

anonymous
()
Ответ на: комментарий от tailgunner

Тогда мои соболезнования. Хотя... в любом случае - мои соболезнования.

Оставь их при себе, пригодятся, если вдруг начнешь что-то большое писать на rust. Хотя это, конечно, маловероятно.

anonymous
()
Ответ на: комментарий от maloi

это вот это ты называешь читабельным кодом?

Относительно кода на rust - да. Относительно варианта на Qt - нет.

anonymous
()
Ответ на: комментарий от Weres

Ты вызываешь reader.consume(length); даже если reader.fill_buf() возвращает Err(). Причем ещё и со случайным значением.
Все-таки раст не может избавить от всех ошибок :)

maloi ★★★★★
()
Ответ на: комментарий от anonymous

в любом случае - мои соболезнования.

Оставь их при себе

Они нужнее человеку, у которого ветка в pattern matching ж0стко ассоциируется с case: break.

если вдруг начнешь что-то большое писать на rust. Хотя это, конечно, маловероятно.

Здесь ты прав - в ближайшие годы точно не начну. Но это не от меня зависит.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Они нужнее человеку, у которого ветка в pattern matching ж0стко ассоциируется с case: break.

Достаточно неожиданное заявление. И на чем оно основано?

Здесь ты прав - в ближайшие годы точно не начну. Но это не от меня зависит.

Конечно, ведь языком на ЛОРе трепать - это не лапшу на расте писать, гораздо приятней и проще. На это можно тратить годы.

anonymous
()
Ответ на: комментарий от maloi

double lol

Ты поди тоже ничего крупного в ближайшие годы писать не будешь? Или все-таки запилишь никому не нужную библиотеку-другую?

По поводу кода на С++:

wifstream f( "data.txt" );

Есть вариант написать проще?

wcout << f.rdbuf();

Аналогично? Если ты хотя бы понимаешь, что тут делается.

f.imbue( { locale(), new codecvt_utf8<wchar_t> } );

Вот тут уже сложней, но тут делается то, что ваш раст просто напросто не умеет. И даже не предусматривает. Вот как ты, например, будешь сортировать массив строк в utf8. Сможешь показать?

anonymous
()
Ответ на: комментарий от anonymous

И на чем оно основано?

На не-шутке.

языком на ЛОРе трепать - это не лапшу на расте писать

Очень сомневаюсь, что писать на Rust сложнее, чем писать на Си. Впрочем... см выше.

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.