Класс System позволяет вам перенаправить стандартный ввод, вывод и поток ошибок. Для этого предусмотрены простые статические методы: setIn(InputStream); setOut(PrintStream); setErr(PrintStream). Перенаправление стандартного вывода особенно полезно тогда, когда ваша программа выдает слишком много сообщений сразу и вы попросту не успеваете читать их, поскольку они заменяются новыми сообщениями. Перенаправление ввода удобно для программ, работающих с командной строкой, в которых необходимо поддержать некоторую последовательность введенных пользователем данных. Вот простой пример, показывающий, как использовать эти методы:
//: io/Redirecting.java
// Перенаправление стандартного ввода/вывода.
import java.io.*;
public class Redirecting {
public static void main(String[] args)
throws IOException {
PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("Redirecting.java"));
PrintStream out = new PrintStream(
new BufferedOutputStream(
new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null)
System.out.println(s);
out.close(); // Remember this!
System.setOut(console);
}
}
Программа присоединяет стандартный ввод к файлу и перенаправляет, стандартный ввод и поток для ошибок в другие файлы. Обратите внимание на сохранение ссылки на исходный объект System.out в начале программы и его восстановление в конце. Перенаправление основано на байтовом, а не на символьном вводе/выводе, поэтому в примере используются InputStream и OutputStream, а не их символьно-ориентированные эквиваленты Reader и Writer.
Новый ввод/вывод (nio)
При создании библиотеки «нового ввода/вывода» Java, появившейся в JDK-1.4 в пакетах java.nio.*, ставилась единственная цель: скорость. Более того, «старые» пакеты ввода/вывода были переписаны с учетом достижений nio, с намерением использовать преимущества повышенного быстродействия, поэтому улучшения вы получите, даже если не будете писать явный nіо-код. Подъем производительности просматривается как в файловом вводе/выводе, который мы здесь рассматриваем, так и в сетевом вводе/выводе.
Увеличения скорости удалось достичь с помощью структур, близких к средствам самой операционной системы: каналов[30] (channels) и буферов (buffers). Канал можно сравнить с угольной шахтой, вырытой на угольном пласте (данные), а буфер — с вагонеткой, которую вы посылаете в шахту. Тележка возвращается доверху наполненная углем, который вы из нее выгружаете. Таким образом, прямого взаимодействия с каналом у вас нет, вы работаете с буфером и «посылаете» его в канал. Канал либо извлекает данные из буфера, либо помещает их в него.
Напрямую взаимодействует с каналом только буфер ByteBuffer, то есть буфер, хранящий простые байты. Если вы просмотрите документацию JDK для класса java.nio.ByteBuffer, то увидите что он достаточно прост: вы создаете его, указывая, сколько места надо выделить под данные. Класс содержит набор методов для получения и помещения данных в виде последовательности байтов или в виде примитивов. Однако возможности записать в него объект или даже простую строку нет. Буфер работает на достаточно низком уровне, поскольку обеспечивается более эффективная совместимость с большинством операционных систем.
Три класса из «старой» библиотеки ввода/вывода были изменены так, чтобы они позволяли получить канал FileChannel: это FileInputStream, FileOutputStream и RandomAccessFile. Заметьте, что эти классы манипулируют байтами, что согласуется с низкоуровневой направленностью nio. Классы для символьных данных Reader и Writer не образуют каналов, однако вспомогательный класс java.nio.channels.Channels имеет набор методов, позволяющих получить объекты Reader и Writer для каналов.
Простой пример использования всех трех типов потоков. Создаваемые каналы поддерживают запись, чтение/запись и только чтение:
//: io/GetChannel.java
// Получение каналов из потоков
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
// Write a file:
FileChannel fc =
new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc =
new RandomAccessFile("data.txt", "rw").getChannel();
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining())
System.out.print((char)buff.get());
}
}
<spoiler text="Output:">
Some text Some more
</spoiler> Для любого из рассмотренных выше классов потоков метод getChannel() выдает канал FileChannel. Канал довольно прост: ему передается байтовый буфер ByteBuffer для чтения и записи, и вы можете заблокировать некоторые участки файла для монопольного доступа (этот процесс будет описан чуть позже).
Для помещения байтов в буфер ByteBuffer используется один из нескольких методов для записи данных (put); данные записываются в виде одного или нескольких байтов или значений примитивов. Впрочем, как было показано в примере, можно «заворачивать» уже существующий байтовый массив в буфер ByteBuffer, используя метод wrap(). Когда вы так делаете, байтовый массив не копируется, а используется как хранилище для полученного буфера ByteBuffer. В таких случаях говорят, что буфер ByteBuffer создается на базе массива.
Файл data.txt заново открывается с помощью класса RandomAccessFile. Заметьте, что канал FileChannel может перемещаться внутри файла; в нашем примере он сдвигается в конец файла так, чтобы дополнительные записи присоединялись за существующим содержимым.
Чтобы доступ к файлу ограничивался только чтением, следует явно получить байтовый буфер ByteBuffer статическим методом allocate(). Предназначение nio — быстрое перемещение большого количества данных, поэтому размер буфера имеет значение: на самом деле установленный в примере размер в 1 килобайт меньше, чем обычно требуется (поэкспериментируйте с работающим приложением, чтобы найти оптимальное решение).
Можно получить еще большее быстродействие, используя вместо метода allocate() метод allocateDirect(). Он производит буфер «прямого доступа», еще теснее привязанный к низкоуровневой работе операционной системы. Однако такой буфер требует больше ресурсов, а реализация его различается в различных операционных системах. Опять же, поэкспериментируйте со своим приложением и выясните, дадут ли буферы прямого доступа лучшую производительность.
После вызова метода read() буфера FileChannel для сохранения байтов в буфере ByteBuffer также необходимо вызвать для буфера метод flip(), позволяющий впоследствии извлечь из буфера его данные (да, все это выглядит немного неудобно, но помните, что расчет делался на высокое быстродействие, поэтому все делается на низком уровне). И если затем нам снова понадобится буфер для чтения, придется вызывать перед каждым методом read() метод clear(). В этом нетрудно убедиться на примере простой программы копирования файлов:
//: io/ChannelCopy.java
// Копирование файла с использованием каналов и буферов
// {Параметры Channel Copy java test txt}
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {