Страницы

Поиск по вопросам

Показаны сообщения с ярлыком поток-ввода. Показать все сообщения
Показаны сообщения с ярлыком поток-ввода. Показать все сообщения

понедельник, 30 марта 2020 г.

Проблема с кодировками

#java #кодировка #поток_ввода #filewriter


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

Программа в общем-то ничего сложного не делает, обычная работа с файлами и текстом,
если упростить, но проблема в том, что при чтении файла (в файле точно не UTF-8, word
определил кодировку, как Юникод) поэтому геморрой с обработкой информации, с этим я
в общем-то справился, вот только на ноутах она выдает дичь, где используется кириллица. 

К слову к примеру если в строку я записываю кириллицу сам, то всё ок. Проблема только
с файлом. Вот 1 из методов.

void networkAdapterGet() {
    StringBuilder result = new StringBuilder();
    try(BufferedReader reader = new BufferedReader(new FileReader(NETWORK_ADAPTER_INFO_PATH));
        BufferedWriter writer = new BufferedWriter(new FileWriter(TEMP_RESULT_FILE_PATH,
true))) {
        line = reader.readLine();
        while (line != null){
            if (line.length() > 1){
                line = trimSpaces(line);
                line = networkAdapterProcessing(line);
                if (!line.equals("")){
                    result.append(line);
                    result.append(lineSeparator);
                }
            }
            line = reader.readLine();
        }
        writer.write("Сетевые устройства:");
        writer.write(lineSeparator);
        writer.write(result.toString());
        writer.write(lineSeparator);
        writer.write(lineSeparator);
    } catch (IOException e) {
        e.printStackTrace();
    }
}


Скорее всего зря повелся наFileReader/Writer и думаю основная проблема, как раз таки
в Reader'е. Пошаманил с кодировками на открытии и получил фигу, упало как раз таки
из-за несовпадения кодировок, можно конечно перебрать вариантов там пара всего, но
хотелось бы узнать можно ли как-то это дело исправить. Это пока единственный мешающий/раздражающий
баг на данном этапе разработки утилитки.
    


Ответы

Ответ 1



Нужно писать в той кодировке, которая читается клиентом. Чтобы указать кодировку надо заменить FileWriter на new OutputStreamWriter(new FileOutputStream(TEMP_RESULT_FILE_PATH), StandardCharsets.UTF_8); если кодировка UTF_8 не подходит, то можно попробовать KOI-8 или CP1251, или другая кодировка, которая поддерживает кирилицу, например CP866.

Ответ 2



Очень помог ответ, Roman C. Получилась немного громоздкая конструкция со всеми этими обертками, но вроде бы работает осталось поправить другой метод, а именно networkAdapterProcessing(), чтобы корректно отображался результат, да и остальные методы можно оставить без изменений потому, что проблема только в сетевых устройствах, но вот решение самой проблемы с кодировками получилась весьма интересной хотя бы для меня, я еще пока только учусь. try(BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream(NETWORK_ADAPTER_INFO_PATH), "Unicode")); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream(TEMP_RESULT_FILE_PATH, true), "Unicode"))){ Так вот для того, чтобы определить кодировку достаточно было открыть файл клятым блокнотом и жамкнуть "Сохранить как", чтобы убедиться в том, что это Юникод. Причем пробовал вначале пересохранить в UTF-8, но там была полная борода. Сейчас конечно из-за того, что открывает с помощью другого инструмента сменилась и сама структура получаемой строки немного, но это уже мелочи по сравнению с раздражающей меня ошибкой с кодировкой. Как это у меня обычно и бывает проблема была в сути своей совсем не сложной, но мозги она заклинила неслабо. В будущем думаю исправлю, чтобы было все одинаково и не выделялось, но пока надо сделать хотя бы это. Спасибо вам за помощь.

вторник, 24 декабря 2019 г.

Зачем BufferedInputStream, если InputStream предоставляет read с буфером

#java #поток_ввода


Зачем понадобилось включать в систему ввода-вывода Java обертку BufferedInputStream,
если все реализации интерфейса InputStream по умолчанию содержат метод read(byte b[]),
в котором для чтения из потока используется буфер? 

Для примера, написал программу, которая копирует видеофайл ~100 мб из одного файла
в другой.


Реализация с использованием BufferedInputStream:

public static void main (String[] args) throws IOException{

BufferedInputStream fis=new BufferedInputStream(new FileInputStream("C:/tests/1.mp4"));
BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream("C:/tests/2.mp4"));
int b=fis.read();
while (b!=-1){

    fos.write(b);
    b=fis.read();

}
fos.flush();        


}
Реализация с использованием метода read(byte b[])

public static void main (String[] args) throws IOException{

FileInputStream fis=new FileInputStream("C:/tests/1.mp4");
FileOutputStream fos=new FileOutputStream("C:/tests/2.mp4");

byte[] buff=new byte[4096];
int bytes=fis.read(buff);
while(bytes!=-1){

    fos.write(buff, 0, bytes);
    bytes=fis.read(buff);

}


}


Реализация с использованием класса BufferedInputStream работает примерно в 10 раз
медленнее
    


Ответы

Ответ 1



Попробуйте сравнить скорость обработки с буферизацией и считыванием массива: BufferedInputStream fis = new BufferedInputStream(new FileInputStream("C:/tests/1.mp4")); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("C:/tests/2.mp4")); byte[] buff = new byte[4096]; int bytes = fis.read(buff); while (bytes != -1) { fos.write(buff, 0, bytes); bytes = fis.read(buff); } fos.flush(); Полагаю, что скорость обработки будет сравнима со скоростью без буферизации, а может, даже быстрее, за счет другого размера буфера. Почему первый пример работает медленно Считывание/запись массива байтов из локального файла — очень быстрая операция. В лучше случае BufferedInputStream (BufferedOutputStream) не повлияет на производительность существенным образом, в худшем — производительность упадет из-за различных накладных расходов. В вашем случае байты (~ 10^8) после считывания из буфера обрабатываются по одному, что сильно увеличивает накладные расходы: для каждого байта вызываются методы read и write — вызовы методов не бесплатны; хуже того, оба метода синхронизированные (synchronized) и при каждом вызове виртуальная машина выполняет переключение контекста/блокировку; в самих методах есть разнообразные проверки (открыт ли входной/выходной поток, закончился ли буфер и т.п.), которые также отнимают время. Я полагаю, что основную нагрузку создает синхронизация. Можете для эксперимента добавить синхронизированные методы в вариант с FileInputStream while (bytes != -1) { for (int i = 0; i < bytes; i++) { write(read(buff[i])); } fos.write(buff, 0, bytes); bytes = fis.read(buff); } } static int count = 0; public static synchronized void write(int b) { //неважно count += b; } public static synchronized int read(int b) { return count + b; } Зачем BufferedInputStream Буферизированные потоки нужны чтобы не писать буферизацию самому. Также они позволяют отделить логику программы от настроек буферизации. BufferedInputStream особенно удобен если: Используется алгоритм, который обрабатывает байты последовательно. В этом случае можно упростить код, доверив буферизацию встроенному классу. Сам поток используется в качестве аргумента для класса-чтеца. Например, BufferedInputStream можно передать в качестве аргумента Scanner: Scanner scanner = new Scanner(new BufferedInputStream(new FileInputStream("input"))); После этого через Scanner можно будет работать с данными построчно, а BufferedInputStream сэкономит на обращениях к файловой системе. Используются специфичные методы. Методы InputStream.mark и InputStream.reset, как правило, недоступны в небуферизированных потоках. Соответственно, если потребуется откатывать состояние потока, то использовать FileInputStream не получится. На примере с копированием файла преимущества увидеть сложно. С другой стороны, для копирования файла целесообразнее использовать встроенные методы, а не потоки.

понедельник, 23 декабря 2019 г.

Как хранят информацию потоки ввода-вывода?

#java #кодировка #поток_ввода


Объясните, пожалуйста, почему при вводе в консоли, скажем, буквы ‘t’ и последующих
выводах System.in.read() и new BufferedReader(new InputStreamReader(System.in)).read(),
получается один и тот же результат при приведении к типу int, а именно: 116. Ведь стандартный
read() байтового потока должен читать по одному байту, и выдать ноль (как первый байт
двухбайтной записи ‘t’), а символьный BufferedReader всё выдаёт правильно.
В чём дело? 
    


Ответы

Ответ 1



Консоль передает готовые байты. System.in — это входной поток процесса Java, который управляется ОС. Консоль передает в этот поток уже преобразованные байты. Что происходит когда Вы вводите данные в консоли: Консоль преобразовывает введенные символы в байты в соотвествии с кодировкой заданной для консоли. Установка кодировки зависит от отдельно взятой консоли/ОС. Скорее всего в Вашей консоли установлена кодировка, которая преобразовывает t в один байт. System.in.read честно считывает полученный байт. InputStreamReader преобразовывает полученные байты в символы в соответствии с кодировкой по-умолчанию, т.к. Вы выбрали конструктор с незаданой кодировкой. Кодировка по-умолчанию задается параметрами JVM. Можете проверить кодировку Вашего reader-а следующим кодом: InputStreamReader reader = new InputStreamReader(System.in); System.out.println("Encoding: "+reader.getEncoding()); Кодировку можно задать явно, воспользовавшись одним из конструкторов. При считывании reader считывает байты из входного потока и преобразовывает их в соответствии с кодировкой в числовое значение соответствующего символа (char). Согласно спецификации Java (§3.1 Unicode), символы хранятся в кодировке UTF-16. Допустим мы используем однобайтную кодировку, вводим символ «ы» и обрабатываем его: char value = (char) reader.read(); Reader прочитает из входного потока один байт, поймет что это за символ найдет этот символ в таблиц UTF-16 и вернет его числовое значение. В резултате этот код будет эквивалентен: char value = 'ы'; независимо от кодировки. Двухбайтные кодировки Чтобы увидеть двухбайтное кодирование стандартного ввода нужно установить соответствующую кодировку. В целях экономии места символы английского алфавита в большинстве кодировок кодируются одним байтом. ... должен читать по одному байту, и выдать ноль (как первый бит двухбитной записи ‘t’) Так символы преобразовываются в двухбайтных кодировках с порядком от старшего к младшему (например, UTF16-BE). Соответственно, чтобы увидеть такое преобразование через System.in.read Вам нужно подавать данные на вход в такой кодировке. Это можно сделать рядом способов, например: Прочитать документацию консоли, узнать как задать для нее кодировку и поддерживается ли кодировка UTF16-BE. Если да, то задать кодировку. Если нет, то этот способ не подойдет. Не вводить данные с консоли, а передать на вход файл с текстом. Файл сохранить в кодировке UTF16-BE, после чего выполнить команду вроде: java Main < encoded.txt Нужно принять во внимание, что при сохранении первым символом в файле будет BOM и пропустить его.

Ответ 2



При явном приведении строки к массиву байтов: import java.util.Arrays; public class App { public static void main(String[] args) { String str = "t"; byte[] b = str.getBytes(); System.out.println(b.length); //длина массива System.out.println(Arrays.toString(b)); //значение } } увидим в консоли 1 [116] Длина массива из строки "t" это 1, а искомый байт 116. Дейстивительно, как выше ответил @default locale, read() честно отдает этот байт. P.S. В случае с двубайтным символом, например "ы", метод getBytes() дает массив из двух байт [-47, -117], а если считывать этот символ из потока методом System.in.read(), мы получим тоже два байта 209 и 139. Почему так, потому что (цитирую из "Программирование на Java", Патрик Нимейер, Дэниэл Леук, 4-е издание 2014 г., стр.568 ) на платформе Java для выделения конца потока данный метод использует специальное значение, следуя стандарту из языка С. Байты возвращаются в виде беззнаковых целых чисел в диапазоне от 0 до 255; Закономерность смещения значения байтов простая - явное приведение int к byte. (byte) 209 и (byte) 139 соответственно -47 и -117. В случае, если мы читаем методом read() из потока типа BufferedReader, то возвращаем кодировку символа, как сказано в документации: The character read, as an integer in the range 0 to 65535 (0x00-0xffff)

пятница, 7 июня 2019 г.

Проблема с кодировками

Имеется утилита, которая запускает батники, которые в свою очередь собирают инфу с помощью утилиты wmic и потом с них форматирует отчет на рабочий стол.
Программа в общем-то ничего сложного не делает, обычная работа с файлами и текстом, если упростить, но проблема в том, что при чтении файла (в файле точно не UTF-8, word определил кодировку, как Юникод) поэтому геморрой с обработкой информации, с этим я в общем-то справился, вот только на ноутах она выдает дичь, где используется кириллица.
К слову к примеру если в строку я записываю кириллицу сам, то всё ок. Проблема только с файлом. Вот 1 из методов.
void networkAdapterGet() { StringBuilder result = new StringBuilder(); try(BufferedReader reader = new BufferedReader(new FileReader(NETWORK_ADAPTER_INFO_PATH)); BufferedWriter writer = new BufferedWriter(new FileWriter(TEMP_RESULT_FILE_PATH, true))) { line = reader.readLine(); while (line != null){ if (line.length() > 1){ line = trimSpaces(line); line = networkAdapterProcessing(line); if (!line.equals("")){ result.append(line); result.append(lineSeparator); } } line = reader.readLine(); } writer.write("Сетевые устройства:"); writer.write(lineSeparator); writer.write(result.toString()); writer.write(lineSeparator); writer.write(lineSeparator); } catch (IOException e) { e.printStackTrace(); } }
Скорее всего зря повелся наFileReader/Writer и думаю основная проблема, как раз таки в Reader'е. Пошаманил с кодировками на открытии и получил фигу, упало как раз таки из-за несовпадения кодировок, можно конечно перебрать вариантов там пара всего, но хотелось бы узнать можно ли как-то это дело исправить. Это пока единственный мешающий/раздражающий баг на данном этапе разработки утилитки.


Ответ

Нужно писать в той кодировке, которая читается клиентом. Чтобы указать кодировку надо заменить FileWriter на
new OutputStreamWriter(new FileOutputStream(TEMP_RESULT_FILE_PATH), StandardCharsets.UTF_8);
если кодировка UTF_8 не подходит, то можно попробовать KOI-8 или CP1251, или другая кодировка, которая поддерживает кирилицу, например CP866

понедельник, 19 ноября 2018 г.

Как хранят информацию потоки ввода-вывода?

Объясните, пожалуйста, почему при вводе в консоли, скажем, буквы ‘t’ и последующих выводах System.in.read() и new BufferedReader(new InputStreamReader(System.in)).read(), получается один и тот же результат при приведении к типу int, а именно: 116. Ведь стандартный read() байтового потока должен читать по одному байту, и выдать ноль (как первый байт двухбайтной записи ‘t’), а символьный BufferedReader всё выдаёт правильно. В чём дело?


Ответ

Консоль передает готовые байты.
System.in — это входной поток процесса Java, который управляется ОС. Консоль передает в этот поток уже преобразованные байты.
Что происходит когда Вы вводите данные в консоли:
Консоль преобразовывает введенные символы в байты в соотвествии с кодировкой заданной для консоли. Установка кодировки зависит от отдельно взятой консоли/ОС. Скорее всего в Вашей консоли установлена кодировка, которая преобразовывает t в один байт. System.in.read честно считывает полученный байт. InputStreamReader преобразовывает полученные байты в символы в соответствии с кодировкой по-умолчанию, т.к. Вы выбрали конструктор с незаданой кодировкой. Кодировка по-умолчанию задается параметрами JVM. Можете проверить кодировку Вашего reader-а следующим кодом:
InputStreamReader reader = new InputStreamReader(System.in); System.out.println("Encoding: "+reader.getEncoding());
Кодировку можно задать явно, воспользовавшись одним из конструкторов. При считывании reader считывает байты из входного потока и преобразовывает их в соответствии с кодировкой в числовое значение соответствующего символа (char). Согласно спецификации Java (§3.1 Unicode), символы хранятся в кодировке UTF-16. Допустим мы используем однобайтную кодировку, вводим символ «ы» и обрабатываем его:
char value = (char) reader.read();
Reader прочитает из входного потока один байт, поймет что это за символ найдет этот символ в таблиц UTF-16 и вернет его числовое значение. В резултате этот код будет эквивалентен:
char value = 'ы';
независимо от кодировки.
Двухбайтные кодировки
Чтобы увидеть двухбайтное кодирование стандартного ввода нужно установить соответствующую кодировку. В целях экономии места символы английского алфавита в большинстве кодировок кодируются одним байтом.
... должен читать по одному байту, и выдать ноль (как первый бит двухбитной записи ‘t’)
Так символы преобразовываются в двухбайтных кодировках с порядком от старшего к младшему (например, UTF16-BE). Соответственно, чтобы увидеть такое преобразование через System.in.read Вам нужно подавать данные на вход в такой кодировке.
Это можно сделать рядом способов, например:
Прочитать документацию консоли, узнать как задать для нее кодировку и поддерживается ли кодировка UTF16-BE. Если да, то задать кодировку. Если нет, то этот способ не подойдет. Не вводить данные с консоли, а передать на вход файл с текстом. Файл сохранить в кодировке UTF16-BE, после чего выполнить команду вроде:
java Main < encoded.txt
Нужно принять во внимание, что при сохранении первым символом в файле будет BOM и пропустить его.