Reader.read: примеры (JAVA)
Reader.read: intОбщее описание метода Reader.read
Класс java.io.Reader представляет абстрактный поток символов. Метод read считывает символы из этого потока. В API Java присутствуют несколько перегруженных вариантов:
- int read() - читает один символ и возвращает его в виде целого (значение Unicode), либо
-1, если достигнут конец потока. - int read(char[] cbuf) - пытается заполнить массив символов, возвращает количество фактически прочитанных символов или
-1при EOF. - int read(char[] cbuf, int off, int len) - читает до
lenсимволов с записью в массив, начиная с индексаoff. Возвращает число прочитанных символов или-1при EOF. БросаетIndexOutOfBoundsException, если параметры некорректны, иNullPointerException, если массив нулевой. - int read(java.nio.CharBuffer target) - пытается поместить символы в заданный CharBuffer, возвращает число записанных символов или
-1при EOF (наличие метода зависит от реализации).
Общие свойства поведения:
- Методы блокирующие: они ожидают данных от источника (файла, сокета и т. п.) до тех пор, пока не будут доступны символы или не наступит EOF.
- Возвращаемое значение указывает число прочитанных символов. При значении
-1дальнейшее чтение сигнализирует о конце потока. - Абстрактный Reader может быть реализован разными классами:
FileReader,InputStreamReader,StringReader,BufferedReaderи т. д. У некоторых реализаций дополнительные оптимизации (буферизация, кодировка). - При работе с байтовыми источниками рекомендуется использовать
InputStreamReaderс явной кодировкой, чтобы избежать проблем с неправильной интерпретацией байтов как символов. - Методы бросают
IOExceptionпри ошибках ввода-вывода, а также специализированные исключения для некорректных аргументов.
Типичные аргументы и значения:
cbuf- массив символов для записи; не должен бытьnull.off- смещение в массиве, с которого начнётся запись; должно лежать в пределах от 0 доcbuf.length.len- максимальное число символов для чтения; должно быть неотрицательным и таковым, чтобыoff + len <= cbuf.length.- Возвращаемое
int- число прочитанных символов (>= 0) или-1при EOF; при чтении одного символа - значение символа в диапазоне 0..65535 представляющее char, либо-1.
В реальных сценариях часто используется комбинация BufferedReader для повышения производительности и InputStreamReader для задания кодировки.
Короткие примеры использования
Чтение одного символа из файла с помощью FileReader:
import java.io.FileReader;
import java.io.IOException;
public class Example1 {
public static void main(String[] args) throws IOException {
try (FileReader r = new FileReader("example.txt")) {
int ch = r.read();
System.out.println(ch);
}
}
}
Если первый символ файла 'A' вывод: 65 Если файл пуст: -1
Чтение в буфер символов:
import java.io.StringReader;
import java.io.IOException;
public class Example2 {
public static void main(String[] args) throws IOException {
try (StringReader r = new StringReader("Hello")) {
char[] buf = new char[3];
int n = r.read(buf);
System.out.println(n);
System.out.println(java.util.Arrays.toString(buf));
}
}
}
Вывод: 3 ['H', 'e', 'l']
Чтение с указанием смещения и длины:
import java.io.StringReader;
import java.io.IOException;
public class Example3 {
public static void main(String[] args) throws IOException {
try (StringReader r = new StringReader("abcdef")) {
char[] buf = new char[10];
int n = r.read(buf, 2, 3); // запишет 'a','b','c' начиная с позиции 2
System.out.println(n);
System.out.println(java.util.Arrays.toString(buf));
}
}
}
Вывод: 3 [\u0000, \u0000, 'a', 'b', 'c', \u0000, \u0000, \u0000, \u0000, \u0000]
Чтение через InputStreamReader с указанием кодировки:
import java.io.InputStreamReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class Example4 {
public static void main(String[] args) throws IOException {
byte[] bytes = "Привет".getBytes(java.nio.charset.StandardCharsets.UTF_8);
try (InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(bytes), "UTF-8")) {
char[] buf = new char[10];
int n = r.read(buf);
System.out.println(n);
System.out.println(new String(buf, 0, n));
}
}
}
Вывод: 6 Привет
Похожая функциональность в Java
- InputStream.read - работает с байтами, не с символами; использовать при необходимости низкоуровневой работы с бинарными данными или при явной конвертации в нужную кодировку через
InputStreamReader. - BufferedReader.readLine - возвращает строку до конца строки; удобен для построчной обработки, но возвращает
nullпри EOF вместо-1. - Files.newBufferedReader и Files.readAllLines - утилитарные методы из java.nio.file для удобного чтения файлов с указанием кодировки и буферизацией.
- Scanner - облегчает разбор токенов и чисел, но медленнее на больших объёмах по сравнению с Reader + буфер.
Выбор зависит от задачи: для посимвольной обработки подходит Reader, для построчной - BufferedReader.readLine, для побайтной передачи - InputStream.
Аналоги в других языках и отличия
Короткие сравнения с примерами.
Python
# read чтение всей строки
with open('example.txt', 'r', encoding='utf-8') as f:
s = f.read(5)
print(s)
Возвращает строку длиной до 5 символов или пустую строку при EOF
Отличие: в Python read возвращает строку, а EOF сигнализируется пустой строкой, а не -1.
JavaScript (Node.js)
const fs = require('fs');
const buf = Buffer.alloc(5);
const fd = fs.openSync('example.txt', 'r');
const n = fs.readSync(fd, buf, 0, 5, null);
console.log(n);
console.log(buf.toString('utf8', 0, n));
fs.closeSync(fd);
n - число байт; содержимое как строка с учётом выбранной кодировки
Отличие: Node.js оперирует байтами (Buffer), преобразование в текст - отдельный шаг.
PHP
$f = fopen('example.txt', 'r');
$s = fread($f, 5);
echo $s;
fclose($f);
fread возвращает строку длиной до указанного числа байт/символов
C#
using System.IO;
var sr = new StreamReader("example.txt");
char[] buf = new char[5];
int n = sr.Read(buf, 0, 5);
Console.WriteLine(n);
Console.WriteLine(new string(buf, 0, n));
Read возвращает количество символов или 0 при EOF
Отличие: в C# EOF обозначается возвращением 0 для перегруженной версии чтения в буфер и -1 для Read() читающего один символ.
Go
package main
import (
"fmt"
"os"
)
func main() {
f, _ := os.Open("example.txt")
buf := make([]byte, 5)
n, _ := f.Read(buf)
fmt.Println(n)
fmt.Println(string(buf[:n]))
}
n - число байт; EOF обозначается ошибкой io.EOF
Отличие: Go использует интерфейс io.Reader, возвращающий (n, err) где EOF представлен err == io.EOF.
Kotlin
val r = java.io.FileReader("example.txt")
val buf = CharArray(5)
val n = r.read(buf)
println(n)
println(String(buf, 0, if (n>0) n else 0))
r.close()
Поведение почти идентично Java, отличие - синтаксис и расширения Kotlin для удобства
В целом в разных языках есть похожие примитивы чтения. Главное отличие - способ представления EOF (число, специальная ошибка или пустая строка) и байтовая/строковая модель ввода.
Типичные ошибки при использовании read
- Необработка значения
-1при чтении. Результат: неверная обработка данных или бессмысленные преобразования. - Передача некорректных параметров в
read(char[], off, len)- возникаетIndexOutOfBoundsExceptionилиNullPointerException. - Игнорирование кодировки при чтении байтов через
InputStreamReader, что приводит к искажённым символам. - Чтение в буфер и использование неполных данных без учета возвращённого числа символов (используется весь массив вместо фактического количества).
- Не закрытие ресурса, что ведёт к утечкам дескрипторов файлов.
Примеры ошибок.
Ошибка: не проверяется -1
import java.io.StringReader;
public class Err1 {
public static void main(String[] args) throws Exception {
try (StringReader r = new StringReader("")) {
int ch = r.read();
char c = (char) ch; // ch == -1 -> преобразование в символ
System.out.println(c);
}
}
}
Вывод: '\uffff' или непредсказуемый символ, потому что -1 приведён к char. Нужна проверка ch == -1.
Ошибка: неверные off/len
import java.io.StringReader;
public class Err2 {
public static void main(String[] args) throws Exception {
try (StringReader r = new StringReader("abc")) {
char[] buf = new char[2];
r.read(buf, 1, 2); // off + len > buf.length
}
}
}
Бросает IndexOutOfBoundsException
Ошибка: чтение без учета возвращаемого значения
import java.io.StringReader;
import java.util.Arrays;
public class Err3 {
public static void main(String[] args) throws Exception {
try (StringReader r = new StringReader("xy")) {
char[] buf = new char[5];
r.read(buf);
System.out.println(Arrays.toString(buf));
}
}
}
Вывод: ['x','y','\u0000','\u0000','\u0000'] - нужно использовать возвращаемое значение для определения реальной длины.
Изменения в последних версиях Java
API класса Reader как абстракции остался стабильным, но в современных релизах появились утилитные возможности и дополнительные методы в стандартной библиотеке, облегчающие работу с потоками текста:
- Добавление методов типа transferTo(Writer) в стандартных классах ввода-вывода в более новых версиях Java упрощает копирование содержимого Reader в Writer без явного цикла чтения.
- В java.nio.file появились удобные методы для чтения всего файла в строку или список строк (Files.readString, Files.readAllLines), что снижает потребность в ручном использовании Reader для простых сценариев.
- Современные реализации библиотек и JDK уделяют внимание производительности и локализации, но сигнатуры основных методов чтения остались прежними.
При миграции на более новые версии стоит проверить наличие новых утилит и оптимизаций, а также учитывать уведомления об устаревших API в релиз-нотах.
Расширенные примеры и нетипичные сценарии
Копирование Reader в Writer с помощью цикла и с transferTo (если доступно):
// 1. Ручной цикл
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Adv1 {
public static void main(String[] args) throws IOException {
try (FileReader r = new FileReader("in.txt");
FileWriter w = new FileWriter("out.txt")) {
char[] buf = new char[8192];
int n;
while ((n = r.read(buf)) != -1) {
w.write(buf, 0, n);
}
}
}
}
Результат: содержимое in.txt скопировано в out.txt. Подходит для больших файлов.
// 2. Использование transferTo (в Java 9+ при наличии метода)
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Adv2 {
public static void main(String[] args) throws IOException {
try (FileReader r = new FileReader("in.txt");
FileWriter w = new FileWriter("out.txt")) {
r.transferTo(w);
}
}
}
Результат: тот же, но реализовано внутри платформы, возможно более оптимально.
Чтение данных из сетевого сокета с конвертацией байтов в символы (примитивный пример):
import java.net.Socket;
import java.io.InputStreamReader;
import java.io.BufferedReader;
public class Adv3 {
public static void main(String[] args) throws Exception {
try (Socket s = new Socket("example.com", 80);
InputStreamReader isr = new InputStreamReader(s.getInputStream(), "UTF-8");
BufferedReader br = new BufferedReader(isr)) {
br.readLine(); // чтение первой строки ответа
}
}
}
Результат: первая строка ответа сервера или null при закрытии соединения
Использование PushbackReader для «отката» одного символа при анализе:
import java.io.PushbackReader;
import java.io.StringReader;
public class Adv4 {
public static void main(String[] args) throws Exception {
try (PushbackReader pr = new PushbackReader(new StringReader("12x"))) {
int a = pr.read() - '0';
int b = pr.read() - '0';
int ch = pr.read();
if (ch == 'x') {
pr.unread(ch);
}
int next = pr.read();
System.out.println(a + ", " + b + ", " + (char) next);
}
}
}
Вывод: 1, 2, x - демонстрация возврата символа в поток и последующего чтения
Чтение с использованием java.nio.CharBuffer:
import java.io.StringReader;
import java.nio.CharBuffer;
public class Adv5 {
public static void main(String[] args) throws Exception {
try (StringReader r = new StringReader("HelloWorld")) {
CharBuffer cb = CharBuffer.allocate(4);
int n = r.read(cb);
cb.flip();
System.out.println(n);
System.out.println(cb.toString());
}
}
}
Вывод: 4 Hell
Построчная нумерация с LineNumberReader:
import java.io.LineNumberReader;
import java.io.StringReader;
public class Adv6 {
public static void main(String[] args) throws Exception {
try (LineNumberReader lr = new LineNumberReader(new StringReader("one\ntwo\nthree"))) {
String s;
while ((s = lr.readLine()) != null) {
System.out.println(lr.getLineNumber() + ": " + s);
}
}
}
}
Вывод: 1: one 2: two 3: three
Примечание: для нетипичных сценариев (парсинг, анализ символов, потоковая конвертация) Reader в сочетании с специализированными оболочками и буферами предоставляет гибкие возможности без существенных затрат в простоте кода.