Reader.read: примеры (JAVA)

Метод read класса Reader: разбор и практические примеры
Раздел: Потоки ввода-вывода (Streams) символьные
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 (если доступно):

Пример java
// 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. Подходит для больших файлов.
Пример java
// 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);
        }
    }
}
Результат: тот же, но реализовано внутри платформы, возможно более оптимально.

Чтение данных из сетевого сокета с конвертацией байтов в символы (примитивный пример):

Пример java
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 для «отката» одного символа при анализе:

Пример java
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:

Пример java
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:

Пример java
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 в сочетании с специализированными оболочками и буферами предоставляет гибкие возможности без существенных затрат в простоте кода.

джава Reader.read function comments

En
Reader.read Reads a single character