Java как написать текст

Последнее обновление: 27.10.2018

Наиболее простой способ взаимодействия с пользователем представляет консоль: мы можем выводить на консоль некоторую информацию или,
наоборот, считывать с консоли некоторые данные. Для взаимодействия с консолью в Java применяется класс System,
а его функциональность собственно обеспечивает консольный ввод и вывод.

Вывод на консоль

Для создания потока вывода в класс System определен объект out. В этом объекте определен метод println,
который позволяет вывести на консоль некоторое значение с последующим переводом курсора консоли на следующую строку. Например:

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Hello world!");
		System.out.println("Bye world...");
    }
}

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

Hello world!
Bye world...

При необходимости можно и не переводить курсор на следующую строку. В этом случае можно использовать метод
System.out.print(), который аналогичен println за тем исключением,
что не осуществляет перевода на следующую строку.

public class Program {
  
    public static void main(String[] args) {
          
        System.out.print("Hello world!");
		System.out.print("Bye world...");
    }
}

Консольный вывод данной программы:

Но с помощью метода System.out.print также можно осуществить перевод каретки на следующую строку. Для этого надо использовать escape-последовательность
n:

System.out.print("Hello world n");

Нередко необходимо подставлять в строку какие-нибудь данные. Например, у нас есть два числа, и мы хотим вывести их
значения на экран. В этом случае мы можем, например, написать так:

public class Program {
  
    public static void main(String[] args) {
          
        int x=5;
		int y=6;
		System.out.println("x=" + x + "; y=" + y);
    }
}

Консольный вывод программы:

Но в Java есть также функция для форматированного вывода, унаследованная от языка С: System.out.printf(). С ее помощью мы можем переписать
предыдущий пример следующим образом:

int x=5;
int y=6;
System.out.printf("x=%d; y=%d n", x, y);

В данном случае символы %d обозначают спецификатор, вместо которого подставляет один из аргументов. Спецификаторов
и соответствующих им аргументов может быть множество. В данном случае у нас только два аргумента, поэтому вместо первого %d
подставляет значение переменной x, а вместо второго — значение переменной y. Сама буква d означает, что данный спецификатор
будет использоваться для вывода целочисленных значений.

Кроме спецификатора %d мы можем использовать еще ряд спецификаторов для других типов данных:

  • %x: для вывода шестнадцатеричных чисел

  • %f: для вывода чисел с плавающей точкой

  • %e: для вывода чисел в экспоненциальной форме, например, 1.3e+01

  • %c: для вывода одиночного символа

  • %s: для вывода строковых значений

Например:

public class Program {
  
    public static void main(String[] args) {
          
        String name = "Tom";
		int age = 30;
		float height = 1.7f;
         
		System.out.printf("Name: %s  Age: %d  Height: %.2f n", name, age, height);
    }
}

При выводе чисел с плавающей точкой мы можем указать количество знаков после запятой, для этого используем спецификатор на %.2f,
где .2 указывает, что после запятой будет два знака. В итоге мы получим следующий вывод:

Name: Tom  Age: 30  Height: 1,70

Ввод с консоли

Для получения ввода с консоли в классе System определен объект in. Однако непосредственно через объект
System.in не очень удобно работать, поэтому, как правило, используют класс Scanner, который, в
свою очередь использует System.in. Например, напишем маленькую программу, которая осуществляет ввод чисел:

import java.util.Scanner;

public class Program {
  
    public static void main(String[] args) {
          
        Scanner in = new Scanner(System.in);
		System.out.print("Input a number: ");
        int num = in.nextInt();
         
		System.out.printf("Your number: %d n", num);
		in.close();
    }
}

Так как класс Scanner находится в пакете java.util, то мы вначале его импортируем с помощью инструкции import java.util.Scanner.

Для создания самого объекта Scanner в его конструктор передается объект System.in. После этого мы можем получать вводимые значения.
Например, в данном случае вначале выводим приглашение к вводу и затем получаем вводимое число в переменную num.

Чтобы получить введенное число, используется метод in.nextInt();, который возвращает
введенное с клавиатуры целочисленное значение.

Пример работы программы:

Input a number: 5
Your number: 5

Класс Scanner имеет еще ряд методов, которые позволяют получить введенные пользователем значения:

  • next(): считывает введенную строку до первого пробела

  • nextLine(): считывает всю введенную строку

  • nextInt(): считывает введенное число int

  • nextDouble(): считывает введенное число double

  • nextBoolean(): считывает значение boolean

  • nextByte(): считывает введенное число byte

  • nextFloat(): считывает введенное число float

  • nextShort(): считывает введенное число short

То есть для ввода значений каждого примитивного типа в классе Scanner определен свой метод.

Например, создадим программу для ввода информации о человеке:

import java.util.Scanner;

public class Program {
  
    public static void main(String[] args) {
          
        Scanner in = new Scanner(System.in);
        System.out.print("Input name: ");
        String name = in.nextLine();
        System.out.print("Input age: ");
        int age = in.nextInt();
		System.out.print("Input height: ");
        float height = in.nextFloat();
        System.out.printf("Name: %s  Age: %d  Height: %.2f n", name, age, height);
		in.close();
    }
}

Здесь последовательно вводятся данные типов String, int, float и потом все введенные данные вместе выводятся на консоль. Пример работы программы:

Input name: Tom
Input age: 34
Input height: 1,7
Name: Tom  Age: 34  Height: 1,70

Обратите внимание, что для ввода значения типа float (то же самое относится к типу double) применяется число «1,7», где разделителем является
запятая, а не «1.7», где разделителем является точка. В данном случае все зависит от текущей языковой локализации системы. В моем случае русскоязычная локализация, соответственно
вводить необходимо числа, где разделителем является запятая. То же самое касается многих других локализаций, например, немецкой, французской и т.д.,
где применяется запятая.

Строка (String) – объект, который содержит последовательность символов (char). Если взглянуть на реализацию данного класса, то можно увидеть такую картину:

        public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** Значение используется для хранения символов. */
    private final char value[];

    /** Смещение – первый индекс */
    private final int offset;

    /** Count – это количество символов в строке. */
    private final int count;

    /** Кэширование хэш-кода строки */
    private int hash; // Default to 0

    [...]
}
    

В данном коде представлена часть реализации класса String. Обращаем внимание на поле value – в данном массиве хранятся все данные.

Также String является финальным классом, то есть не имеет подклассов и об этом говорит ключевое слово final при объявлении класса.

Создание

Для того чтобы создать String, имеется 2 метода:

1. Строковые литералы

        String name = "Andrey"; // присвоение к переменной
System.out.println(name);
System.out.println("Ivanov") // использование без переменной
    

В данном примере для создания строки необходимые данные заключаются в двойные кавычки. Строка выводится на экран командой println() без присваивания значения переменной.

2. Конструктор new String()

        String name = "Andrey"; 
char[] nameCharsArray = {'A', 'n', 'd', 'r', 'e', 'y'};
String nameFromArray = new String(nameCharsArray);

System.out.println(name);
System.out.println(nameFromArray );
    

В примере выше для создания объекта используется конструктор String(). Массив символов (nameCharsArray) помещается как аргумент в конструктор String, который создает строку.

Длина

У строки есть метод length, который возвращает int – длину строки.

        String name = "alexandr"; 
// проходимся по строке name
for(int i = 0; i < name.length(); i++) { 
	if (name.charAt(i) != 'a') { // если символ i в строке name не является 'a'
			System.out.print(name.charAt(i)); // то вывести данный символ
	} 
}
    

В примере выше написали программу, которая проходится по переменной name и выводит все cимволы кроме a.

Конкатенация

Конкатенация — операция объединения строк, которая возвращает новую строку, объединяя первую строчку со второй. Операция для объекта String может быть выполнена двумя способами:

1. Метод concat

        String name = "Andrey";
String helloMessage = "Hello, ";

String helloMessageWithName = helloMessage.concat(name);
System.out.println(helloMessageWithName );
    

Важно понимать, что метод concat не изменяет строку, а лишь создает новую как результат слияния текущей и переданной в качестве параметра.

2. Перегруженные операторы «+» и «+=»

Данный метод для конкатенации использует математические операторы + и +=. Пример использования:

        String message = "Hello, " + "Andrey!"; 
message += "/nGood to see you";
System.out.println(message);
    

В данном примере есть переменная message, к которой через конкатенацию присваивается значения “Hello” и “Andrey!”. Также на следующей строчке оператором += присваивается значение “/nGood to see you”. В случаях, когда слияние нужно провести только один раз, рекомендовано использовать операторы.

Форматирование

Класс String предоставляет статистический метод format, который дает возможность для форматирования строк:

        String message = "Hello, %s! /nGood to see you. Today's weather is %d degree Celcius."
System.out.println(String.format(message, "Andrey", 35))
    

Метод format предоставляет возможность для вставки и редактирования строки. Как видно на примере, имеется переменная message, содержащая сообщение. В сообщении находятся специальные символы:

  • %s – означает строку.
  • %d – означает целое число.
  • %f – означает вещественное число.

Методы

Класс String предоставляет множество методов управления строкой и его символами. Подробное описание методов можно найти, перейдя по данной ссылке.

Преобразование

1. Число в строку

        int degreeInCelcius = 35;
String firstMethod = degreeInCelcius + ""; // конкатенация со строкой
String secondMethod = String.valueOf(degreeInCelcius); // использования метода valueOf()
String thirdMethod = Interger.toString(degreeInCelcius); // использование метода toString()
    

2. Строку в число

        String degreeInCelcius = "35";
int firstMethod = Interger.parseInt(degreeInCelcius); // использование метода parseInt()
int secondMethod = Interger.valueOf(degreeInCelcius); // использование метода valueOf()
    

StringBuffer

Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что, в свою очередь, расходует память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым. StringBuffer используется в многопоточных средах, так как все необходимые методы являются синхронизированными.

Создание

Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), отвечающую за длину внутреннего буфера. Если длина строки, хранящейся во внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется – он автоматически становится больше.

        StringBuffer firstBuffer = new StringBuffer(); // capacity = 16
StringBuffer secondBuffer = new StringBuffer("habrahabr"); // capacity = str.length() + 16
StringBuffer thirdBuffer = new StringBuffer(secondBuffer); // параметр - любой класс, что реализует CharSequence
StringBuffer fourthBuffer = new StringBuffer(50); // передаем capacity
    

Модификация

В большинстве случаев StringBuffer используется для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Например:

        String name = "Andrey";
String surName = "Andreyev";
StringBuffer buffer = new StringBuffer();
buffer.append(name + " "); // добавляем имя вместе с пробелом
buffer.append(surName); // добавляем фамилие
buffer.delete(buffer.length() - surName.length(), buffer.length()); // удаляем фамилие
// buffer.delete([стартовый индекс], [индекс конечного символа])

buffer.insert(buffer.length(), surName); // вставляем строку по определенному индексу
// buffer.insert([с какого индекса вставить], [cтрока])

    

StringBuilder

StringBuilder – класс, предоставляющий изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие – StringBuilder не синхронизирован. Это означает, что его нежелательно использовать в многопоточных средах. Следовательно, для работы с многопоточностью идеально подходит StringBuffer. Пример использования:

        // Создаем StringBuilder объект
// используя StringBuilder() конструктор
StringBuilder str = new StringBuilder();
str.append("Andrey");
// выводим как string
System.out.println("String = " + str1.toString());

// Создаем StringBuilder объект
// используя StringBuilder(CharSequence) конструктор
StringBuilder str1
    = new StringBuilder("Andreyev");

// выводим как string
System.out.println("String1 = " + str1.toString());

// Создаем StringBuilder объект
// using StringBuilder(capacity) конструктор
StringBuilder str2 = new StringBuilder(10);

// выводим вмещаемость
System.out.println("String2 capacity = "
                   + str2.capacity());

// Создаем StringBuilder объект
// uспользуя StringBuilder(String) конструктор
StringBuilder str3
    = new StringBuilder(str1.toString());

// выводим как string
System.out.println("String3 = " + str3.toString());
    

Сравнение строк

Для сравнения строк используются методы:

  • equals(), с учетом регистра
  • equalsIgnoreCase(), без учета регистра

Оба метода в качестве параметра принимают строку, с которой надо сравнить:

        String str1 = "Hello";
String str2 = "hello";
         
System.out.println(str1.equals(str2)); 
// false - в данном примере происходит сравнение строк с учетом регистра
System.out.println(str1.equalsIgnoreCase(str2)); 
// true - в данном примере сравнение происходит без учета регистра
    

Разбиение строки на массив строк

Для деления строки у класса java.lang.String существует метод split, который в аргументе принимает символ — разделитель строки.

        String str = "Строка для деления методом split";
String[] arrStrings = str.split(" "); // делим строку 
System.out.println(Arrays.toString(arrStrings));

/*
Вывод
[Строка, для, деления, методом, split]
*/
    

Определение позиции элемента в строке

Метод indexOf() ищет в строке заданный символ или строку и возвращает индекс (т. е. порядковый номер).

Метод:

  • возвращает индекс, под которым символ или строка первый раз появляется в строке;
  • возвращает (-1), если символ или строка не найдены.

Метод также может искать символ или строку, начиная с указанного индекса.

        String hello = "Hello";
int index1 = hello.indexOf('H');
int index2 = hello.indexOf('o');
int index3 = hello.indexOf('W');
System.out.println("Мы ищем букву 'H' в строке "+hello+". Индекс данной буквы "+index1 );
System.out.println("Мы ищем букву 'o' в строке "+hello+". Индекс данной буквы "+index2 );
System.out.println("Мы ищем букву 'W' в строке "+hello+". Индекс данной буквы "+index3 );
    

Извлечение подстроки из строки

Метод substring() в Java возвращает новую строку, которая является подстрокой данной строки. Подстрока начинается с символа, заданного индексом, и продолжается до конца данной строки или до endIndex-1, если введен второй аргумент.

        // строка.substring(откуда отрезать, докуда отрезать)

String Str = new String("Добро пожаловать на proglib.io");

System.out.print("Возвращаемое значение: ");
System.out.println(Str.substring(5));

System.out.print("Возвращаемое значение: ");
System.out.println(Str.substring(5, 15));

/*
Возвращаемое значение:  пожаловать на proglib.io
Возвращаемое значение:  пожаловат
*/
    

Перевод строки в верхний/нижний регистр

Преобразовать текстовые строки Java в верхний или нижний регистр довольно просто: для этого используйте встроенные методы Java – toUpperCase и toLowerCase. Подробнее с примером.

        String changeCase = "Текст, Который Будет Изменяться";
System.out.println(changeCase);

String result;
result = changeCase.toUpperCase(); // строка переводится в верхний регистр
System.out.println(result);

result = changeCase.toLowerCase(); // строка переводится в нижний регистр
System.out.println(result);
    

Перевод коллекции строк к строковому представлению

Чтобы соединить строки в Java, используется метод String.join(). Разделитель, заданный в качестве первого параметра в методе, копируется для каждого элемента. Допустим, нужно объединить строки «Demo» и «Text». Для этого используется метод join(), как показано ниже:

        String str = String.join("$","Demo","Text"); // разделителем является $
System.out.println("Joined strings: "+str);

/*
Joined strings: Demo$Text
*/
    

***

Материалы по теме

  • ☕ Учебник по Java: инкапсуляция на простых примерах
  • ☕ Учебник по Java: списочный массив ArrayList
  • ☕ Учебник по Java: cтатический и динамический полиморфизм

Время на прочтение
8 мин

Количество просмотров 420K

Вступление

Что вы знаете о обработке строк в Java? Как много этих знаний и насколько они углублены и актуальны? Давайте попробуем вместе со мной разобрать все вопросы, связанные с этой важной, фундаментальной и часто используемой частью языка. Наш маленький гайд будет разбит на две публикации:

  1. String, StringBuffer, StringBuilder (реализация строк)
  2. Pattern, Matcher (регулярные выражения)

Реализация строк на Java представлена тремя основными классами: String, StringBuffer, StringBuilder. Давайте поговорим о них.

String

Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен. Можно подумать что методы имеют право изменять этот объект, но это неверно. Методы могут только создавать и возвращать новые строки, в которых хранится результат операции. Неизменяемость строк предоставляет ряд возможностей:

  • использование строк в многопоточных средах (String является потокобезопасным (thread-safe) )
  • использование String Pool (это коллекция ссылок на String объекты, используется для оптимизации памяти)
  • использование строк в качестве ключей в HashMap (ключ рекомендуется делать неизменяемым)

Создание

Мы можем создать объект класса String несколькими способами:

1. Используя строковые литералы:

String habr = "habrahabr";

Строковый литерал — последовательность символов заключенных в двойные кавычки. Важно понимать, что всегда когда вы используете строковой литерал компилятор создает объект со значением этого литерала:

System.out.print("habrahabr"); // создали объект и вывели его значение

2. С помощью конструкторов:

String habr = "habrahabr";
char[] habrAsArrayOfChars = {'h', 'a', 'b', 'r', 'a', 'h', 'a', 'b', 'r'};
byte[] habrAsArrayOfBytes = {104, 97, 98, 114, 97, 104, 97, 98, 114};
 
String first = new String();
String second = new String(habr);

Если копия строки не требуется явно, использование этих конструкторов нежелательно и в них нет необходимости, так как строки являются неизменными. Постоянное строительство новых объектов таким способом может привести к снижению производительности. Их лучше заменить на аналогичные инициализации с помощью строковых литералов.

String third = new String(habrAsArrayOfChars); // "habrahabr"
String fourth = new String(habrAsArrayOfChars, 0, 4); // "habr"

Конструкторы могут формировать объект строки с помощью массива символов. Происходит копирование массива, для этого используются статические методы copyOf и copyOfRange (копирование всего массива и его части (если указаны 2-й и 3-й параметр конструктора) соответственно) класса Arrays, которые в свою очередь используют платформо-зависимую реализацию System.arraycopy.

String fifth = new String(habrAsArrayOfBytes, Charset.forName("UTF-16BE")); // кодировка нам явно не подходит "桡扲慨慢�"

Можно также создать объект строки с помощью массива байтов. Дополнительно можно передать параметр класса Charset, что будет отвечать за кодировку. Происходит декодирование массива с помощью указанной кодировки (если не указано — используется Charset.defaultCharset(), который зависит от кодировки операционной системы) и, далее, полученный массив символов копируется в значение объекта.

String sixth = new String(new StringBuffer(habr));
String seventh = new String(new StringBuilder(habr));

Ну и наконец-то конструкторы использующие объекты StringBuffer и StringBuilder, их значения (getValue()) и длину (length()) для создания объекта строки. С этими классами мы познакомимся чуть позже.

Приведены примеры наиболее часто используемых конструкторов класса String, на самом деле их пятнадцать (два из которых помечены как deprecated).

Длина

Важной частью каждой строки есть ее длина. Узнать ее можно обратившись к объекту String с помощью метода доступа (accessor method) length(), который возвращает количество символов в строке, например:

public static void main(String[] args) {
    String habr = "habrahabr";
    // получить длину строки
    int length = habr.length();
    // теперь можно узнать есть ли символ символ 'h' в "habrahabr"
    char searchChar = 'h';
    boolean isFound = false;
    for (int i = 0; i < length; ++i) {
        if (habr.charAt(i) == searchChar) {
            isFound = true;
            break; // первое вхождение
        }
    }
    System.out.println(message(isFound)); // Your char had been found!
    // ой, забыл, есть же метод indexOf
    System.out.println(message(habr.indexOf(searchChar) != -1)); // Your char had been found!
}

private static String message(boolean b) {
    return "Your char had" + (b ? " " : "n't ") + "been found!";
}

Конкатенация

Конкатенация — операция объединения строк, что возвращает новую строку, что есть результатом объединения второй строки с окончанием первой. Операция для объекта String может быть выполнена двумя способами:

1. Метод concat

String javaHub = "habrhabr".concat(".ru").concat("/hub").concat("/java");
System.out.println(javaHub); // получим "habrhabr.ru/hub/java"
// перепишем наш метод используя concat
private static String message(boolean b) {
    return "Your char had".concat(b ? " " : "n't ").concat("been found!");
}

Важно понимать, что метод concat не изменяет строку, а лишь создает новую как результат слияния текущей и переданной в качестве параметра. Да, метод возвращает новый объект String, поэтому возможны такие длинные «цепочки».

2. Перегруженные операторы «+» и «+=«

String habr = "habra" + "habr"; // "habrahabr"
habr += ".ru"; // "habrahabr.ru"

Это одни с немногих перегруженных операторов в Java — язык не позволяет перегружать операции для объектов пользовательских классов. Оператор «+» не использует метод concat, тут используется следующий механизм:

String habra = "habra";
String habr = "habr";
// все просто и красиво
String habrahabr = habra + habr;
// а на самом деле
String habrahabr = new StringBuilder()).append(habra).append(habr).toString(); // может быть использован StringBuffer

Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор «+» или StringBuffer / StringBuilder. Также стоит отметить, что получить NPE (NullPointerException), если один с операндов равен null, невозможно с помощью оператора «+» или «+=«, чего не скажешь о методе concat, например:

String string = null;
string += " habrahabr"; // null преобразуется в "null", в результате "null habrahabr"
string = null;
string.concat("s"); // логично что NullPointerException

Форматирование

Класс String предоставляет возможность создания форматированных строк. За это отвечает статический метод format, например:

String formatString = "We are printing double variable (%f), string ('%s') and integer variable (%d).";
System.out.println(String.format(formatString, 2.3, "habr", 10));
// We are printing double variable (2.300000), string ('habr') and integer variable (10).

Методы

Благодаря множеству методов предоставляется возможность манипулирования строкой и ее символами. Описывать их здесь нет смысла, потому что Oracle имеет хорошие статьи о манипулировании и сравнении строк. Также у вас под рукой всегда есть их документация. Хотелось отметить новый статический метод join, который появился в Java 8. Теперь мы можем удобно объединять несколько строк в одну используя разделитель (был добавлен класс java.lang.StringJoiner, что за него отвечает), например:

String hello = "Hello";
String habr = "habrahabr";
String delimiter = ", ";

System.out.println(String.join(delimiter, hello, habr));
// или так
System.out.println(String.join(delimiter, new ArrayList<CharSequence>(Arrays.asList(hello, habr))));
// в обоих случаях "Hello, habrahabr"

Это не единственное изменение класса в Java 8. Oracle сообщает о улучшении производительности в конструкторе String(byte[], *) и методе getBytes().

Преобразование

1. Число в строку

int integerVariable = 10;
String first = integerVariable + ""; // конкатенация с пустой строкой
String second = String.valueOf(integerVariable); // вызов статического метода valueOf класса String
String third = Integer.toString(integerVariable); // вызов метода toString класса-обертки

2. Строку в число

String string = "10";
int first = Integer.parseInt(string); 
/* 
   получаем примитивный тип (primitive type) 
   используя метод parseXхх нужного класса-обертки,
   где Xxx - имя примитива с заглавной буквы (например parseInt) 
*/
int second = Integer.valueOf(string); // получаем объект wrapper класса и автоматически распаковываем

StringBuffer

Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными.

Создание

Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), что отвечает за длину внутреннего буфера. Если длина строки, что хранится в внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется — он автоматически становиться больше.

StringBuffer firstBuffer = new StringBuffer(); // capacity = 16
StringBuffer secondBuffer = new StringBuffer("habrahabr"); // capacity = str.length() + 16
StringBuffer thirdBuffer = new StringBuffer(secondBuffer); // параметр - любой класс, что реализует CharSequence
StringBuffer fourthBuffer = new StringBuffer(50); // передаем capacity

Модификация

В большинстве случаев мы используем StringBuffer для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Тут все очень просто, например:

String domain = ".ru";
// создадим буфер с помощью String объекта
StringBuffer buffer = new StringBuffer("habrahabr"); // "habrahabr"
// вставим домен в конец
buffer.append(domain); // "habrahabr.ru"
// удалим домен
buffer.delete(buffer.length() - domain.length(), buffer.length()); // "habrahabr"
// вставим домен в конец на этот раз используя insert
buffer.insert(buffer.length(), domain); // "habrahabr.ru"

Все остальные методы для работы с StringBuffer можно посмотреть в документации.

StringBuilder

StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах есть нежелательным. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций. Напишем небольшой тест для сравнения скорости работы этих двух классов:

public class Test {
    public static void main(String[] args) {
        try {
            test(new StringBuffer("")); // StringBuffer: 35117ms.
            test(new StringBuilder("")); // StringBuilder: 3358ms.
        } catch (java.io.IOException e) {
            System.err.println(e.getMessage());
        }
    }
    private static void test(Appendable obj) throws java.io.IOException {
        // узнаем текущее время до теста 
        long before = System.currentTimeMillis();
        for (int i = 0; i++ < 1e9; ) {
            obj.append("");
        }
        // узнаем текущее время после теста 
        long after = System.currentTimeMillis();
        // выводим результат
        System.out.println(obj.getClass().getSimpleName() + ": " + (after - before) + "ms.");
    }
}

Спасибо за внимание. Надеюсь статья поможет узнать что-то новое и натолкнет на удаление всех пробелов в этих вопросах. Все дополнения, уточнения и критика приветствуются.

JavaSpec_970x90-20219-e8e90f.png

Консоль (console) в Java обеспечивает простое и удобное взаимодействия с пользователем. С помощью консоли можно выводить какую-нибудь информацию либо, напротив, используя консоль, считывать данные. В этой статье будет рассказано о том, как осуществляется ввод и вывод данных в консоли Java.

Чтобы обеспечивать взаимодействие с консолью, в языке программирования Java используют класс System.

Вывод на консоль в Java

Чтобы создать потока вывода в вышеупомянутый класс System, вам понадобится специальный объект out. В нём определен метод println, обеспечивающий вывод значения на консоль и перевод курсора консоли на другую строку.

Рассмотрим практический пример с Hello world:

    public class Main {

    public static void main(String[] args) {

        System.out.println("Привет, мир!");
        System.out.println("Пока, мир...");
    }
}

Что здесь происходит? В метод println осуществляется передача значения (в нашем случае это строка), которое пользователь желает вывести в консоль Java. Консольный вывод данных в Джава будет следующий:

Привет, мир!                                                                                                                  
Пока, мир... 

Выполнять перевод строки не обязательно. Если необходимость в этом отсутствует, применяют метод System.out.print(). Он аналогичен println, но перевод каретки на следующую строку не выполняется.

    public class Main {

    public static void main(String[] args) {

        System.out.print("Привет, мир!");
        System.out.print("Пока, мир...");
    }
}

Вывод в консоли Java:


Однако никто не мешает, используя System.out.print, всё же выполнить перенос на следующую строку. Как вариант — использование n:

    System.out.print("Привет, мир! n");

Также есть возможность подставить в строку Ява данные, которые объявлены в переменных. Вот, как это реализуется:

    public class Main {

    public static void main(String[] args) {

        int i = 10;
        int y = 99;
        System.out.println("i=" + i + "; y=" + y);
    }
}

В консоли увидим:


Ещё в Java существует функция, предназначенная для форматирования вывода в консоли, —System.out.printf(). При использовании со спецификаторами, она позволяет добиться нужного формата вывода.

Спецификаторы:
• %d — для вывода в консоль целочисленных значений;
• %x — для 16-ричных чисел;
• %f — выводятся числа с плавающей точкой;
• %e — для чисел в экспоненциальной форме (1.3e+01);
• %c — вывод в консоль одиночного символа;
• %s — вывод в консоль строковых значений.

Рассмотрим, как это функционирует на практике:

    public class Main {

    public static void main(String[] args) {

        String name = "Bob";
        int age = 40;
        float height = 1.8f;

        System.out.printf("Name: %s  Age: %d  Height: %.2f n", name, age, height);
    }
}

Когда осуществляется вывод в консоль Java значений с плавающей точкой, есть возможность задать количество знаков после запятой. Спецификатор %.2f (точнее, «.2») определяет, что будет 2 знака после запятой. Вывод в консоль Java будет следующим:

Name: Bob  Age: 40  Height: 1.80  

JavaSpec_970x90-20219-e8e90f.png

Ввод с консоли Java или как ввести данные с консоли Джавы

Чтобы обеспечить ввод с консоли Java, в классе System есть объект in. Именно через объект System.in работать не очень удобно, поэтому часто применяют класс Scanner. Он уже, в свою очередь, как раз таки и применяет System.in.

Рассмотрим практический пример:

    import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        System.out.print("Введите любой номер: ");
        int num = in.nextInt();

        System.out.printf("Ваш номер: %d n", num);
        in.close();
    }
}

Сам по себе класс Scanner хранится в пакете java.util, поэтому в начале кода мы выполняем его импорт посредством команды import java.util.Scanner.

Для создания непосредственно объекта Scanner в его конструктор осуществляется передача объекта System.in. Далее можно получать значения. В нашей мини-программе сначала выводится просьба ввести номер, а потом введённое пользователем число помещается в переменную num (для получения введённого значения задействуется метод in.nextInt(), возвращающий набранное на клавиатуре целочисленное значение.

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

Работать она будет простейшим образом:
1. Сначала вы увидите сообщение в консоли «Введите любой номер:».
2. После ввода числа (пускай это будет 8) в консоли появится второе сообщение — «Ваш номер: 8».

Для класса Scanner предусмотрены и другие методы:
next() — для считывания введённой строки до первого пробела;
nextLine() — для всей введённой строки;
nextInt() — считывает введённое число int;
nextDouble() — для double;
nextBoolean() — для boolean;
nextByte() — для byte;
nextFloat() — для float;
nextShort() — для short.

Давайте напишем простую программу, обеспечивающую ввод информационных данных о человеке в консоль Java:

    import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        System.out.print("Введите имя: ");
        String name = in.nextLine();
        System.out.print("Введите возраст: ");
        int age = in.nextInt();
        System.out.print("Введите вес: ");
        float height = in.nextFloat();
        System.out.printf("Name: %s  Age: %d  Height: %.1f n", name, age, height);
        in.close();
    }
}

В этой программке пользователь последовательно вводит данные разных типов: String, int и float. Потом вся информация выводится в консоль Java:

Введите имя: Андрей                                                                                                           
Введите возраст: 39                                                                                                           
Введите вес: 89                                                                                                               
Name: Андрей  Age: 39  Height: 89.0   

Вот и всё. Это базовые вещи, если же вас интересуют более продвинутые знания, записывайтесь на курс OTUS в Москве:

JavaSpec_970x550-20219-a74b18.png

Глава 10

Ввод-вывод данных

Основные навыки и понятия

  • Представление о потоках ввода-вывода
  • Отличия байтовых и символьных потоков
  • Классы для поддержки байтовых потоков
  • Классы для поддержки символьных потоков
  • Представление о встроенных потоках
  • Применение байтовых потоков
  • Использование байтовых потоков для файлового ввода-вывода
  • Автоматическое закрытие файлов с помощью оператора try с ресурсами
  • Чтение и запись двоичных данных
  • Манипулирование файлами с произвольным доступом
  • Применение символьных потоков
  • Использование символьных потоков для файлового ввода-вывода
  • Применение оболочек типов Java для преобразования символьных строк в числа

В примерах программ, приводившихся в предыдущих главах, уже применялись отдельные части системы ввода-вывода в Java, в частности метод println(), но делалось это без каких-либо формальных пояснений. Система ввода-вывода основана в Java на иерархии классов, поэтому ее функции и особенности нельзя было представлять до тех пор, пока не были рассмотрены классы, наследование и исключения. А теперь настал черед и для средств ввода-вывода.

Приступая к изучению системы ввода вырода Java, приготовьтесь к длительной и кропотливой работе. Система ввода-вывода в Java довольно обширна и содержит немало классов, интерфейсов и методов. Объясняется это, в частности, тем, что в Java, по существу, определены две полноценные системы ввода-вывода: одна — для обмена байтами, другая — для обмена символами. Здесь нет возможности рассмотреть все аспекты ввода-вывода в Java, ведь для этого бы потребовалась отдельная книга. Поэтому в данной главе будут рассмотрены лишь наиболее важные и часто используемые языковые средства ввода-вывода. Правда, элементы системы ввода-вывода в Java тесно взаимосвязаны, и поэтому, уяснив основы, вы легко освоите все остальные свойства этой системы.

Прежде чем приступать к рассмотрению системы ввода-вывода, необходимо сделать следующее замечание. Классы, описанные в этой главе, предназначены для консольного и файлового ввода-вывода. Они не применяются для создания графических пользовательских интерфейсов. Поэтому ими не имеет смысла пользоваться при создании оконных приложений. Для графических интерфейсов предусмотрены другие языковые средства. Они будут представлены в главе 14 при рассмотрении апплетов, а также в главе 15, служащей введением в библиотеку Swing. (Swing — это современный набор инструментальных средств, ориентированных на создание графических пользовательских интерфейсов приложений.)

Организация системы ввода-вывода в Java на потоках

Ввод-вывод в программах на Java осуществляется посредством потоков. Поток — это некая абстракция производства или потребления информации. С физическим устройством поток связывает система ввода-вывода. Все потоки действуют одинаково — даже если они связаны с разными физическими устройствами. Поэтому классы и методы ввода-вывода могут применяться к самым разным типам устройств. Например, методами вывода на консоль можно пользоваться и для вывода в файл на диске. Для реализации потоков используется иерархия классов, содержащихся в пакете java.io.

Байтовые и символьные потоки

В современных версиях Java определены два типа потоков: байтовые и символьные. (В первоначальных версиях Java были доступны только байтовые потоки, тогда как символьные потоки были реализованы в дальнейшем.) Байтовые потоки предоставляют удобные средства для ввода и вывода байтов. Они используются, например, при чтении и записи двоичных данных. В особенности они полезны для обращения с файлами. А символьные потоки ориентированы на обмен символьными данными. В них применяется кодировка в уникоде (Unicode), а следовательно, программы, в которых используются символьные потоки, легко поддаются локализации на разные языки мира. В некоторых случаях символьные потоки обеспечивают более высокую эффективность по сравнению с байтовыми.

Необходимость поддерживать два разных типа потоков ввода-вывода привела к созданию двух иерархий классов (одна для байтовых, другая для символьных данных). Из-за того что число классов достаточно велико, на первый взгляд система ввода-вывода кажется сложнее, чем она есть на самом деле. Но не следует забывать, что функциональные возможности для байтовых потоков дублируются соответствующими средствами для символьных потоков.

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

Классы байтовых потоков

Для определения байтовых потоков служат две иерархии классов. На их вершине находятся два абстрактных класса: InputStream и OutputStream. В классе InputStream определены свойства, общие для байтовых потоков ввода, а в классе OutputStream — свойства, общие для байтовых потоков вывода.

Производными от классов InputStream и OutputStream являются конкретные подклассы, реализующие различные функциональные возможности и учитывающие особенности обмена данными с разными устройствами, например ввода-вывода в файлы на
диске. Классы байтовых потоков приведены в табл. 10.1. Не следует пугаться большого
количества этих классов: изучив один из них, легко освоить остальные.

Таблица 10.1. Классы байтовых потоков

Класс байтового потока Описание
BufferedlnputStream Буферизованный поток ввода
BufferedOutputStream Буферизованный поток вывода
ByteArrayInputStream Поток ввода для чтения из байтового массива
ByteArrayOutputStream Поток вывода для записи в байтовый массив
DatalnputStream Поток ввода с методами для чтения стандартных типов данных Java
DataOutputStream Поток вывода с методами для записи стандартных типов данных Java
FileInputStream Поток ввода для чтения из файла
FileOutputStream Поток вывода для записи в файл
FilterlnputStream Подкласс, производный от класса InputStream
FilterOutputStream Подкласс, производный от класса OutputStream
InputStream Абстрактный класс, описывающий потоковый ввод
ObjectInputStream Поток для ввода объектов
ObjectOutputStream Поток для вывода объектов
OutputStream Абстрактный класс, описывающий потоковый вывод
PipedlnputStream Поток конвейерного ввода
PipedOutputStream Поток конвейерного вывода
PrintStream Поток вывода с методами print() и println()
PushbacklnputStream Поток ввода с возвратом прочитанных байтов в поток
RandomAccessFile Класс, поддерживающий файловый ввод-вывод с произвольным доступом
SequenceInputStream Поток ввода, сочетающий в себе несколько потоков ввода для поочередного чтения данных из них

Классы символьных потоков

Для определения символьных потоков служат две иерархические структуры классов, на вершине которых находятся абстрактные классы Reader и Writer соответственно. Класс Reader и его подклассы используются для чтения, а класс Writer и его подклассы — для записи данных. Конкретные классы, производные от классов Reader и Writer, оперируют символами в уникоде.

Классы, производные от классов Reader и Writer, предназначены для выполнения различных операций ввода-вывода символов. Символьные классы присутствуют в Java параллельно с байтовыми классами. Классы символьных потоков приведены в табл. 10.2.
Таблица 10.2. Классы символьных потоков

Класс символьного потока Описание
BufferedReader Буферизованный поток ввода символов
BufferedWriter Буферизованный поток вывода символов
CharArrayReader Поток ввода для чтения из символьного массива
CharArrayWriter Поток вывода для записи в символьный массив
FileReader Поток ввода для чтения символов из файла
FileWriter Поток вывода для записи символов в файл
FilterReader Класс для чтения символов с фильтрацией
FilterWriter Класс для записи символов с фильтрацией
InputStreamReader Поток ввода с преобразованием байтов в символы
LineNumberReader Поток ввода с подсчетом символьных строк
OutputStreamWriter Поток вывода с преобразованием символов в байты
PipedReader Поток конвейерного ввода
PipedWriter Поток конвейерного вывода
PrintWriter Поток вывода с методами print() и println()
PushbackReader Поток ввода с возвратом прочитанных символов в поток
Reader Абстрактный класс, описывающий потоковый ввод символов
StringReader Поток ввода для чтения из символьной строки
StringWriter Поток вывода для записи в символьную строку
Writer Абстрактный класс, описывающий потоковый вывод символов

Встроенные потоки

Как вам должно быть уже известно, во все программы на Java автоматически импортируется пакет java. lang. В этом пакете определен класс System, инкапсулирующий некоторые элементы среды выполнения программ. Помимо прочего, в нем содержатся предопределенные переменные in, out и err, представляющие стандартные потоки ввода-вывода. Эти поля объявлены как public, final и static. А это означает, что ими можно пользоваться в любой другой части программы, не ссылаясь на конкретный объект типа System.

Переменная System.out ссылается на поток стандартного вывода. По умолчанию этот поток связан с консолью. А переменная System, in ссылается на поток стандартного ввода (по умолчанию с клавиатуры). И наконец, переменная System.err ссылается на поток стандартных сообщений об ошибках, которые по умолчанию выводятся на консоль. По мере необходимости все эти потоки могут быть перенаправлены на другие совместимые устройства ввода-вывода.

Поток System.in представляет собой объект типа InputStream, а потоки System.out и System.err — объекты типа PrintStream. Хотя эти потоки обычно используются для чтения и записи символов, они на самом деле являются байтовыми потоками. Дело в том, что эти потоки были определены в первоначальной спецификации Java, где символьные потоки вообще не были предусмотрены. Как станет ясно в дальнейшем, для этих потоков можно по необходимости создать оболочки, превратив их в символьные потоки.

Применение байтовых потоков

Начнем рассмотрение системы ввода-вывода в Java с байтовых потоков. Как пояснялось ранее, на вершине иерархии байтовых потоков находятся классы InputStream и OutputStream. Методы из класса InputStream приведены в табл. 10.3, а методы из класса OutputStream — в табл. 10.4. При возникновении ошибок в процессе выполнения методы из классов InputStream и OutputStream могут генерировать исключения типа IOException. Методы, определенные в этих двух абстрактных классах, доступны во всех подклассах. Таким образом, они формируют минимальный набор функций ввода-вывода, общих для всех байтовых потоков.

Таблица 10.3. Методы, определенные в классе InputStream

Метод Описание
int available() Возвращает количество байтов, доступных для чтения
void close() Закрывает поток ввода. При последующей попытке чтения из потока генерируется исключение IOException
void mark(int numBytes) Ставит отметку на текущей позиции в потоке. Отметка доступна до тех пор, пока на будет прочитано количество байтов, определяемое параметром numBytes
boolean markSupported() Возвращает логическое значение true, если методы mark() и reset() поддерживаются в вызывающем потоке
int read() Возвращает целочисленное представление следующего байта в потоке. Если достигнут конец потока, возвращается значение -1
int read(byte buffer[]) Предпринимает попытку прочитать количество байтов, определяемое выражением buffer, length, в массив buffer и возвращает фактическое количество успешно прочитанных байтов. Если достигнут конец потока, возвращается значение -1
int read(byte buffer[], int offset, int numBytes) Предпринимает попытку прочитать количество байтов, определяемое параметром numBytes, в массив buffer, начиная с элемента buffer[offset]. Если достигнут конец потока, возвращается значение -1
void reset() Устанавливает указатель ввода на помеченной ранее позиции
long skip (long numBytes) Пропускает количество байтов, определяемое параметром numBytes, в потоке ввода. Возвращает фактическое количество пропущенных байтов

Таблица 10.4. Методы, определенные в классе OutputStream

Метод Описание
void close() Закрывает выходной поток. При последующей попытке записи в поток генерируется исключение IOExceptionВыводит содержимое выходного буфера вывода в
void flush() Выводит содержимое выходного буфера вывода в целевой поток. По завершении этой операции выходной буфер очищается
void write(int b) Записывает один байт в поток вывода. Параметр b относится к типу int, что позволяет вызывать данный метод в выражениях, не приводя результат их вычисления к типу byte
void write(byte buffer[]) Записывает массив в поток вывода
void write(byte buffer[], int offset, int numBytes) Записывает в поток вывода часть массива buffer длиной numBytes байтов, начиная с элемента buffer[offset]

Консольный ввод

Первоначально единственный способ реализовать консольный ввод в Java состоял в использовании байтовых потоков, и во многих программах на Java до сих пор используются исключительно потоки данного типа. В настоящее время для разработки прикладных программ доступны как байтовые, так и символьные потоки. В коммерческих приложениях для чтения данных с консоли в основном используются символьные потоки. Такой подход упрощает локализацию программ и их сопровождение. Ведь намного удобнее оперировать непосредственно символами и не тратить время и труд на преобразование символов в байты, а байтов — в символы. Но в простых служебных и прикладных программах, где данные, введенные с клавиатуры, обрабатываются непосредственно, удобно пользоваться байтовыми потоками. Именно поэтому они здесь и рассматриваются.

Поток System, in является экземпляром класса InputStream, и благодаря этому обеспечивается автоматический доступ к методам, определенным в классе InputStream. К сожалению, в классе InputStream определен только один метод ввода, read(), предназначенный для чтения байтов. Ниже приведены разные формы объявления этого метода.

int read() throws IOException
int read(byte data[]) throws IOException
int read(byte data[], int start, int max) throws IOException

В главе 3 было показано, как пользоваться первой формой метода read() для ввода отдельных символов с клавиатуры (а по существу, из потока стандартного ввода System, in). Достигнув конца потока, этот метод возвращает значение -1. Вторая форма метода read() предназначена для чтения данных из потока ввода в массив data. Чтение завершается по достижении конца потока, па заполнении массива или при возникновении ошибки. Метод возвращает количество прочитанных байтов или значение -1, если достигнут конец потока. И третья форма данного метода позволяет разместить прочитанные данные в массиве data, начиная с элемента, обозначаемого индексом start. Максимальное количество байтов, которые могут быть введены в массив, определяется параметром max. Метод возвращает число прочитанных байтов или значение -1, если достигнут конец потока. При возникновении ошибки в каждой из этих форм метода read() генерируется исключение IOException. Условие конца потока ввода System, in устанавливается при нажатии клавиши .

Ниже приведен краткий пример программы, демонстрирующий чтение байтов из потока ввода System, in в массив. Следует иметь в виду, что исключения, которые могут быть сгенерированы при выполнении данной программы, обрабатываются за пределами метода main(). Такой подход часто используется при чтении данных с консоли. По мере необходимости вы сможете самостоятельно организовать обработку ошибок.

// Чтение байтов с клавиатуры в массив,
import java.io.*;
class ReadBytes {
    public static void main(String args[])
            throws IOException {
        byte data[] = new byte[10];

        System.out.println("Enter some characters.");
        // Чтение данных, введенных с клавиатуры,
        // и размещение их в байтовом массиве.
        System.in.read(data);
        System.out.print("You entered: ");
        for(int i=0; i < data.length; i++)
            System.out.print((char) data[i]);
    }
}

Выполнение этой программы дает например, следующий результат:

Enter some characters.
Read Bytes
You entered: Read Bytes

Вывод на консоль

Как и для консольного ввода, в Java для консольного вывода первоначально были предусмотрены только байтовые потоки. Но уже в версии Java 1.1 были реализованы символьные потоки. Именно их и рекомендуется применять в прикладных программах, особенно в тех случаях, когда необходимо добиться переносимости кода. Но поскольку System, out является байтовым потоком вывода, он по-прежнему широко используется для побайтового вывода данных на консоль. Именно такой подход до сих пор применялся в примерах, представленных в этой книге. Поэтому он здесь и рассматривается.

Вывести данные на консоль проще всего с помощью уже знакомых вам методов print() и println(). Эти методы определены в классе PrintStream (на объект данного типа ссылается переменная потока стандартного вывода System.out). Несмотря на то что System, out является байтовым потоком вывода, пользоваться им вполне допустимо для организации элементарного вывода данных на консоль.

Класс PrintStream представляет собой выходной поток, производный от класса OutputStream, и поэтому в нем также реализуется метод write() низкоуровневого вывода. Следовательно, этот метод может быть использован для вывода данных на консоль. Самая простая форма метода write(), определенного в PrintStream, имеет следующий вид:

Этот метод записывает в поток байтовое значение, указываемое в качестве параметра byteval. Несмотря на то что этот параметр объявлен как int, учитываются только 8 младших битов его значения. Ниже приведен простой пример программы, где метод write() используется для вывода символов S и новой строки на консоль.

// Применение метода System.out.write() .
class WriteDemo {
    public static void main(String args[])  {
        int b;

        b = 'S';
        // Вывод байтов на экран.
        System.out.write(b);
        System.out.write('n');
    }
}

На практике для вывода на консоль метод write.() применяется достаточно редко. Для этой цели намного удобнее пользоваться методами print() и println(). В классе PrintStream реализованы два дополнительных метода, printf() и format(), которые позволяют управлять форматом выводимых данных. Например, при выводе можно указать количество десятичных цифр, минимальную ширину поля или способ представления отрицательных числовых значений. И хотя эти методы не используются в примерах, представленных в данной книге, вам стоит обратить на них пристальное внимание, поскольку они могут оказаться очень полезными при написании прикладных программ.

Чтение и запись в файлы из байтовых потоков

В Java предоставляется большое количество классов и методов, позволяющих читать и записывать данные в файлы. Разумеется, чаще всего приходится обращаться к файлам, хранящимся на дисках. В Java все файлы имеют байтовую организацию, и поэтому для побайтового чтения и записи данных в такие файлы предоставляются соответствующие методы. Следовательно, организовывать чтение и запись данных в файлы из байтовых потоков приходится довольно часто. Кроме того, для байтовых потоков ввода-вывода в файлы в Java разрешено создавать специальные оболочки в виде символьных объектов. Такие оболочки будут рассмотрены далее в этой главе.

Для того чтобы создать байтовый поток и связать его с файлом, следует воспользоваться классом FilelnputStream или FileOutputStream. А для открытия файла достаточно создать объект одного из этих классов, передав имя файла конструктору в качестве параметра. В открытый файл можно записывать данные или читать их из него.

Ввод данных из файла

Файл открывается для ввода созданием объекта типа FilelnputStream. Для этой цели чаще всего используется приведенная ниже форма объявления конструктора данного класса. FilelnputStream(String имя_файла) throws FileNotFoundException

В качестве параметра этому конструктору передается имя_файла, который требуется открыть. Если указанный файл не существует, генерируется исключениеFileNotFoundException.

Для чтения данных из файла служит метод read(). Ниже приведена форма объявления этого метода, которой мы будем пользоваться в дальнейшем,

int read() throws IOException

При каждом вызове метод read() читает байт из файла и возвращает его как целочисленное значение. По достижении конца файла этот метод возвращает значение -1. При возникновении ошибки метод генерирует исключение IOException. Как видите, в этой форме метод read() выполняет те же самые действия, что и одноименный метод для ввода данных с консоли.

Завершив операции с файлом, следует закрыть его с помощью метода close(), общая форма объявления которого выглядит следующим образом:

void close() throws IOException

При закрытии файла освобождаются связанные с ним системные ресурсы, чтобы использовать их для работы с другим файлом. Если же файл не будет закрыт, могут произойти “утечки памяти” из-за того, что часть памяти остается выделенной для неиспользуемых ресурсов. Ниже приведен пример программы, где метод read() используется для ввода содержимого текстового файла. Имя файла задается с помощью параметра в командной строке при запуске программы на выполнение. Полученные данные выводятся на экран. Обратите внимание на то, что ошибки ввода-вывода обрабатываются с помощью блока try/catch.

/* Отображение текстового файла.
При вызове этой программы следует указать имя файла,
содержимое которого требуется просмотреть.
Например, для вывода на экран содержимого файла TEST.TXT,
в командной строке нужно указать следующее:
java ShowFile TEST.TXT
*/
import java.io.*;
class ShowFile {
public static void main(String args[])
{
int i;
FilelnputStream fin;
// Прежде всего следует убедиться, что файл был указан,
if(args.length != 1) {
System.out.println("Usage: ShowFile File");
return;
}
try {
// Открытие файла.
fin = new FilelnputStream(args[0]);
} catch(FileNotFoundException exc) {
System.out.println("File Not Found");
return;
}
try {
// читать из файла до тех пор, пока не встретится знак EOF.
do {
// Чтение из файла.
i = fin.read();
if(i != -1) System.out.print((char) i) ;
// Если значение переменной i равно -1,значит,
// достингут конец файла.
} while (i != -1);
} catch(IOException exc) {
System.out.println("Error reading file.");
}
try {
// Закрытие файла.
fin.close();
} catch(IOException exc) {
System.out.println("Error closing file.");
}
}
}

В приведенном выше примере поток ввода из файла закрывается после того, как чтение данных из файла завершается в блоке try. Такой способ оказывается удобным не всегда, и поэтому в Java предоставляется более совершенный и чаще употребляемый способ. А состоит он в вызове метода close() в блоке finally. В этом случае все методы, получающие доступ к файлу, помещаются в блок try, а для закрытия файла используется блок finally. Благодаря этому файл закрывается независимого от того, как завершится блок try. Если продолжить предыдущий пример, то блок try, в котором выполняется чтение из файла, можно переписать следующим образом:

try {
  do {
      i = fin.read();
      if(i != -1) System.out.print((char) i) ;
  } while(i != —1) ;
} catch(IOException exc) {
  System.out.println("Error Reading File");
  // Блок finally используется для закрытия файла.
} finally {
  // закрыть файл при выходе из блока try.
  try {
    fin.close();
  } catch(IOException exc) {
    System.out.println("Error Closing File");
  }
}

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

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

/* В этой версии программы отображения текстового файла код,
открывающий файл и получающий к нему доступ, заключается
в единственный блок try. А закрывается файл в блоке finally.
*/
import java.io.*;
class ShowFile {
  public static void main(String args[])
  {
    int i;
    FilelnputStream fin = null;

    // Прежде всего следует убедиться, что файл был указан,
    if (args.length != 1)   {
      System.out.println("Usage: ShowFile filename");
      return;
    }

    // В следующем коде открывается файл, из которого читаются
    // символы до тех пор, пока не встретится знак EOF, а затем
    // файл закрывается в блоке finally,
    try {
      fin = new FilelnputStream(args[0]);
      do {
        i = fin.read() ;
        if(i != -1) System.out.print((char) i);
      } while(i != -1);
    } catch(FileNotFoundException exc) {
      System.out.println("File Not Found.");
    } catch(IOException exc) {
      System.out.println("An I/O Error Occurred");
    } finally {
      // Файл закрывается в любом случае,
      try {
        if (fin != null) fin.closeO;
      } catch(IOException exc) {
        System.out.println("Error Closing File");
      }
    }
  }
}

Обратите внимание на то, что переменная fin инициализируется пустым значением null. А в блоке finally файл закрывается только в том случае, если значение переменной fin не является пустым. Такой способ оказывается вполне работоспособным, поскольку переменная fin не будет содержать пустое значение лишь в том случае, если файл был успешно открыт. Следовательно, метод close() не будет вызываться, если во время открытия файла возникнет исключение.

В приведенном выше примере блок try/catch можно сделать более компактным. Ведь исключение FileNotFoundException является подклассом исключения IOException, и поэтому его не нужно перехватывать отдельно. В качестве примера ниже приведен блок оператора catch, которым можно воспользоваться для перехвата обоих этих исключений, не прибегая к перехвату исключения FileNotFoundException в отдельности. В данном случае выводится стандартное сообщение о возникшем исключении с описанием характера ошибки.

} catch(IOException exc) {
  System.out.println("I/O Error: " + exc);
} finally {
...
В рассматриваемом здесь способе любая ошибка, в том числе и ошибка открытия файла, будет обработана единственным оператором catch. Благодаря своей компактности именно такой способ применяется в большинстве примеров ввода-вывода, представленных в этой книге. Следует, однако, иметь в виду, что он может оказаться не вполне пригодным в тех случаях, когда требуется отдельно обрабатывать ошибку открытия файла, например, вследствие того, что пользователь введет имя файла с опечаткой. В подобных случаях рекомендуется выдать сначала приглашение правильно ввести имя файла, а затем перейти к блоку try для доступа к файлу.

### Вывод в файл
Для того чтобы открыть файл для вывода, следует создать объект типа FileOutputStream. Ниже приведены два наиболее часто употребляемых конструктора этого класса.

FileOutputStream(String имя_файла) throws FileNotFoundException
FileOutputStream(String имя_файлаг boolean append)
throws FileNotFoundException

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

Для того чтобы записать данные в файл, следует вызвать метод write(). Наиболее простая форма этого метода приведена ниже,

void write(int byteval) throws IOException

Этот метод записывает в поток байтовое значение, указанное в качестве параметра byteval. Несмотря на то что этот параметр объявлен как int, учитываются только 8 младших битов его значения. Если в процессе записи возникнет ошибка, будет сгенерировано исключение IOException.

По завершении работы с файлом его нужно закрыть с помощью метода close(). Объявление этого метода выглядит следующим образом:

void close() throws IOException

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

В приведенном ниже примере программы осуществляется копирование текстового файла. Имена исходного и целевого файлов указываются в командной строке.

/* Копирование текстового файла.
При вызове этой программы следует указать имя исходного
и целевого файлов. Например, для копирования файла FIRST.TXT
в файл SECOND.TXT в командной строке нужно указать следующее:
java CopyFile FIRST.TXT SECOND.TXT
/
import java.io.
;
class CopyFile {
public static void main(String args[])
{
int i;
FilelnputStream fin;
FileOutputStream fout;

// Прежде всего следует убедиться, что оба файла были указаны,
if(args.length !=2 ) {
  System.out.println("Usage: CopyFile From To");
  return;
}

// открыть исходный файл
try {
  fin = new FilelnputStream(args[0] ) ;
} catch(FileNotFoundException exc) {
  System.out.println("Input File Not Found");
  return;
}

// открыть целевой файл
try {
  fout = new FileOutputStream(args[1]);
} catch(FileNotFoundException exc) {
  System.out.println("Error Opening Output File");
  // закрыть исходный файл
  try {
    fin.close();
  } catch(IOException exc2) {
    System.out.println("Error closing input file.");
  }
  return;
}
// копировать файл

try {
  do {
    // Чтение байтов из одного файла и запись их в другой файл.
    i = fin.read();
    if(i != -1) fout.write (i);
  } while(i != -1);
} catch(IOException exc) {
  System.out.println("File Error");
}
try {
  fin.close() ;
} catch(IOException exc) {
  System.out.println("Error closing input file.");
}
try {
  fout.close();
} catch(IOException exc) {
  System.out.println("Error closing output file.");
}

}
}


## Автоматическое закрытие файлов
В примерах программ из предыдущего раздела метод с 1 о s е () вызывался явным образом для закрытия файла, когда он уже не был больше нужен. Подобным образом файлы закрывались с тех пор, как появилась первая версия Java. В итоге именно такой способ получил широкое распространение в существующих программах на Java. И до сих пор он остается вполне обоснованным и пригодным. Но в JDK 7 внедрено новое средство, предоставляющее другой, более рациональный способ управления ресурсами, в том числе и потоками файлового ввода-вывода, автоматизирующий процесс закрытия файлов. Этот способ основывается на новой разновидности оператора try, называемой оператором try с ресурсами, а иногда еще — автоматическим управлением ресурсами. Главное преимущество оператора try с ресурсами заключается в том, что он предотвращает ситуации, в которых файл (или другой ресурс) неумышленно остается неосвобожденным после того, как он уже больше не нужен. Как пояснялось ранее, если не позаботиться вовремя о закрытии файла в программе, это может привести к утечкам памяти и прочим осложнениям в работе программы.

Ниже приведена общая форма оператора try с ресурсами

try (описание_ресурса) {
// использовать ресурс
}

где описание_ресурса обозначает оператор, в котором объявляется и инициализируется конкретный ресурс, например файл. По существу, он содержит объявление переменной, в котором переменная инициализируется ссылкой на объект управляемого ресурса. По завершении блока try объявленный ресурс автоматически освобождается. Если этим ресурсом является файл, то он автоматически закрывается, что избавляет от необходимости вызывать метод close() явным образом. В блок оператора try с ресурсами могут также входить операторы catch и finally.

Оператор try с ресурсами можно применять только к тем ресурсам, в которых реализуется интерфейс AutoCloseable, определенный в пакете java. lang. Этот интерфейс внедрен в JDK 7, и в нем определен метод close(). Интерфейс AutoCloseable наследует от интерфейса Close able, определенного в пакете j ava. io. Оба интерфейса реализуются классами потоков, в том числе FilelnputStream и FileOutputStream. Следовательно, оператор try с ресурсами может применяться вместе с потоками, включая и потоки файлового ввода-вывода.

В качестве примера ниже приведена переделанная версия программы ShowFile, в которой оператор try с ресурсами применяется для автоматического закрытия файла.

/* В этой версии программы ShowFile оператор try с ресурсами
применяется для автоматического закрытия файла, когда он
уже больше не нужен.
Примечание: для компиляции этого кода требуется JDK 7 или
более поздняя версия данного комплекта.
/
import java.io.
;
class ShowFile {
public static void main(String args[])
{
int i;
// Прежде всего следует убедиться, что оба файла были указаны,
if(args.length != 1) {
System.out.println(«Usage: ShowFile filename»);
return;
}

// Ниже оператор try с ресурсами применяется сначала для открытия, а
// затем для автоматического закрытия файла после выхода из блока try.
try(FilelnputStream fin = new FilelnputStream(args[0])) {
  // Блок оператора try с ресурсами,
  do {
    i = fin.read();
    if (i != -1) System.out.print((char) i) ;
  } while(i != -1);

} catch(IOException exc) {
  System.out.println("I/O Error: " + exc);
}

}
}

Особое внимание в данной программе обращает на себя следующая строка кода, в которой файл открывается в операторе try с ресурсами.

try(FilelnputStream fin = new FilelnputStream(args[0])) {

Как видите, в той части оператора try с ресурсами, где указывается конкретный ресурс, объявляется переменная fin типа FilelnputStream, которой затем присваивается ссылка на файл как объект, открываемый конструктором класса FilelnputStream. Следовательно, в данной версии программы переменная fin является локальной для блока try и создается при входе в этот блок. А при выходе из блока try файл, связанный с переменной fin, автоматически закрывается с помощью неявно вызываемого метода close(). Это означает, что метод close() не нужно вызывать явным образом, а следовательно, он избавляет от необходимости помнить, что файл нужно закрыть. Именно в этом и заключается главное преимущество автоматического управления ресурсами.

Следует иметь в виду, что ресурс, объявляемый в операторе try с ресурсами, неявно считается как final. Это означает, что ресурс нельзя присвоить после того, как он был создан. Кроме того, область действия ресурса ограничивается блоком оператора try с ресурсами.

С помощью одного оператора try с ресурсами можно управлять несколькими ресурсами. Для этого достаточно указать каждый из них через точку с запятой. В качестве примера ниже приведена переделанная версия рассмотренной ранее программы CopyFile. В этой версии оператор с ресурсами используется для управления переменными fin и fout, ссылающимися на два ресурса (в данном случае — оригинал и копию файла).

/* В этой версии программы CopyFile используется оператор try с
ресурсами. В ней демонстрируется управление двумя ресурсами
(в данном случае — файлами) с помощью единственного оператора try.

Примечание: для компиляции этого кода требуется JDK 7 или
более поздняя версия данного комплекта.
/
import java.io.
;
class CopyFile {
public static void main.(String args[] ) throws IOException
{
int i;
// Прежде всего следует убедиться, что оба файла были указаны,
if(args.length != 2) {
System.out.println(«Usage: CopyFile from to»);
return;
}

// открыть оба файла для управления с помощью оператора try
try (FilelnputStream fin = new FilelnputStream(args[0]);
     FileOutputStream fout = new FileOutputStream(args[1]))
     // Управление двумя ресурсами (в данном случае — файлами).
{
do {
    i = fin.read();
    if(i != -1) fout.write(i);
  } whiled ! = -1) ;
} catch(IOException exc) {
  System.out.println("I/O Error: " + exc);
}

}
}
Обратите внимание на то, каким образом входной и выходной файлы открываются в операторе try с ресурсами, как показано ниже.

try (FilelnputStream fin = new FilelnputStream(args[0]);
     FileOutputStream fout = new FileOutputStream(args[1]))
{

По завершении этого блока try оба файла, на которые ссылаются переменные fin и fout, закрываются автоматически. Если сравнить эту версию программы с предыдущей, то можно заметить, что ее исходный код намного компактнее. Возможность писать более компактный код является еще одним, дополнительным преимуществом оператора try с ресурсами.

Следует также упомянуть о еще одной особенности оператора try с ресурсами. Вообще говоря, когда выполняется блок try, в нем может возникнуть одно исключение, приводящее к другому исключению при закрытии ресурса в блоке finally. И если это блок обычного оператора try, то исходное исключение теряется, прерываясь вторым исключением. А в блоке оператора try с ресурсами второе исключение подавляется. Но оно не теряется, а добавляется в список подавленных исключений, связанных с первым исключением. Этот список можно получить, вызвав метод get Suppressed(), определенный в классе Throwable.

В силу упомянутых выше преимуществ, присущих оператору try с ресурсами, можно ожидать, что он найдет широкое применение в программировании на Java. Поэтому именно он и будет использоваться в остальных примерах программ, представленных далее в этой главе. Но не менее важным остается и умение пользоваться рассмотренным ранее традиционным способом освобождения ресурсов с помощью вызываемого явным образом оператора close(). И на то имеется ряд веских оснований. Во-первых, уже существует немало написанных и повсеместно эксплуатируемых программ на Java, в которых применяется традиционный способ управления ресурсами. Поэтому все программирующие на Java должны как следует усвоить и уметь пользоваться этим традиционным способом для сопровождения устаревшего кода. Во-вторых, переход на JDK 7 может произойти не сразу, а следовательно, придется работать с предыдущей версией данного комплекта. В этом случае воспользоваться преимуществами оператора try с ресурсами не удастся и придется применять традиционный способ управления ресурсами. И наконец, в некоторых классах закрытие ресурса явным образом может оказаться более пригодным, чем его автоматическое освобождение. Но, несмотря на все сказанное выше, новый способ автоматического управления ресурсами считается более предпочтительным при переходе к JDK 7 или более поздней версии данного комплекта, поскольку он рациональнее и надежнее традиционного способа.

Чтение и запись двоичных данных

В приведенных до сих пор примерах программ читались и записывались байтовые значения, содержащие символы в коде ASCII. Но аналогичным образом можно также организовать чтение и запись любых типов данных. Допустим, требуется создать файл, содержащий значения типа int, double или short. Для чтения и записи простых типов данных в Java предусмотрены классы DatalnputStream и DataOutputStream.

Класс DataOutputStream реализует интерфейс DataOutput, в котором определены методы, позволяющие записывать в файл значения любых простых типов. Следует, однако, иметь в виду, что данные записываются во внутреннем двоичном формате, а не в виде последовательности символов. Методы, наиболее часто применяемые для записи простых типов данных в Java, приведены в табл. 10.5. Каждый из них генерирует исключение IOException при возникновении ошибки ввода-вывода.

Таблица 10.5. Наиболее часто употребляемые методы вывода данных, определенные в классе DataOutputStream

Метод вывода данных Описание
void writeBoolean (boolean val) Записывает логическое значение, определяемое параметром val
void writeByte (int,val) Записывает младший байт целочисленного значения, определяемого параметром val
void writeChar (int,val) Записывает значение, определяемое параметром val, интерпретируя его как символ
void writeDouble (double val) Записывает значение типа double, определяемое параметром val
void writeFloat (float val) Записывает значение типа float, определяемое параметром val
void writelnt(int val) Записывает значение типа int, определяемое параметром val
void writeLong (long val) Записывает значение типа long, определяемое параметром val
void writeShort (int val) Записывает целочисленное значение, определяемое параметром val, преобразуя его в тип short

Ниже приведен конструктор класса DataOutputStream. Обратите внимание на то, что при вызове ему передается экземпляр класса OutputStream.

DataOutputStream(OutputStream OutputStream)

где OutputStream — это поток вывода, в который записываются данные. Для того чтобы организовать запись данных в файл, следует передать конструктору в качестве параметра OutputStream объект типа FileOutputStream.

Класс DatalnputStream реализует интерфейс Datalnput, в котором объявлены методы для чтения всех простых типов данных в Java (табл. 10.6). В каждом из этих методов может быть сгенерировано исключение IOException при возникновении ошибки ввода-вывода. В качестве своего основания класс DatalnputStream использует экземпляр класса InputStream, перекрывая его методами для чтения различных типов данных в Java. Однако в потоке типа DatalnputStream данные читаются в двоичном виде, а не в удобной для чтения форме. Ниже приведен конструктор класса DatalnputStream.

DatalnputStream(InputStream inputStream)

где inputStream — это поток, связанный с создаваемым экземпляром класса DatalnputStream. Для того чтобы организовать чтение данных из файла, следует передать конструктору в качестве параметра inputStream объект типа FilelnputStream.

Таблица 10.6. Наиболее часто употребляемые методы ввода данных, определенные в классе DatalnputStream

Метод ввода данных Описание
boolean readBoolean() Читает значение типа boolean
byte readByte() Читает значение типа byte
char readChar() Читает значение типа char
double readDouble() Читает значение типа double
float readFloat() Читает значение типа float
int readlnt() Читает значение типа int
long readLong() Читает значение типа long
short readShort() Читает значение типа short

Ниже приведен пример программы, демонстрирующий применение классов DataOutputStream и DatalnputStream. В этой программе данные разных типов сначала записываются в файл, а затем читаются из файла.

// Запись и чтение двоичных данных.Для компиляции этого кода
// требуется JDK 7 или более поздняя версия данного комплекта.

import java.io.*;

class RWData {
  public static void main(String args[])
  {
    int i = 10;
    double d = 1023.56;
    boolean b = true;

    // записать ряд значений
    try (DataOutputStream dataOut =
          new DataOutputStream(new FileOutputStream("testdata")))
    {
      // Запись двоичных данных в файл testdata.
      System.out.println("Writing " + i) ;
      dataOut.writelnt(i);

      System.out.println("Writing " + d) ;
      dataOut.writeDouble(d);

      System.out.println("Writing " + b);
      dataOut.writeBoolean(b);

      System.out.println("Writing " + 12.2 * 7.4);
      dataOut.writeDouble(12.2 * 7.4);
    }
    catch(IOException exc) {
      System.out.println("Write error.");
      return;
    }

    System.out.println() ;

    // а теперь прочитать записанные значения
    try (DatalnputStream dataln =
          new DatalnputStream(new FilelnputStream("testdata")))
    {
      // Чтение двоичных данных из файла testdata.
      i = dataln.readlnt();
      System.out.println("Reading " + i) ;

      d = dataln.readDouble();
      System.out.println("Reading " + d);

      b = dataln.readBoolean() ;
      System.out.println("Reading " + b);

      d = dataln.readDouble();
      System.out.println("Reading " + d) ;
    }
    catch(IOException exc) {
      System.out.println("Read error.");
    }
  }
}

Выполнение этой программы дает следующий результат:

Writing 10
Writing 1023.56
Writing true
Writing 90.28

Reading 10
Reading 1023.56
Reading true
Reading 90.28

Пример для опробования 10.1.
Утилита сравнения файлов

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

Последовательность действий

  1. Создайте файл CompFiles.java.
  2. Введите в файл CompFiles.java приведенный ниже исходный код.
/*
Для того чтобы воспользоваться этой программой, укажите
имена сравниваемых файлов в командной строке, например:
java CompFile FIRST.TXT SECOND.TXT
Для компиляции этого кода требуется JDK 7
или более поздняя версия данного комплекта.
*/
import java.io.*;
class CompFiles {
  public static void main(String args[])
  {
    int i=0, j=0;

    // Прежде всего следует убедиться, что файлы были указаны,
    if(args.length !=2 )    {
      System.out.println("Usage: CompFiles fl f2");
      return;
    }

    // сравнить файлы
    try (FilelnputStream fl = new FilelnputStream(args[0]);
         FilelnputStream f2 = new FilelnputStream(args[1]))
    {
      // проверить содержимое каждого файла
      do {
        i = f1.read();
        j = f2.read();
        if(i != j) break;
      }   while (i != -1 && j != -1) ;

      if(i != j)
        System.out.println("Files differ.");
      else
        System.out.println("Files are the same.");
    } catch(IOException exc) {
      System.out.println("I/O Error: " + exc);
    }
  }
}
  1. Для опробования программы скопируйте сначала файл CompFiles. java во временный файл temp, а затем введите в командной строке следующее:
java CompFiles CompFiles.java temp
  1. Программа сообщит, что файлы одинаковы. Далее сравните файл CompFiles.java с рассмотренным ранее файлом CopyFile. j ava, введя в командной строке следующее:
java CompFiles CompFiles.java CopyFile.java
  1. Эти файлы содержат разные данные, о чем и сообщит программа CompFiles.
  2. Попробуйте самостоятельно внедрить в программу CompFiles ряд дополнительных возможностей. В частности, введите в нее возможность выполнять сравнение без учета регистра символов. Программу CompFiles можно также доработать таким образом, чтобы она выводила место, где обнаружено первое отличие сравниваемых файлов.

Файлы с произвольным доступом

До сих пор нам приходилось иметь дело с последовательными файлами, содержимое которых вводилось и выводилось побайтно, т.е. строго по порядку. Но в Java предоставляется также возможность обращаться к хранящимся в файле данным в произвольном порядке. Для этой цели предусмотрен класс RandomAccessFile, инкапсулирующий файл с произвольным доступом. Класс RandomAccessFile не является производным от класса InputStream или OutputStream. Вместо этого он реализует интерфейсы Datalnput и DataOutput, в которых объявлены основные методы ввода-вывода. В нем поддерживаются также запросы позиционирования, т.е. возможность задавать положение указателя файла произвольным образом. Ниже приведен конструктор класса RandomAccessFile, которым мы будем пользоваться далее.

RandomAccessFile(String имя_файла, String доступ)
  throws FileNotFoundException

Здесь конкретный файл указывается с помощью параметра имя_файла, а параметр доступ определяет, какой именно тип доступа будет использоваться для обращения к файлу. Если параметр доступ принимает значение «г», то данные могут читаться из файла, но не записываться в него. Если же указан тип доступа «rw», то файл открывается как для чтения, так и для записи.

Метод seek(), общая форма объявления которого приведена ниже, служит для установки текущего положения указателя файла,

void seek(long newPos) throws IOException

Здесь параметр newPos определяет новое положение указателя файла в байтах относительно начала файла. Операция чтения или записи, следующая после вызова метода seek(), будет выполняться относительно нового положения указателя файла.

В классе RandomAccessFile определены методы read() и write(). Этот класс также реализует интерфейсы Datalnput и DataOuput, т.е. в нем доступны методы чтения и записи простых типов, например readlnt() и writeDouble().

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

// Демонстрация произвольного доступа к файлам.
// Для компиляции этого кода требуется JDK 7
// или более поздняя версия данного комплекта.
import java.io.*;
class RandomAccessDemo {
  public static void main(String args[])
  {
    double data[] = { 19.4, 10.1, 123.54, 33.0, 87.9, 74.25 };
    double d;

    // открыть и использовать файл с произвольным доступом
    // Файл с произвольным доступом открывается для записи и чтения.
    try (RandomAccessFile raf = new RandomAccessFile("random.dat", "rw"))
    {
      // записать значения в Файл
      for(int i=0; i < data.length; i++)  {
        raf.writeDouble(data[i]);
      }

      //а теперь прочитать отдельные значения из файла
      // Для установки указателя файла служит метод seek().
      raf.seek(0); // найти первое значение типа double
      d = raf.readDouble();
      System.out.println("First value is " + d) ;

      raf.seek(8); // найти второе значение типа double
      d = raf.readDouble();
      System.out.println("Second value is " + d) ;

      raf.seek(8 * 3); // найти четвертое значение типа double
      d = raf.readDouble();
      System.out.println("Fourth value is " + d);

      System.out.println();

      // а теперь прочитать значения через одно
      System.out.println("Here is every other value: ");
      for(int i=0; i < data.length; i+=2) {
        raf.seek(8 * i); // найти i-e значение типа double
          d = raf.readDouble();
        System.out.print(d + " ") ;
      }
    }
    catch(IOException exc) {
      System.out.println("I/O Error: " + exc) ;
    }
  }
}

Результат выполнения данной программы выглядит следующим образом:

First value is 19.4
Second value is 10.1
Fourth value is 33.0

Here is every other value:
19.4 123.54 87.9

Обратите внимание на расположение каждого числового значения. Ведь значение типа double занимает 8 байтов, и поэтому каждое последующее число начинается на 8-байтовой границе предыдущего числа. Иными словами, первое числовое значение начинается на позиции нулевого байта, второе — на позиции 8-го байта, третье — на позиции 16-го байта и т.д. Поэтому для чтения четвертого числового значения нужно установить указатель файла на позиции 24-го байта при вызове метода seek().

Применение символьных потоков в Java

Как следует из предыдущих разделов этой главы, байтовые потоки в Java довольно эффективны и удобны в употреблении. Но что касается ввода-вывода символов, то байтовые потоки далеки от идеала. Поэтому для этих целей в Java определены классы символьных потоков. На вершине иерархии классов, поддерживающих символьные потоки, находятся абстрактные классы Reader и Writer. Методы класса Reader приведены в табл. 10.7, а методы класса Writer — в табл. 10.8. В большинстве этих методов может быть сгенерировано исключение IOException. Методы, определенные в указанных абстрактных классах Reader и Writer, доступны во всех их подклассах. Таким образом, они образуют минимальный набор функций ввода-вывода, необходимых для всех символьных потоков.

Таблица 10.7. Методы, определенные в классе Reader

Метод Описание
abstract void close() Закрывает поток ввода данных. При последующей попытке чтения генерируется исключение IOException
void mark (int numChars) Ставит отметку на текущей позиции в потоке. Отметка доступна до тех пор, пока на будет прочитано количество символов, определяемое параметром numChars
boolean markSupported() Возвращает логическое значение true, если поток поддерживает методы mark() и reset()
int read() Возвращает целочисленное представление очередного символа из потока ввода. Если достигнут конец потока, возвращается значение -1
int read(char buffer[]) Предпринимает попытку прочитать количество байтов, определяемое выражением buffer, length, в массив buffer и возвращает фактическое количество успешно прочитанных символов. Если достигнут конец потока, возвращается значение -1
abstract int read(char buffer[], int offset, int numChars) Предпринимает попытку прочитать количество символов, определяемое параметром numChars, в массив buffer, начиная с элемента buffer [ offset]. Если достигнут конец потока, возвращается значение -1
int read(CharBuffer buffer) Предпринимает попытку заполнить буфер, определяемый параметром buffer, символами, прочитанными из входного потока. Если достигнут конец потока, возвращается значение -1. CharBuffer — это класс, представляющий последовательность символов, например строку
boolean ready() Возвращает логическое значение true, если следующий запрос на получение символа может быть выполнен немедленно. В противном случае возвращается логическое значение false
void reset() Устанавливает указатель ввода на помеченной ранее позиции
long skip(long numChars) Пропускает количество символов, определяемое параметром numChars, в потоке ввода. Возвращает фактическое количество пропущенных символов

Таблица 10.8. Методы, определенные в классе Writer

Метод Описание
Writer append(char ch) Записывает символ ch в конец текущего потока. Возвращает ссылку на поток
Writer append(CharSequence chars) Записывает символы chars в конец текущего потока. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов
Writer append(CharSequence chars, int begin, int end) Записывает символы chars в конец текущего потока, начинаяс позиции, определяемой параметром begin, и кончая позицией, определяемой параметром end. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов
abstract void close() Закрывает поток вывода. При последующей попытке записи в поток генерируется исключение IOException
abstract void flush() Выводит текущее содержимое буфера на устройство. В результате выполнения данной операции буфер очищается
void write(int ch) Записывает в вызывающий поток вывода один символ. Параметр ch относится к типу int, что позволяет вызывать данный метод в выражениях, не приводя результат их вычисления к типу char
void write(char buffer[]) Записывает в вызывающий поток вывода массив символов buffer
abstract void write(char buffer[], int offset, int numChars) Записывает в вызывающий поток вывода количество символов, определяемое параметром numChars, из массива buffer, начиная с элемента buffer[ offset ]
void write(String str) Записывает в вызывающий поток вывода символьную строку str
void write(String str, int offset, int numChars) Записывает в вызывающий поток вывода часть numChars символов из строки str, начиная с позиции, обозначаемой параметром offset

Консольный ввод из символьных потоков

Если программа подлежит локализации, то при организации ввода с консоли символьным потокам следует отдать предпочтение перед байтовыми. А поскольку System.in — это байтовый поток, то для него придется построить оболочку в виде класса, производного от класса Reader. Наиболее подходящим для ввода с консоли является класс Buf feredReader, поддерживающий буферизованный поток ввода. Но объект типа Buf feredReader нельзя построить непосредственно из потока стандартного ввода System, in. Сначала нужно преобразовать байтовый поток в символьный. И для этой цели служит класс InputStreamReader, преобразующий байты в символы. Для того чтобы получить объект типа InputStreamReader, связанный с потоком стандартного ввода System, in, нужно воспользоваться следующим конструктором:

InputStreamReader(InputStream inputStream)

Поток ввода System.in является экземпляром класса InputStream, и поэтому его можно указать в качестве параметра inputStream данного конструктора.

Затем на основании объекта типа InputStreamReader можно создать объект типа BufferedReader, используя следующий конструктор:

BufferedReader(Reader inputReader)

где inputReader — это поток, который связывается с создаваемым экземпляром класса Buf feredReader. Объединяя обращения к указанным выше конструкторам в одну операцию, мы получаем приведенную ниже строку кода. В ней создается объект типа BufferedReader, связанный с клавиатурой.

BufferedReader br = new BufferedReader(new
                    InputStreamReader(System.in));

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

Чтение символов

Прочитать символы из потока ввода System, in можно с помощью метода read(), определенного в классе Buf f eredReader. Чтение символов мало чем отличается от чтения данных из байтовых потоков. Ниже приведены общие формы объявления трех вариантов метода read(), предусмотренных в классе Buf f eredReader.

int read() throws IOException
int read(char data[]) throws IOException
int read(char data[], int start, int max) throws IOException

В первом варианте метод read() читает один символ в уникоде. По достижении конца потока этот метод возвращает значение -1. Во втором варианте метод read() читает данные из потока ввода и помещает их в массив. Чтение оканчивается по достижении конца потока, по заполнении массива data символами или при возникновении ошибки. В этом случае метод возвращает число прочитанных символов, а если достигнут конец потока, — значение -1. В третьем варианте метод read() помещает прочитанные символы в массив data, начиная с элемента, определяемого индексом start. Максимальное число символов, которые могут быть записаны в массив, определяется параметром max. В данном случае метод возвращает число прочитанных символов или значение -1, если достигнут конец потока. При возникновении ошибки в каждом из перечисленных выше вариантов метода read() генерируется исключение IOException. При чтении данных из потока ввода System, in конец потока устанавливается нажатием клавиши < Enter>.

Ниже приведен пример программы, демонстрирующий применение метода read() для чтения символов с консоли. Символы читаются до тех пор, пока пользователь не введет точку. Следует иметь в виду, что исключения, которые могут быть сгенерированы при выполнении данной программы, обрабатываются за пределами метода main(). Как пояснялось выше, подобный подход характерен для обработки ошибок при чтении данных с консоли. По желанию вы можете употребить другой механизм обработки ошибок.

// Применение класса BufferedReader для чтения символов с консоли,
import java.io.*;

class ReadChars {
  public static void main(String args[])
    throws IOException
  {
    char c;
    // Создание объекта типа BufferedReader, связанного
    // с потоком стандартного ввода System.in.
    BufferedReader br = new
      BufferedReader(new
        InputStreamReader'(System. in) ) ;

    System.out.println("Enter characters, period to quit.");

    // читать символы
    do {
      с = (char) br.read();
      System.out.println(c) ;
    } while(c != '.');
  }
}

Результат выполнения данной программы выглядит следующим образом:

Enter characters, period to quit.
One Two.
O
n
e
T
w
о

Чтение символьных строк

Для ввода символьной строки с клавиатуры следует воспользоваться методом readLine() из класса Buf feredReader. Ниже приведена общая форма объявления этого метода.

String readLine() throws IOException

Этот метод возвращает объект типа String, содержащий прочитанные символы. При попытке прочитать символьную строку по окончании потока метод возвращает пустое знчение null.

Ниже приведен пример программы, демонстрирующий применение класса BufferedReader и метода readLine(). В этой программе текстовые строки читаются и отображаются до тех пор, пока не будет введено слово «stop».

// Чтение символьных строк с консоли средствами класса BufferedReader.
import java.io.*;
  class ReadLines {
    public static void main(String args[])
      throws IOException
    {
    // создать объект типа BufferedReader, связанный с потоком System.in
    BufferedReader br = new BufferedReader(new
                        InputStreamReader(System.in));

    String str;

    System.out.println("Enter lines of text.");
    System.out.println("Enter 'stop' to quit.");
    do {
      // использовать метод readLine() из класса BufferedReader
      // для чтения текстовой строки
      str = br.readLine();
      System.out.println(str) ;
    } while(!str.equals("stop")) ;
  }
}

Консольный вывод в символьные потоки

Несмотря на то что поток стандартного вывода System, out вполне может использоваться для вывода на консоль, такой подход скорее пригоден для целей отладки или при создании очень простых программ, подобных тем, которые приводятся в качестве примеров в этой книге. Для реальных прикладных программ на Java вывод на консоль обычно организуется через поток PrintWriter, относящийся к одному из классов, представляющих символьные потоки. Как упоминалось ранее, применение символьных потоков упрощает локализацию прикладных программ.

В классе PrintWriter определен ряд конструкторов. Далее будет использоваться следующий конструктор:

PrintWriter(OutputStream OutputStream, boolean flushOnNewline)

где в качестве первого параметра OutputStream конструктору передается объект типа OutputStream, а второй параметр f lushOnNewline указывает, должен ли производиться вывод данных из буфера в поток вывода при каждом вызове метода println(). Если параметр f lushOnNewline принимает логическое значение true, данные выводятся из буфера автоматически.

В классе PrintWriter поддерживаются методы print() и println() для всех типов, включая Object. Следовательно, методы print() и println() можно использовать точно так же, как и вместе с потоком вывода System, out. Если значение аргумента не относится к простому типу, то методы из класса PrintWriter вызывают метод toString() для объекта, указываемого в качестве параметра, а затем выводят результат.

Для вывода данных на консоль через поток типа PrintWriter следует указать System.out B качестве потока вывода и обеспечить вывод данных из буфера после каждого вызова метода println(). Например, при выполнении следующей строки кода создается объект типа PrintWriter, связанный с консолью:

PrintWriter pw = new PrintWriter(System.out, true);

Ниже приведен пример прикладной программы, демонстрирующий применение класса PrintWriter для организации вывода на консоль.

// Применение класса PrintWriter.
import java.io.*;
public class PrintWriterDemo {
  public static void main(String args[]) {
    // Создание объекта типа PrintWriter, связанного
    // с потоком стандартного вывода System.out.
    PrintWriter pw = new PrintWriter(System.out, true);
    int i = 10;
    double d = 123.65;

    pw.println("Using a PrintWriter.");
    pw.println(i);
    pw.println(d);

    pw.println(i + " + " + d + " is " + (i+d));
  }
}

Выполнение этой программы дает следующий результат:

Using a PrintWriter.
10
123.65
10 + 123.65 is  133.65

Несмотря на все удобство символьных потоков, не следует забывать, что для изучения языка Java или отладки программ можно вполне пользоваться и потоком вывода System, out. Но если в программе применяется поток PrintWriter, то ее проще локализировать. Для кратких примеров программ, представленных в этой книге, применение потока PrintWriter не имеет существенных преимуществ перед потоком System, out, поэтому в и последующих примерах для вывода на консоль будет использоваться поток System.out.

Ввод-вывод в файлы через символьные потоки

На практике чаще всего приходится обращаться с файлами, имеющими байтовую организацию, тем не менее, для этой цели можно пользоваться символьными потоками. Преимущество символьных потоков заключается в том, что они оперируют непосредственно символами в уникоде. Так, если требуется сохранить текст в уникоде, для этой цели лучше всего воспользоваться символьными потоками. Как правило, для файлового ввода-вывода символов служат классы FileReader и FileWriter.

Применение класса FileWriter

Класс FileWriter представляет поток, через который можно осуществлять запись данных в файл. Ниже приведены общие формы объявления двух наиболее часто употребляемых конструкторов данного класса.

FileWriter(String имя_файла) throws IOException
FileWriter(String имя_файла, boolean append) throws IOException

Здесь имя файла обозначает полный путь к файлу. Если параметр append принимает логическое значение true, данные записываются в конец файла, а иначе они перезаписывают прежние данные на том же месте в файле. При возникновении ошибки в каждом из указанных выше конструкторов генерируется исключение IOException. Класс FileWriter является производным от классов OutputStreamWriter и Writer. Следовательно, в нем доступны методы, объявленные в его суперклассах.

Ниже приведен краткий пример программы, демонстрирующий ввод текстовых строк с клавиатуры и последующий их вывод в файл test. txt. Набираемый текст читается до тех пор, пока пользователь не введет слово «stop». Для вывода текстовых строк в файл используется класс FileWriter.

// Простой пример утилиты ввода с клавиатуры и вывода данных
// на диск, демонстрирующий применение класса FileWriter.
// Для компиляции этого кода требуется JDK 7
// или более поздняя версия данного комплекта.
import java.io.*;
class KtoD {
  public static void main(String args[])
  {
    String str;
    BufferedReader br =
      new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("Enter text ('stop' to quit).");

    // Создание потока вывода типа FileWriter.
    try (FileWriter fw = new FileWriter("test.txt"))
    {
      do {
        System.out.print(": ");
        str = br.readLine();
        if(str.compareTo("stop") == 0) break;
        str = str + "rn"; // add newline
        // Запись текстовых строк в файл,
        fw.write(str);
      } while(str.compareTo("stop")   != 0) ;
    } catch(IOException exc) {
      System.out.println("I/O Error: " + exc);
    }
  }
}

Применение класса FileReader

В классе FileReader создается объект типа Reader, который можно использовать для чтения содержимого файла. Чаще всего употребляется такой конструктор этого класса:

FileReader(String имя_файла) throws FileNotFoundException

где имя файла обозначает полный путь к файлу. Если указанный файл не существует, генерируется исключение FileNotFoundException. Класс FileReader является производным от классов InputStreamReader и Reader. Следовательно, в нем доступны методы, объявленные в его суперклассах.

Приведенный ниже пример демонстрирует простую утилиту для отображения на экране содержимого текстового файла test. txt. Она является своего рода дополнением к утилите, рассмотренной в предыдущем разделе.

// Простая утилита ввода с дйска и вывода на экран,
// демонстрирующая применение класса FileReader.

// Для компиляции этого кода требуется JDK 7
// или более поздняя версия данного комплекта.
import java.io.*;

class DtoS {
  public static void main(String args[]) {
    String s;
    // Создание в классе BufferedReader оболочки с целью заключить
    // в нее класс FileReader и организовать чтение данных из файла.
    try (BufferedReader br = new BufferedReader(new FileReader("test.txt")))
    {
      while((s = br.readLine())   !=  null) {
        System.out.println(s) ;
      }
    } catch(IOException exc) {
      System.out.println("I/O Error: " + exc) ;
    }
  }
}

Обратите внимание на то, что для потока типа FileReader создана оболочка в классе BufferedReader. Благодаря этому появляется возможность обращаться к методу readLine(). Кроме того, закрытие потока типа Buf feredReader, на который в данном примере ссылается переменная br, автоматически приводит к закрытию файла.

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

Прежде чем завершить обсуждение средств ввода-вывода, необходимо рассмотреть еще один способ, помогающий читать числовые строки. Как известно, метод println() предоставляет удобные средства для вывода на консоль различных типов данных, в том числе целых чисел и чисел с плавающей точкой. Он автоматически преобразует числовые значения в удобную для чтения форму. Но в Java отсутствует метод, который читал бы числовые строки и преобразовывал бы их во внутреннюю двоичную форму. Например, не существует варианта метода read(), который читал бы числовую строку «100» и автоматически преобразовывал ее в целое число, пригодное для хранения в переменной типа int. Но для этой цели в Java имеются другие средства. И проще всего подобное преобразование осуществляется с помощью так называемых оболочек типов.

Оболочки типов в Java представляют собой классы, которые инкапсулируют простые типы. Оболочки типов необходимы, поскольку простые типы не являются объектами, что ограничивает их применение. Так, простой тип нельзя передать методу по ссылке. Для того чтобы исключить ненужные ограничения, в Java были предусмотрены классы, соответствующие каждому из простых типов.

Оболочками типов являются классы Double, Float, Long, Integer, Short, Byte, Character и Boolean. Эти классы предоставляют обширный ряд методов, позволяющих полностью интегрировать простые типы в иерархию объектов Java. Кроме того, в классах-оболочках числовых типов содержатся методы, предназначенные для преобразования числовых строк в соответствующие двоичные эквиваленты. Эти методы приведены ниже. Каждый из них возвращает двоичное значение, соответствующее числовой строке.

Оболочка типа Метод преобразования
Double static double parseDouble(String str) throws NumberFormatException
Float static float parseFloat(String str) throws NumberFormatException
Long static long parseLong(String str) throws NumberFormatException
Integer static int parselnt(String str) throws NumberFormatException
Short static short parseShort(String str) throws NumberFormatException
Byte static byte parseByte(String str) throws NumberFormatException

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

Методы синтаксического анализа позволяют без труда преобразовать во внутренний формат числовые значения, введенные в виде символьных строк с клавиатуры или из текстового файла. Ниже приведен пример программы, демонстрирующий применение для этих целей методов parselnt() и parseDouble(). В этой программе находится среднее арифметическое ряда чисел, введенных пользователем с клавиатуры. Сначала пользователю прелагается указать количество числовых значений для обработки, а затем программа вводит числа с клавиатуры, используя метод readLine(), а с помощью метода parselnt() преобразует символьную строку в целочисленное значение. Далее осуществляется ввод числовых значений и последующее их преобразование в тип double с помощью метода parseDouble().

/* Эта программа находит среднее арифметическое для
   ряда чисел, введенных пользователем с клавиатуры. */
import java.io.*;

class AvgNums {
  public static void main(String args[])
    throws IOException
  {
    // создать объект типа BufferedReader,
    // использующий поток ввода System.in
    BufferedReader br = new
      BufferedReader(new InputStreamReader(System.in));
    String str;
    int n;
    double sum = 0.0;
    double avg, t;

    System.out.print("How many numbers will you enter: ");
    str = br.readLine();
    try {
      // Преобразование символьной строки
      // в числовое значение типа int.
      n = Integer.parselnt(str);
    }
    catch(NumberFormatException exc) {
      System.out.println("Invalid format");
      n = 0;
    }

    System.out.println("Enter " + n + " values.");
    for(int i=0; i < n ; i++)   {
      System.out.print(" : ");
      str = br.readLine();
      try {
        // Преобразование символьной строки
        // в числовое значение типа double,
        t = Double.parseDouble(str) ;
      } catch(NumberFormatException exc) {
        System.out.println("Invalid format");
        t = 0.0;
      }
      sum += t;
    }
    avg = sum / n;
    System.out.println("Average is " + avg);
  }
}

Выполнение этой программы может дать, например, следующий результат:

How many numbers will you enter: 5
Enter 5 values.
: 1.1
: 2.2
: 3.3
: 4.4
: 5.5
Average is 3.3

Пример для опробования 10.2.
Создание справочной системы, находящейся на диске

В примере для опробования 4.1 был создан класс
Help, позволяющий отображать сведения об операторах Java. Справочная информация хранилась в самом классе, а пользователь выбирал требуемые сведения из меню. И хотя такая справочная система выполняет свои функции, подход к ее разработке был выбран далеко не самый лучший. Так, если требуется добавить или изменить какие-нибудь сведения в подобной справочной системе, придется внести изменения в исходный код программы, которая ее реализует. Кроме того, выбирать пункт меню по его номеру не очень удобно, а если количество пунктов велико, то такой способ оказывается вообще непригодным. В этом проекте предстоит устранить недостатки, имеющиеся в справочной системе, разместив справочную информацию на диске.

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

Последовательность действий

  1. Создайте файл, в котором будет храниться справочная информация и который будет использоваться в справочной системе. Это должен быть обычный текстовый файл, организованный следующим образом:
#название_темы_1
Информация по теме

#название_темы_2
Информация по теме


#название_темы_N
Информация по теме
  1. Название каждой темы располагается в отдельной строке и предваряется символом #. Наличие специального символа в строке (в данном случае — #) позволяет программе быстро найти начало раздела. Под названием темы может располагаться любая справочная информация. После окончания одного раздела и перед началом другого должна быть введена пустая строка. Кроме того, в конце строк не должно быть лишних пробелов.
  2. Ниже приведен пример простого файла со справочной информацией, который можно использовать вместе с новой версией справочной системы. В нем хранятся сведения об операторах Java.
#if
if(condition) statement;
else statement;

#switch
switch(expression) {
case constant:
  statement sequence
  break;
  // ...
}

#for
for(init; condition; iteration) statement;

#while
while(condition) statement;

#do
do {
  statement;
} while (condition);

#break
break; or break label;

#continue
continue; or continue label;
  1. Присвойте этому файлу имя helpfile.txt.
  2. Создайте файл FileHelp.java.
  3. Начните создание новой версии класса Help со следующих строк кода:
class Help {
String helpfile; // Имя файла со справочной информацией

  Help(String fname) {
    helpfile = fname;
}
  1. Имя файла со справочной информацией передается конструктору класса Help и запоминается в переменной экземпляра helpfile. А поскольку каждый экземпляр класса Help содержит отдельную копию переменной helpf ile, то каждый из них может взаимодействовать с отдельным файлом. Это дает возможность создавать отельные наборы справочных файлов на разные темы.
  2. Добавьте в класс Help метод helpon(), код которого приведен ниже. Этот метод извлекает справочную информацию по заданной теме.
// отобразить справочную информацию по заданной теме
boolean helpon(String what) {
  int ch;
  String topic, info;

  // открыть справочный файл
  try (BufferedReader helpRdr =
        new BufferedReader(new FileReader(helpfile)))
  {
    do {
      // читать символы до тех пор, пока не встретится знак #
      ch = helpRdr.read();

      // а теперь проверить, совпадают ли темы
      if(ch == '#') {
      topic = helpRdr.readLine();
        if(what.compareTo(topic) == 0)  { // found topic
        do {
          info = helpRdr.readLine();
          if(info != null) System.out.println(info);
        } while((info != null) &&
                (info.compareTo("") !=  0));
          return true;
        }
      }
    } while(ch != -1);
  }
  catch(IOException exc) {
    System.out.println("Error accessing help file.");
    return false;
  }
  return false; // тема не найдена
}
  1. Прежде всего обратите внимание на то, что в методе helpon() обрабатываются все исключения, связанные с вводом-выводом, поэтому в заголовке метода не указано ключевое слово throws. Благодаря такому подходу упрощается разработка методов, в которых используется метод helpon(). В вызывающем методе достаточно обратиться к методу helpon(), не заключая его вызов в блок try/catch.
  2. Для открытия файла со справочной информацией служит класс FileReader, оболочкой которого является класс Buf feredReader. В справочном файле содержится текст, и поэтому справочную систему удобнее локализовать через символьные потоки ввода-вывода.
  3. Метод helpon ( действует следующим образом. Символьная строка, содержащая название темы, передается этому методу в качестве параметра. Метод открывает сначала файл со справочной информацией. Затем в файле производится поиск, т.е. проверяется совпадение содержимого переменной what и названия темы. Напомним, что в файле заголовок темы предваряется символом #, поэтому метод сначала ищет данный символ. Если символ найден, производится сравнение следующего за ним названия темы с содержимым переменной what. Если сравниваемые строки совпадают, то отображается справочная информация по данной теме. И если заголовок темы найден, то метод helpon() возвращает логическое значение true, иначе — логическое значение false.
  4. В классе Help содержится также метод getSelectionO, который предлагает задать тему и возвращает строку, введенную пользователем.
// получить тему
String getSelectionO {
  String topic = "";

  BufferedReader br = new BufferedReader(
    new InputStreamReader(System.in));

  System.out.print("Enter topic: ") ;
  try {
    topic = br.readLine();
  }
  catch(IOException exc) {
    System.out.println("Error reading console.");
  }
  return topic;
}
  1. В теле этого метода сначала создается объект типа Buf feredReader, который связывается с потоком вывода System, in. Затем в нем запрашивается название темы, которое принимается и далее возвращается вызывающей части программы.
  2. Ниже приведен весь исходный код программы, реализующей справочную систему, находящуюся на диске.
/*
  Пример для опробования 10.2.

  Справочная система, находящаяся на диске.

  Для компиляции этой программы требуется JDK 7
  или более поздняя версия данного комплекта.
*/
import java.io.*;

/* В классе Help открывается файл со справочной информацией,
   производится поиск названия темы, а затем отображается
   справочная информация по этой теме.
   Обратите внимание на то, что в этом классе поддерживаются
   все исключения, освобождая от этой обязанности вызывающий код. */
class Help {
  String helpfile; // Имя файла со справочной информацией

  Help(String fname) {
    helpfile = fname;
  }

  // отобразить справочную информацию по заданной теме
  boolean helpon(String what) {
    int ch;
    String topic, info;

    // открыть справочный файл
    try (BufferedReader helpRdr =
          new BufferedReader(new FileReader(helpfile)))
    {
      do {
        // читать символы до тех пор, пока не встретится знак #
        ch = helpRdr.read();

        // а теперь проверить, совпадают ли темы
        if(ch =='#') {
          topic = helpRdr.readLine();
          if(what.compareTo(topic) == 0)  {   //  тема    найдена
            do {
              info = helpRdr.readLine();
              if(info != null) System.out.println(info);
            } while((info != null) &&
                    (info.compareTo("") !=  0));
              return true;
          }
        }
      } while(ch != -1);
    }
    catch(IOException exc) {
    System.out.println("Error accessing help file.");
    return false;
    }
    return false; // тема не найдена
  }

  // получить тему
  String getSelection()   {
    String topic = "";

    BufferedReader br = new BufferedReader(
      new InputStreamReader(System.in));

    System.out.print("Enter topic: ");
    try {
      topic = br.readLine();
    }
    catch(IOException exc) {
      System.out.println("Error reading console.");
    }
    return topic;
  }
}

// продемонстрировать справочную систему, находящуюся на диске
class FileHelp {
  public static void main(String args[]) {
    Help hlpobj = new Help("helpfile.txt");
    String topic;

    System.out.println("Try the help system. " +
                       "Enter ’stop' to end.");
    do {
      topic = hlpobj.getSelection();

      if(!hlpobj.helpon(topic))
        System.out.println("Topic not found.n");

    } while(topic.compareTo("stop") !=  0);
  }
}

Упражнение для самопроверки

по материалу главы 10

  1. Для чего в Java определены как байтовые, так и символьные потоки?
  2. Как известно, ввод-вывод данных на консоль осуществляется в текстовом виде. Почему же в Java для этой цели используются байтовые потоки?
  3. Как открыть файл для чтения байтов?
  4. Как открыть файл для чтения символов?
  5. Как открыть файл для ввода-вывода с произвольным доступом?
  6. Как преобразовать числовую строку «123.23» в двоичный эквивалент?
  7. Напишите программу, которая будет копировать текстовые файлы. Видоизмените ее таким образом, чтобы все пробелы заменялись дефисами. Используйте при написании программы классы, представляющие байтовые потоки, а также традиционный способ закрытия файла явным вызовом метода close().
  8. Перепишите программу, созданную в ответ на предыдущий вопрос, таким образом, чтобы в ней использовались классы, представляющие символьные потоки. На этот раз воспользуйтесь оператором try с ресурсами для автоматического закрытия файла.
  9. К какому типу относится поток System. in?
  10. Что возвращает метод read() из класса InputStream по достижении конца потока?
  11. Поток какого типа используется для чтения двоичных данных?
  12. Классы Reader и Writer находятся на вершине иерархии классов _______ .
  13. Оператор try без ресурсов служит для ______ .
  14. Если для закрытия файла используется традиционный способ, то это лучше всего делать в блоке finally. Верно или неверно?

Афоризм

Если ты споришь с идиотом, то, вероятно, то же самое делает и он.

Михаил Жванецкий

Поддержка проекта

Если Вам сайт понравился и помог, то будем признательны за Ваш «посильный» вклад в его поддержку и развитие

 • Yandex.Деньги
  410013796724260

 • Webmoney
  R335386147728
  Z369087728698

Ввод/вывод информации

  • ввод с клавиатуры Scaner
  • блокирование ввода/вывода

Форматированный вывод System.out.printf

Метод вывода в консоль System.out.println (x), где x — переменная, содержащая число, выводит
максимальное количество ненулевых цифр числа x, заданных типом. Например:

double d = 99;
System.out.println (d / 47);

выведет на экран число :

2.106382978723404;
 

Данная точность вывода иногда бывает избыточной для представления результатов вычислений. Для того,
чтобы числа можно было представить в требуемом формате в Java SE применяется метод
System.out.println (). Например последовательность команд

double d = 99;
System.out.printf ("99 / 47 = %.2f", d / 47);

выведет на экран строку :

99 / 47 = 2.11;
 

Обычный текст выводится на экран без изменений. Значение числа отображается согласно спецификации
преобразования данных. Спецификация преобразования начинается с символа ‘%’ и заканчивается спецификатором.
Некоторые спецификаторы указаны в следующей таблице:

Спецификатор Тип значения
d целое десятичное число
f десятичная форма вещественного числа
e экспоненциальная форма вещественного числа
s строка
c символ
b boolean

Для представления числовых значений бывает полезным использовать в спецификации преобразования указание
ширины поля и точности. Ширина поля и точность последовательно указываются между ‘%’ и спецификатором и
отделяются друг от друга точкой.

Ширина поля — целое положительное число, определяющее длину (в символах экрана) представления выводимого
значения (позиции на знак числа и десятичную точку входят в ширину поля вывода).

Точность — целое положительное число, определяющее количество цифр в дробной части внешнего представления
числа (с фиксированной точкой) или его мантиссы (при использовании формы с плавающей точкой).

Ввод данных с клавиатуры, Scanner

Для ввода данных с клавиатуры необходимо создать объект, который будет использоваться для чтения из
стандартного потока ввода. Этот объект имеет тип Scanner, описанный в пакете java.util.
Класс необходимо импортировать в программу, использующую команды ввода данных из какого-либо потока.

Пример ввода данных:

public class InputTest
{
    public static void main(String[] args)
	{
        // создание объекта чтения из стандартного 
        // потока ввода
        Scanner in = new Scanner(System.in);
        System.out.println("Enter your name, please:");
        // чтение строки из консоли
        String name = in.nextLine();
        System.out.printf("Hello, %s", name);
    }
}

Для ввода целых чисел используется метод nextInt(), для дробных — nextDouble().

Блокирование ввода и вывода

Для блокирования ввода/вывода необходимо закрыть входные и выходные потоки.

try {
    System.in.close();
    System.out.close();
} catch (IOException e) {}

Наверх

Improve Article

Save Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Java brings various Streams with its I/O package that helps the user to perform all the input-output operations. These streams support all the types of objects, data-types, characters, files etc to fully execute the I/O operations.

    Before exploring various input and output streams lets look at 3 standard or default streams that Java has to provide which are also most common in use:

    1. System.in: This is the standard input stream that is used to read characters from the keyboard or any other standard input device.
    2. System.out: This is the standard output stream that is used to produce the result of a program on an output device like the computer screen.

      Here is a list of the various print functions that we use to output statements:

      • print(): This method in Java is used to display a text on the console. This text is passed as the parameter to this method in the form of String. This method prints the text on the console and the cursor remains at the end of the text at the console. The next printing takes place from just here.
        Syntax:
        System.out.print(parameter);

        Example:

        import java.io.*;

        class Demo_print {

            public static void main(String[] args)

            {

                System.out.print("GfG! ");

                System.out.print("GfG! ");

                System.out.print("GfG! ");

            }

        }

        Output:

        GfG! GfG! GfG! 
      • println(): This method in Java is also used to display a text on the console. It prints the text on the console and the cursor moves to the start of the next line at the console. The next printing takes place from the next line.
        Syntax:
        System.out.println(parameter);

        Example:

        import java.io.*;

        class Demo_print {

            public static void main(String[] args)

            {

                System.out.println("GfG! ");

                System.out.println("GfG! ");

                System.out.println("GfG! ");

            }

        }

        Output:

        GfG! 
        GfG! 
        GfG! 
      • printf(): This is the easiest of all methods as this is similar to printf in C. Note that System.out.print() and System.out.println() take a single argument, but printf() may take multiple arguments. This is used to format the output in Java.
        Example:

        class JavaFormatter1 {

            public static void main(String args[])

            {

                int x = 100;

                System.out.printf(

                    "Printing simple"

                        + " integer: x = %dn",

                    x);

                System.out.printf(

                    "Formatted with"

                        + " precision: PI = %.2fn",

                    Math.PI);

                float n = 5.2f;

                System.out.printf(

                    "Formatted to "

                        + "specific width: n = %.4fn",

                    n);

                n = 2324435.3f;

                System.out.printf(

                    "Formatted to "

                        + "right margin: n = %20.4fn",

                    n);

            }

        }

        Output:

        Printing simple integer: x = 100
        Formatted with precision: PI = 3.14
        Formatted to specific width: n = 5.2000
        Formatted to right margin: n =         2324435.2500
    3. System.err: This is the standard error stream that is used to output all the error data that a program might throw, on a computer screen or any standard output device.

      This stream also uses all the 3 above-mentioned functions to output the error data:

      • print()
      • println()
      • printf()
    4. Example:

      import java.io.*;

      public class SimpleIO {

          public static void main(String args[])

              throws IOException

          {

              InputStreamReader inp = null;

              inp = new InputStreamReader(System.in);

              System.out.println("Enter characters, "

                                 + " and '0' to quit.");

              char c;

              do {

                  c = (char)inp.read();

                  System.out.println(c);

              } while (c != '0');

          }

      }

      Input:

      GeeksforGeeks0

      Output:

      Enter characters, and '0' to quit.
      G
      e
      e
      k
      s
      f
      o
      r
      G
      e
      e
      k
      s
      0

    Types of Streams:

    • Depending on the type of operations, streams can be divided into two primary classes:
      1. Input Stream: These streams are used to read data that must be taken as an input from a source array or file or any peripheral device. For eg., FileInputStream, BufferedInputStream, ByteArrayInputStream etc.
      2. Output Stream: These streams are used to write data as outputs into an array or file or any output peripheral device. For eg., FileOutputStream, BufferedOutputStream, ByteArrayOutputStream etc.
    • Depending on the types of file, Streams can be divided into two primary classes which can be further divided into other classes as can be seen through the diagram below followed by the explanations.
      1. ByteStream: This is used to process data byte by byte (8 bits). Though it has many classes, the FileInputStream and the FileOutputStream are the most popular ones. The FileInputStream is used to read from the source and FileOutputStream is used to write to the destination. Here is the list of various ByteStream Classes:
        Stream class Description
        BufferedInputStream It is used for Buffered Input Stream.
        DataInputStream It contains method for reading java standard datatypes.
        FileInputStream This is used to reads from a file
        InputStream This is an abstract class that describes stream input.
        PrintStream This contains the most used print() and println() method
        BufferedOutputStream This is used for Buffered Output Stream.
        DataOutputStream This contains method for writing java standard data types.
        FileOutputStream This is used to write to a file.
        OutputStream This is an abstract class that describe stream output.

        Example:

        import java.io.*;

        public class BStream {

            public static void main(

                String[] args) throws IOException

            {

                FileInputStream sourceStream = null;

                FileOutputStream targetStream = null;

                try {

                    sourceStream

                        = new FileInputStream("sorcefile.txt");

                    targetStream

                        = new FileOutputStream("targetfile.txt");

                    int temp;

                    while ((

                               temp = sourceStream.read())

                           != -1)

                        targetStream.write((byte)temp);

                }

                finally {

                    if (sourceStream != null)

                        sourceStream.close();

                    if (targetStream != null)

                        targetStream.close();

                }

            }

        }

        Output:

        Shows contents of file test.txt 
      2. CharacterStream: In Java, characters are stored using Unicode conventions (Refer this for details). Character stream automatically allows us to read/write data character by character. Though it has many classes, the FileReader and the FileWriter are the most popular ones. FileReader and FileWriter are character streams used to read from the source and write to the destination respectively. Here is the list of various CharacterStream Classes:
        Stream class Description
        BufferedReader It is used to handle buffered input stream.
        FileReader This is an input stream that reads from file.
        InputStreamReader This input stream is used to translate byte to character.
        OutputStreamReader This output stream is used to translate character to byte.
        Reader This is an abstract class that define character stream input.
        PrintWriter This contains the most used print() and println() method
        Writer This is an abstract class that define character stream output.
        BufferedWriter This is used to handle buffered output stream.
        FileWriter This is used to output stream that writes to file.

        Example:

        import java.io.*;

        public class GfG {

            public static void main(

                String[] args) throws IOException

            {

                FileReader sourceStream = null;

                try {

                    sourceStream

                        = new FileReader("test.txt");

                    int temp;

                    while ((

                               temp = sourceStream.read())

                           != -1)

                        System.out.println((char)temp);

                }

                finally {

                    if (sourceStream != null)

                        sourceStream.close();

                }

            }

        }

      3. Refer here for the complete difference between Byte and Character Stream Class in Java.

    Понравилась статья? Поделить с друзьями:
  • Java как написать или
  • Java как написать web приложение
  • J прописная английская заглавная как пишется
  • Ivi как пишется
  • Itшник как пишется