system out java что это

Класс System в Java

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

Что такое класс System в Java?

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

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

Объявление

Поля класса

Класс java.lang.System поставляется с тремя полями:

Методы системного класса

Всего в классе java.lang.System объявлено 28 встроенных методов.

МетодОписание
static void arraycopy (Object src, int srcPos, Object dest, int destPos, int length)Помогает копировать массив из указанного исходного массива, начиная с указанной позиции до указанной позиции целевого массива.
static String clearProperty (ключ String)Помогает удалить системное свойство, указанное указанным ключом.
Консоль static static ()Помогает вернуть любой доступный уникальный объект консоли, связанный с текущей JVM.
статическое длинное currentTimeMillis()Помогает вернуть текущее время в миллисекундах
выход из статической пустоты (статус int)Помогает завершить текущую JVM
статическая пустота gc()Этот метод помогает в запуске сборщика мусора
статическая карта getenv()Помогает в возврате неизменяемой строковой карты текущей системы
static String getenv (имя строки)Помогает в получении значения указанной переменной среды
статические свойства getProperties ()Помогает в определении текущих свойств системы
статическая строка getProperty (строковый ключ)Помогает получить системное свойство, указанное указанным ключом.
статическая строка getProperty (ключ строки, строка определения)Помогает получить системное свойство, указанное указанным ключом.
статический SecurityManager getSecurityManager ()Помогает получить интерфейс безопасности системы
static int identityHashCode (Object x)Помогает возвращать тот же хэш-код для данного объекта, значение которого будет похоже на метод по умолчанию hashCode (), независимо от переопределения класса данного объекта hashCode ()
статический Канал унаследованный канал ()Помогает в возврате канала, который унаследован от объекта, создавшего JVM.
статическая строка lineSeparator ()Помогает в возврате системно-зависимой строки разделителя строк.
статическая пустая нагрузка (строковое имя файла)Помогает в загрузке файла кода с указанным именем файла из локальной файловой системы в виде динамической библиотеки.
static void loadLibrary (строковое имя_библиотеки)Помогает в загрузке системной библиотеки, указанной аргументом libname
статическая строка mapLibraryName (строковое имя_библиотеки)Помогает в отображении имени библиотеки в специфическую для платформы строку, представляющую собственную библиотеку
статический длинный nanoTime ()Помогает возвращать текущее значение работающего источника времени высокого разрешения JVM в течение наносекунд
static void runFinalization ()Помогает в выполнении методов завершения любых объектов, ожидающих завершения
static void setErr (PrintStream err)Помогает переназначить «стандартный» поток вывода ошибок
статическая пустота setIn (InputStream in)Помогает переназначить «стандартный» поток ввода
static void setOut (PrintStream out)Помогает переназначить «стандартный» поток вывода
static void setProperties (Свойства реквизита)Помогает в настройке системных свойств для аргумента Свойства
статическая строка setProperty (строковый ключ, строковое значение)Помогает в настройке системного свойства, указанного указанным ключом
static void setSecurityManager (SecurityManager s)Помогает в настройке безопасности системы
static void runFi nalizersOnExit (логическое значение)Устаревшее

Реализация

В следующем примере я реализовал несколько из рассмотренных выше методов.

Вывод

Вы можете попробовать реализовать остальные методы, и если вы где-то застряли, вы можете оставить комментарий, и мы поможем вам с этим.

Источник

System out println java — консольный вывод

Содержание

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

Что собой представляет метод

В Java есть потоки, за счет которых выполняется функция вывода. Каждый из них представляется как отдельная инструкция, порядок выполнения которой задает пользователь. Потоки — связующее звено между системами ввода-вывода и физическим устройством, поэтому принцип их действия одинаковый. Отличаются лишь применяемые к ним методы.

Обзор синтаксиса

Чтобы разобраться, как команда работает на Java, следует понять работу каждого отдельного компонента System.out.println().

System — класс, к которому команда будет обращаться при выполнении. Он инкапсулирует необходимые средства из классов, которые обеспечивают работу метода.

Out — переменная, которая предопределена классом Output и принимает поток. К ней обращается System. Он является экземпляром Output, поэтому имеет доступ ко всем его методам по принципам ООП.

Println() — экземпляр класса Outputstream, именуемый PrintStream. Он содержит инструкции, позволяющие обрабатывать данные, выводимые на экран. Сюда ссылается переменная out. Как и в любую функцию, сюда можно передавать аргументы.

Таким образом, для вывода строки Hello, Java-программа обращается к System, а тот, в свою очередь, к Output, а затем к PrintStream, которые являются экземплярами класса OutputStream и производят необходимые действия.

Примеры работы

Работу с методом можно обеспечить с помощью терминала ОС либо среды разработки. Перед этим создается проект и класс с одинаковыми названиями. В окне создания элемента можно поставить галочку на пункте главного метода. В результате получим код с необходимыми для работы данными. Введем в блок главного метода main пустую команду вывода на консоль.

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

В Java метод system out println может выводить разные типы данных. Чтобы вывести на экран строку, необходимо в пустых скобках как аргумент указать набор любых символов в кавычках.

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

Метод может передавать значения переменных любого типа. Для этого нужно ввести без кавычек название переменной откуда будет браться значение.

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

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

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

Такой код выведет результат сложения переменных и строк.

Приложив спецификаторы и escape-последовательности к system out println java, примеры можно реализовывать другим способом.

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

Здесь применяется спецификатор %d, который принимает значение указанных через запятую аргументов. В данном примере нет переноса строки. Его обеспечивает метод println(), в отличие от print() и printf(), которые не осуществляют перевода. Добиться этой функции возможно при помощи escape-последовательности \n. Ее необходимо добавить в строку через пробел в том месте, где нужен перенос.

System.out.println() в Java имеет свои списки escape-последовательностей и заимствованных из C++ спецификаторов.

Сокращенный ввод

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

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

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

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

Источник

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

system out java что это. Смотреть фото system out java что это. Смотреть картинку system out java что это. Картинка про system out java что это. Фото system out java что это

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

BestProg

Система ввода/вывода Java. Потоки. Байтовые потоки. Символьные потоки. Стандартные потоки

Содержание

Поиск на других ресурсах:

1. Общие понятия системы ввода/вывода Java. Поток. Определение потока

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

Соответственно назначению потоков, классифицируются и классы в языке Java. Одни классы реализуют операции ввода, другие реализуют операции вывода. Чтобы использовать классы потоков ввода/вывода нужно импортировать пакет java.io

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

2. Виды потоков в Java

В языке Java различают два вида потоков:

Классы, которые реализуют байтовые потоки ввода унаследованы от абстрактного класса InputStream :

Классы, которые реализуют байтовые потоки вывода унаследованы от абстрактного класса OutputStream :

4. Символьные потоки ввода/вывода. Обзор классов символьных потоков

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

Классы потоков ввода следующие:

Классы потоков вывода следующие:

Источник

11: Система ввода/вывода в Java

Создание хорошей системы ввода/вывода (I/O) является одной из наиболее сложных задач для разработчиков языка.

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

Эта глава даст вам введение в различные классы ввод/вывода стандартной библиотеки Java и расскажет о том, как их использовать.

Класс File

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

Список директории

DirFilter показывает, что из-за того, что interface содержит только набор методов, вы не ограничены в написании только этих методов. (Однако вы должны как минимум обеспечить определение для всех методов интерфейса.) В этом случае также создается конструктор DirFilter.

Метод accept( ) должен принимать объект File, представляющий директорий, в котором находится определенный файл, а String содержит имя этого файла. Вы можете выбрать использовать или игнорировать любой из этих аргументов, но вы, вероятно, как минимум, должны использовать имя файла. Помните, что метод list( ) вызывает метод accept( ) для каждого имени файла в директории, чтобы проверить, какой из них должен быть включен — на это указывает тип boolean результата, возвращаемого accept( ).

Метод list( ) возвращает массив. Вы можете опросить этот массив о его длине, а затем пройтись по нему, выбирая элементы массива. Эта способность легкого прохода по массиву вне методов и в методах является значительным улучшением по сравнению с поведением C и C++.

Анонимные внутренние классы

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

Поиск и создание директориев

В fileData( ) вы можете видеть различные способы исследования файла для отображения информации о файле или о пути директории.

Если вы поэкспериментируете с приведенной выше программой, вы обнаружите, что вы можете создать путь директорий любой сложности, потому что mkdirs( ) будет делать всю работу за вас.

Ввод и вывод

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

Библиотечные классы Java для ввода/вывода делятся на классы ввода и вывода, как вы можете увидеть, взглянув на иерархию Java классов в онлайн документации с помощью вашего Web броузера. При наследовании, все, что наследуется от классов InputStream или Reader, имеет основной метод, называемый read( ) для чтения единичного байта или массива байт. Точно так же, все, что наследуется от классов OutputStream или Writer, имеет основной метод, называемый write( ) для записи единичного байта или массива байт. Однако чаще всего вы не можете использовать эти методы; они существуют для того, чтобы другие классы могли использовать их — эти другие классы обеспечивают более полезные интерфейсы. Таким образом, вы редко будете создавать ваш объект потока, используя единственный класс, вместо этого вы будите располагать множеством объектом для обеспечения желаемой функциональности. Факт в том что вы создаете более, чем один объект для создания единственного результирующего потока, это главная причина, по которой потоки Java являются запутанными.

Типы InputStream

Работа InputStream состоит в представлении классов, которые производят ввод от различных источников. Источниками могут быть:

Таблица 11-1. Типы InputStream

Типы OutputStream

Эта категория включает классы, которые решают, куда будет производиться вывод: в массив байт (но не String ; возможно, вы можете создать его, используя массив байт), в файл, или в “трубу”.

Кроме того, FilterOutputStream обеспечивает базовый класс для «декорирования» классов, которые присоединяют атрибуты или полезные интерфейсы для выходного потока. Это будет обсуждаться позже.

Таблица 11-2. Типы OutputStream

Добавление атрибутов и полезных интерфейсов

Декорирование часто используется, когда простое использование подклассов в результате приводит к большому числу подклассов, способных удовлетворить каждую возможную необходимую комбинацию, что становится непрактично. Библиотека ввода/вывода Java требует много различных комбинаций особенностей, которые являются причиной использования шаблона декоратора. Однако для шаблона декоратора есть препятствие. Декораторы дают вам много больше гибкости, когда вы пишите программу (так как вы можете легко смешивать и сравнивать атрибуты), но они привносят сложность в ваш код. Причина того, что библиотека Java неудобна в использовании, состоит в том, что вы должны создавать много классов — “центральные” типы ввода/вывода, плюс все декораторы — для того, чтобы создать единственный объект ввода/вывода, который вам нужен.

Чтение из InputStream с помощью FilterInputStream

Классы FilterInputStream совершают две значительные вещи. DataInputStream позволяет вам читать различные типы примитивных данных, наряду с объектами типа String. (Все методы начинаются со слова “read”, например: readByte( ), readFloat( ), и т.п.) Таким образом, наряду со своим компаньоном DataOutputStream, это позволяет вам перемещать примитивные данные из одного места в другое через поток. Эти “места” определяются классами в таблице 11-1.

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

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

Таблица 11-3. Типы FilterInputStream

Запись в OutputStream с помощью FilterOutputStream

Изначальное предназначение PrintStream было в печати всех примитивных типов данных и объектов String в удобочитаемом формате. Он отличается от DataOutputStream, чья цель состоит в помещении элементов данных в поток таким способом, чтобы DataInputStream мог без труда реконструировать их.

Двумя важнейшими методами PrintStream являются print( ) и println( ), которые перегружены для печати всех различных типов. Различия между print( ) и println( ) в том, что последний метод добавляет символ новой строки, когда завершен вывод.

PrintStream может быть проблематичным, поскольку он ловит все IOException (вы должны явно проверять статус ошибки с помощью checkError( ), который возвращает true, если возникла ошибка). Так же PrintStream не интернацианализован полностью и не обрабатывает переводы строки платформонезависимым способом (эти проблемы решаются с помощью PrintWriter).

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

Таблица 11-4. Типы FilterOutputStream

Читающие и пишущие

В Java 1.1 сделаны некоторые значительные модификации в фундаментальной библиотеке потоков ввода/вывода (однако Java 2 не внесла фундаментальных модификаций). Когда вы видите классы Reader и Writer, вы сначала можете подумать (как и я), что они предназначены для замены классов InputStream и OutputStream. Но не в этом случае. Хотя некоторые аспекты начальной библиотеки потоков устарели и были заменены (если вы используете их, вы должны получать предупреждение компилятора), классы InputStream и OutputStream все еще обеспечивают ценную функциональность в форме байт-ориентированных систем ввода/вывода, в то время как классы Reader и Writer обеспечивают Unicode-совместимый, символьно ориентированный ввод/вывод. Кроме того:

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

Источники и приемники данных

Почти все оригинальные классы потоков ввода/вывода имеют соответствующие классы Reader и Writer для обеспечения родных манипуляций в Unicode. Однако есть некоторые места, где байт-ориентированные InputStream и OutputStream являются корректным решением; на практике библиотеки из java.util.zip скорее байт-ориентированные, чем символьно-ориентированные. Так что наиболее разумным подходом будет попытка использования классов Reader и Writer там, где э то возможно, и вы обнаружите ситуации, когда будете вынуждены использовать байт-ориентированные библиотеки, потому что ваш код не будет компилироваться.

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

Источники и приемники: класс Java 1.0Соответствующий класс Java 1.1
InputStreamReader
конвертер: InputStreamReader
OutputStreamWriter
конвертер: OutputStreamWriter
FileInputStreamFileReader
FileOutputStreamFileWriter
StringBufferInputStreamStringReader
(соответствующего класса нет)StringWriter
ByteArrayInputStreamCharArrayReader
ByteArrayOutputStreamCharArrayWriter
PipedInputStreamPipedReader
PipedOutputStreamPipedWriter

В общем случае вы обнаружите, что интерфейсы для этих двух различных иерархий сходны, если не идентичны.

Модификация поведения потока

Потоки InputStream и OutputStream адаптируются под определенные требования с использованием “декорирующих” подклассов FilterInputStream и FilterOutputStream. Классы иерархии Reader и Writer продолжают использовать эту идею — но не точно.

В приведенной таблице соответствие произведено с еще большим приближением, чем в предыдущей таблице. Различия происходят из-за организации классов: в то время как BufferedOutputStream является подклассом FilterOutputStream, BufferedWriter не является подклассом FilterWriter (который, не смотря на то, что он является абстрактным, не имеет подклассов и, таким образом, появляется помещенным в любой объект, а здесь упомянуть просто для того, чтобы вы не удивились, увидев его). Однако интерфейсы классов достаточно близки при сравнении.

Есть одно направление, которое достаточно понятно: Когда вы хотите использовать readLine( ), вы не должны более использовать DataInputStream (при этом вы встретитесь с сообщении об использовании устаревших методов во время компиляции), а вместо этого использовать BufferedReader. Тем не менее, DataInputStream все еще остается “привилегированным” членом библиотеки ввода/вывода.

Чтобы сделать переход к использованию PrintWriter более легким, он имеет конструктор, который принимает любой объект типа OutputStream, наряду с объектами Writer. Однако PrintWriter более не поддерживает форматирование, которое поддерживал PrintStream; интерфейс, фактически, остался тем же.

Конструктор PrintWriter также имеет необязательную опцию для выполнения автоматической выгрузки буферов, которая случается после каждого вызова println( ), если установлен флаг в конструкторе.

Неизмененные классы

Некоторые классы остались неизменными при переходе от Java 1.0 к Java 1.1:

Классы Java 1.0 не имеющие соответствующих классов в Java 1.1
DataOutputStream
File
RandomAccessFile
SequenceInputStream

Сам по себе: RandomAccessFile

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

Сначала немного трудно поверить, что RandomAccessFile не является частью иерархии InputStream или OutputStream. Однако он не имеет ассоциаций с этими иерархиями, за исключением того, что он реализует интерфейсы DataInput и DataOutput (которые так же реализуются DataInputStream и DataOutputStream). Он даже не использует любую функциональность существующих классов InputStream или OutputStream — это полностью отдельный класс, написанный для поиска, имеющий все свои собственные (в большинстве своем родные) методы. Объяснением этого может быть то, что RandomAccessFile имеет во многом отличающееся поведение по сравнению с остальными типами ввода/вывода, так как вы можете перемещаться вперед и назад в пределах файла. В любом случае, он стоит отдельно, как прямой потомок от Object.

По существу, RandomAccessFile работает как DataInputStream совмещенный с DataOutputStream, благодаря использованию методов getFilePointer( ) для нахождения местоположения в файле, seek( ) для перемещения в новую точку в файле и length( ) для определения максимального размера файла. Кроме того, конструктор требует второй аргумент (что идентично fopen( ) в C), указывающий будите ли вы производить только чтение в произвольном порядке (“r”) или чтение и запись (“rw”). Нет поддержки для файлов только для чтения, что может сказать о том, что RandomAccessFile мог бы хорошо работать, если он наследовался бы от DataInputStream.

Типичное использование потоков ввода/вывода

Хотя вы можете комбинировать классы потоков ввода/вывода многими различными способами, вы, вероятно, будете использовать несколько комбинаций. Следующий пример может быть использован как отправная точка; он показывает создание и использование типичной конфигурации ввода/вывода. Обратите внимание, что каждая конфигурация начинается с порядкового номера и заголовка, который оглавляет соответствующее объяснение в следующем за ним тексте.

Здесь приведено описание для нумерованных разделов программы:

Потоки ввода

Части с 1 по 4 демонстрируют создание и использование потоков ввода. Часть 4 также показывает простое использование потока вывода.

1. Буферизированный ввод из файла

Для открытия файла для ввода символов вы используете FileInputReader с объектом String или File в качестве имени файла. Для быстрой работы вы можете захотеть, чтобы файл был буферизированный, поэ тому вы передаете результирующую ссылку в конструктор BufferedReader. BufferedReader также обеспечивает метод readLine( ), так что это ваш конечный объект и интерфейс, из которого вы читаете. Когда вы достигаете конца файла, readLine( ) возвращает null, что используется для окончания цикла while.

String s2 использует для аккумулирования всего содержимого файла (включая символы новой строки, которые должны добавляться, поскольку readLine( ) отбрасывает их). s2 далее используется в следующих частях этой программы. В конце вызывается close( ) для закрытия файла. Технически, close( ) будет вызвано при запуске finalize( ), а это произойдет (не зависимо от того произойдет или нет сборка мусора) при выходе из программы. Однако это было реализовано неустойчиво, поэтому безопасным подходом является явный вызов close( ) для файлов.

2. Ввод из памяти


3. Форматированный ввод из памяти

Для чтения “форматированных” данных вы используете DataInputStream, который является байт-ориентированным классом ввода/вывода (а не символьно-ориентированным). Таким образом, вы должны использовать все классы InputStream, а не классы Reader. Конечно, вы можете читать все, что угодно (также как и файл) байтами, используя классы InputStream, но здесь используется String. Для преобразования String в массив байт, который является подходящим для ByteArrayInputStream, String имеет метод getBytes( ), чтобы сделать эту работу. В этой точке вы имеете соответствующий InputStream для управления DataInputStream.

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

4. Вывод в файл

Этот пример также показывает, как писать данные в файл. Сначала создается FileWriter для соединения с файлом. Фактически, вы всегда будете буферизировать вывод, обернув его с помощью BufferedWriter (попробуйте удалить эту обертку, чтобы посмотреть влияние на производительность — буферизация позволяет значительно увеличить производительность операций ввода/вывода). Затем, для форматирование объект включен в PrintWriter. Файл данных, созданный этим способом, читаем, как обычный текстовый файл.

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

Выходные потоки


5. Сохранение и возврат

PrintWriter форматирует данные так, чтобы их читали люди. Однако для вывода данных в виде, чтобы они могли быть возвращены в другой поток, используйте DataOutputStream для записи данных, а DataInputStream для обратного получения данных. Конечно, эти потоки могут быть всем, что угодно, но здесь используется файл, буферизируемый и для чтения, и для записи. DataOutputStream и DataInputStream являются байт-ориентированными и поэтому требуют потоков InputStream и OutputStream.

Если вы используете DataOutputStream для записи данных, то Java гарантирует, что вы можете безошибочно повторно задействовать данные, используя DataInputStream — не зависимо от различий платформ для записи и чтения данных. Это невероятно ценно, как могут подтвердить те, кто потратил время, заботясь о платформозависимых путях движения данными. Эти проблемы снимаются, если вы имеете Java на обеих платформах. [58]

Обратите внимание, что строки символов записываются с использованием как writeChars( ), так и writeBytes( ). Когда вы запустите программу, вы обнаружите, что выводит 16-битные символы Unicode. Когда вы читаете строки, используя readLine( ), вы увидите, что есть пространство между символами, потому что каждый дополнительный байт вставляется из-за Unicode. Так как нет дополнительного метода “readChars” для DataInputStream, вы вынуждены вытягивать символы по одному с помощью readChar( ). Так что для ASCII легче написать символы байтами, за которым следует новая строка, а затем использовать readLine( ) для чтения байтов, как обычной ASCII cтроки.

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

6. Чтение и запись файлов произвольного доступа

Как было замечено ранее, RandomAccessFile почти полностью изолирован от оставшейся иерархии ввода/вывода, и подтвержден тот факт, что он реализует интерфейсы DataInput и DataOutput. Поэтому вы не можете комбинировать его с любыми другими аспектами подклассов InputStream и OutputStream. Даже при том, что имело бы смысл трактовать ByteArrayInputStream, как элемент произвольного доступа, вы можете использовать RandomAccessFile только для открытия файла. Вы должны иметь в виду, что RandomAccessFile буферизирован должным образом, так что вам не нужно заботится об этом.

Одну из настроек вы имеете во втором конструкторе аргумента: вы можете открыть RandomAccessFile для чтения (“r”) или для чтения и записи (“rw”).

Использование RandomAccessFile аналогично использования комбинации DataInputStream и DataOutputStream (потому что он реализует эквивалентные интерфейсы). Кроме того, вы можете видеть, что seek( ) используется для перемещения в файле и изменения одного значения на другое.

Ошибка?

Если вы взглянете на раздел 5, вы увидите, что данные записываются перед текстом. Дело в том, что эта проблема была представлена в Java 1.1 (и сохранилась в Java 2), я был уверен, что это ошибка. Когда я сообщил об этом людям, занимающимся ошибками в JavaSoft, они сказали мне, что это, Проблема показана в следующем коде:

Кажется что все, что вы пишите после вызова writeBytes( ) не возвращаемо. Ответ, очевидно, тот же, что и в случае старой шутки водителя: “Доктор, мне больно, когда я делаю это!” “Так не делайте этого!”

Потоки в виде трубопровода


Стандартный ввод/вывод

Термин стандартный ввод/вывод относится к концепции Unix (которая в некоторой форме была воспроизведена в Windows и многих других операционных системах) единого потока информации, который используется программой. Весь ввод программы может вестись через стандартный ввод, весь вывод может идти в стандартный вывод, а все сообщения об ошибках могут посылаться в стандартный поток ошибок. Значение стандартного ввода/вывода в том, что программы легко могут представлять цепочку вместе, и стандартный вывод одной программы может стать стандартным вводом для другой. Это достаточно мощный инструмент.

Чтение из стандартного ввода


Замена System.out на PrintWriter


Перенаправление стандартного ввода/вывода

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

setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)

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

Эта программа соединяет стандартный ввод с файлом и перенаправляет стандартный вывод и стандартные ошибки в другой файл.

Компрессия

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

Эти классы не наследуются от классов Reader и Writer, а вместо этого они являются частью иерархии InputStream и OutputStream. Это происходит потому, что библиотека компрессии работает с байтами, а не с символами. Однако вы можете иногда встретить необходимость смешивания двух типов потоков. (Помните, что вы можете использовать InputStreamReader и OutputStreamWriter для обеспечения простой конвертации одного типа в другой. )

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

Простая компрессия с помощью GZIP

Интерфейс GZIP прост, и поэтому он является более подходящим, когда вы имеете единственный поток данных, которые хотите компрессировать (в отличие от случая, когда вы имеете кусочки разнородных данных). Здесь приведен пример компрессии единичного файла:

Многофайловое хранение с использованием Zip

Библиотека, поддерживающая Zip формат, более обширная. С ее помощью вы можете легко хранить множественные файлы, есть даже отдельные файлы, которые делают легким процесс чтения Zip файла. Библиотека использует стандартный Zip формат, так что он может работать совместно со всеми инструментами, которые доступны в Internet. Следующий пример имеет ту же форму, что и предыдущий, но он обрабатывает столько аргументов командной строки, сколько вы захотите. Кроме того, он показывает использование классов Checksum для подсчета и проверки контрольной суммы для файла. Есть два типа Checksum : Adler32 (который быстрее) и CRC32 (который медленнее, но немного более аккуратный).

Для каждого файла, добавляемого в архив, вы должны вызвать putNextEntry( ) и передать ему объект ZipEntry. Объект ZipEntry содержит обширный интерфейс, который позволит вам получить и установить все данные, доступные для этого конкретного включения в ваш Zip файл: имя, компрессированный и не компрессированный размеры, дата, CRC контрольная сумма, дополнительное поле данных, комментарий, метод компрессии и есть ли включаемые директории. Однако, хотя формат Zip имеет возможность установки пароля, это не поддерживается в Zip библиотеке Java. И хотя CheckedInputStream и CheckedOutputStream поддерживают обе контрольные суммы Adler32 и CRC32, класс ZipEntry поддерживает только интерфейс для CRC. Это является ограничением лежащего в основе Zip формата, и это может ограничить вас в использовании быстрого Adler32.

Для чтения контрольной суммы вы должны как-то получить доступ к ассоциированному объекту Checksum. Здесь получается ссылка на объекты CheckedOutputStream и CheckedInputStream, но вы также могли просто владеть ссылкой на объект Checksum.

Трудным методом для потоков Zip является setComment( ). Как показано выше, вы можете установить комментарий, когда вы записываете файл, но нет способа получить коментарий в ZipInputStream. Комментарии появились для полной поддержки базиса включение-за-включением только через ZipEntry.

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

Java архивы (JAR’ы)

Формат Zip также используется в файле, формата JAR (Java ARchive), который является способом сбора группы файлов в один компрессированный файл, так же как и Zip. Однако, как и все остальное в Java, JAR файлы являются кроссплатформенными, так что вам не нужно беспокоится о возможностях платформы. Вы также можете включить звуковой и графический файл наряду с файлами классов.

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

JAR файл состоит из единого файла, содержащего набор файлов, упакованных с помощью Zip, наряду с “ манифестом”, который описывает их. (Вы можете создать свой собственный файл манифеста; в противном случае программа jar сделает это за вас.) Вы можете найти больше информации о файлах манифеста JAR в HTML документации для JDK.

Не выполняется автоматическое создание файла манифеста.

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

Вот типичный способ вызова jar :

Инструмент jar не является таким же полезным, как утилита zip. Например, вы не можете добавить или обновить файлы существующего JAR файла; вы можете создать JAR файл только с самого начала. Также вы не можете переместить файл в JAR файл и стереть его сразу, как только он будет перемещен. Однако JAR файл, созданный на одной платформе, может быть прочитан инструментом jar на любой другой платформе (проблема, которая иногда надоедает с утилитой zip).

Как вы увидите в Главе 13, JAR файлы также используются для упаковки JavaBeans.

Сериализация объектов

Сериализация объектов Java позволяет вам взять любой объект, который реализует интерфейс Serializable и включит его в последовательность байт, которые могут быть полностью восстановлены для регенерации оригинального объекта. Это также выполняется при передаче по сети, что означает, что механизм сериализации автоматически поддерживается на различных операционных системах. То есть, вы можете создать объект на машине с Windows, сериализовать его и послать по сети на Unix машину, где он будет корректно реконструирован. Вам не нужно будет беспокоиться о представлении данных на различных машинах, порядке следования байт и любых других деталях.

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

Сериализация объектов была добавлена в язык для поддержки двух главных особенностей. Удаленный вызов методов (RMI) в Java позволяет объектам существовать на другой машине и вести себя так, как будто они существуют на вашей машине. Когда посылается сообщение удаленному объекту, необходима сериализация объекта для транспортировки аргументов и возврата значений. RMI обсуждается в Главе 15.

Сериализация объектов так же необходима для JavaBeans, описанных в Главе 13. Когда используется компонент (Bean), информация о его состоянии обычно конфигурируется во время дизайна. Эта информации о состоянии должна сохранятся, а затем восстанавливаться, когда программа запускается; cериализация объектов выполняет эту задачу.

Сериализация объекта достаточно проста, если объект реализует интерфейс Serializable (этот интерфейс похож на флаг и не имеет методов). Когда сериализация была добавлена в язык, многие стандартные библиотеки классов были изменены, чтобы сделать их сериализованными, включая все оболочки примитивных типов, все контейнерные классы и многие другие. Даже объект Class может быть сериализован. (Смотрите Главу 12 о реализации этого.)

Для сериализации объекта вы создаете определенный сорт объекта OutputStream, а затем вкладываете его в объект ObjectOutputStream. После этого вам достаточно вызвать writeObject( ) и ваш объект будет сериализован и послан в OutputStream. Чтобы провести обратный процесс, вы вкладываете InputStream внутрь ObjectInputStream и вызываете readObject( ). То, что приходит, обычно это ссылка на родительский Object, так что вы должны выполнить обратное приведение, чтобы сделать вещи правильными.

Особенно полезное свойство сериализации объектов состоит в том, что при этом сохраняется не только образ объекта, а за ним также следуют все ссылки, содержащиеся в вашем объекте. Эти объекты также сохраняются, а за ними следуют все ссылки из каждого объекта, и т.д. Иногда это называется “ паутиной объектов”, так как единственный объект может быть присоединен к чему-то, и может содержать массив ссылок на объекты точно так же, как и на члены объектов. Если вы поддерживаете собственную схему сериализации объектов, код, поддерживающий все эти ссылки, может свести с ума. Однако сериализация объектов в Java, по видимому, осуществляет это безупречно, используя, несомненно, оптимизированный алгоритм, который исследует всю паутину объектов. Следующий пример проверяет механизм сериализации, создавая “цепочку” связанных объектов, каждый из которых имеет ссылку на следующий сегмент цепочки точно так же, как и массив ссылок указывает на объекты различных классов :

Чтобы сделать пример интереснее, массив объектов Data внутри Worm инициализируется случайными числами. (Этот способ не дает компилятору представление о типе хранимой мета информации.) Каждый сегмент цепочки (Worm) помечается символом (char), который генерируется автоматически в процессе рекурсивной генерации связанного списка Worm. Когда вы создаете Worm, вы говорите конструктору необходимую вам длину. Чтобы сделать следующую ссылку (next), вызывается конструктор Worm с длиной на единичку меньше, и т.д. Последняя ссылка next остается равной null, указывая на конец цепочки Worm.

Все это сделано для создания чего-то достаточно сложного, что не может быть легко сериализовано. Однако действия, направленные на сериализацию, достаточно просты. Как только создается объект ObjectOutputStream из некоторого другого потока, writeObject( ) сериализует объект. Обратите внимание, что вызов writeObject( ) для String такой же. Вы также можете записать все примитивные типы, используя тот же метод DataOutputStream (они задействуют тот же интерфейс).

Здесь есть две различные секции кода, которые выглядят одинаково. Первая пишет и читает файл, а вторая, для разнообразия, пишет и читает ByteArray. Вы можете прочесть и записать объект, используя сериализацию для любого DataInputStream или DataOutputStream, включая, как вы увидите в Главе 15, сеть. Вывод после одного запуска имеет вид:

Вы можете видеть, что десериализованный объект на самом деле содержит все ссылки, которые были в оригинальном объекте.

Обратите внимание, что в процессе десериализации объекта Serializable не вызывается ни конструктор, ни даже конструктор по умолчанию.

Нахождение класса

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

Лучшим способом для ответа на этот вопрос (как обычно) будет проведение эксперимента. Следующий файл содержится в поддиректории для этой главы:

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

Эта программа открывает файл и успешно читает объект mystery. Однако, как только вы попробуете найти что-нибудь об объекте — что требует Class объекта для Alien — виртуальная машина Java (JVM) не сможет найти Alien.class (если он не будет указан в Classpath, чего не должно случится в этом примере). Вы получите ClassNotFoundException. (Еще раз: все свидетельства иной жизни исчезнут прежде, чем доказательства ее существования могут быть проверены!)

Управление сериализацией

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

Вы можете управлять процессом сериализации, реализовав интерфейс Externalizable вместо интерфейса Serializable. Интерфейс Externalizable расширяет интерфейс Serializable и добавляет два метода: writeExternal( ) и readExternal( ), которые автоматически вызываются для вашего объекта во время сериализации и десериализации, так что вы можете выполнить специальные операции.

Вывод для этой программы:

Причина того, что объект Blip2 не восстановлен в том, что происходит попытка сделать нечто, что является причиной исключения. Вы нашли различия между Blip1 и Blip2? Конструктор для Blip1 является public, в то время как конструктор для Blip2 не такой, и поэтому появляется исключение во время восстановления. Попробуйте сделать конструктор Blip2 public и удалите комментарии //!, чтобы увидеть корректный результат.

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

Поля s и i инициализируются только во втором конструкторе, но не в конструкторе по умолчанию. Это значит, что если вы не инициализируете s и i в readExternal( ), они будут равны null (так как хранилище объектов заполняется нулями при первом шаге создания объектов). Если вы закомментируете две строки кода, следующих за фразой “Вы обязаны сделать это”, и запустите программу, вы увидите, что при восстановлении объекта s равно null, а i равно нулю.

Если вы наследуете от объекта с интерфейсом Externalizable, обычно вы будете вызывать методы writeExternal( ) и readExternal( ) базового класса для обеспечения правильного хранения и восстановления компонент базового класса.

Таким образом, чтобы сделать все правильно, вы должны не только записать важные данные из объекта в методе writeExternal( ) (здесь нет стандартного поведения, при котором записывается любой член объекта с интерфейсом Externalizable), но вы также должны восстановить эти данные в методе readExternal( ). Сначала это может немного смущать, потому что поведение конструктора по умолчанию объекта с интерфейсом Externalizable может представить все, как некоторый вид автоматического сохранения и восстановления. Но это не так.

Ключевое слово transient

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

Один способ предохранения важной части вашего объекта от сериализации, заключающий в реализации Externalizable, показан в предыдущем разделе. Но при этом ничего автоматически не сериализуется и вы должны явно сеарилизовать только нужные вам части внутри writeExternal( ).

Однако если вы работаете с Serializable объектом, вся сериализация происходит автоматически. Для управления этим, вы можете выключить сериализацию полей индивидуально, используя ключевое слово transient, которое говорит: “Не беспокойтесь о сохранении и восстановлении этого — я позабочусь об этом”.

Когда объект восстанавливается, поле password заполняется значением null. Обратите внимание, что toString( ) должна проверять значение на равенство null поля password, потому что если вы попробуете собрать объект String, используя перегруженный оператор ‘+’, а этот оператор обнаружит ссылку, равную null, вы получите NullPointerException. (Новые версии Java могут содержать код для предотвращения этой проблемы.)

Вы также можете заметить, что поле date сохраняется и восстановления с диска и не генерируется заново.

Альтернатива Externalizable

Методы должны иметь следующие точные сигнатуры:

С точки зрения дизайна, это мистические вещи. Прежде всего, вы можете подумать так, потому что эти методы не являются частью базового класса или интерфейса Serializable, следовательно, они не будут определены в своем собственном интерфейсе. Но обратите внимание, что они объявлены как private, что означает, что они будут вызываться только другим членом этого класса. Однако на самом деле вы не вызываете их из других членов этого класса, а вместо этого методы writeObject( ) и readObject( ), принадлежащие объекту ObjectOutputStream и ObjectInputStream, вызывают методы writeObject( ) и readObject( ) вашего объекта. (Обратите внимание на мою невероятную сдержанность, из-за которой я не пускаюсь в пространные обличительные речи по поводу использования одних и тех же имен методов здесь. Я просто скажу: путаница.) Вы можете быть удивлены, как объекты ObjectOutputStream и ObjectInputStream получают доступ к private методам вашего класса. Мы можем только иметь в виду, что эта часть составляет магию сериализации.

В любом случае, все, что определено в интерфейсе, автоматически становится public, поэтому, если writeObject( ) и readObject( ) должны быть private, то они не могут быть частью интерфейса. Так как вы должны следовать точным сигнатурам, получаемый эффект тот же самые, как если бы вы реализовали interface.

Может показаться, что когда вы вызываете ObjectOutputStream.writeObject( ), объект с интерфейсом Serializable, который вы передаете, опрашивается (используя рефлексию, не имеет значения) на предмет реализации своего собственного writeObject( ). Если это так, то нормальный процесс сериализации пропускается, и вызывается writeObject( ). Аналогичная ситуация наблюдается и для readObject( ).

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

Если вы будете использовать стандартный механизм записи не transient частей вашего объекта, вы должны вызвать defaultWriteObject( ), как первое действие writeObject( ) и defaultReadObject( ), как первое действие readObject( ). Это странный вызов методов. Он может показать, например, что вы вызываете defaultWriteObject( ) для ObjectOutputStream и не передаете ему аргументов, но все же как-то происходит включение и узнавание ссылки на ваш объект и способа записи всех не transient частей. Мираж.

Работа с версиями

Возможно, что вам захочется изменить версию сериализованного класса (объекты оригинального класса могут храниться, например, в базе данных). Это допустимо, но вы, вероятно, будете делать это только в специальных случаях, так как это требует дополнительного глубокого понимания, которого мы не достигнем здесь. Документация по JDK в формате HTML, доступная на java.sun.com, описывает эту тему достаточно полно.

Вы также должны обратить внимание, что в HTML документация JDK многие комментарии начинаются с предупреждения:

Это происходит потому, что механизм работы с версиями слишком прост для надежной работы во всех ситуациях, особенно с JavaBeans. Он работает корректно для дизайна и это то, о чем говорит это предупреждение.

Использование устойчивости

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

Вот пример, показывающий эту проблему:

Одна вещь, которая интересна здесь, состоит в возможности использовать сериализацию объекта через массив байт, как способ выполнения “глубокого копирования” любого объекта с интерфейсом Serializable. (Глубокое копирование означает, что вы дублируете всю паутину объектов, а не просто основной объект и принадлежащие ему ссылки.) Более глубоко копирование освещено в Приложении А.

Конечно, вы ожидаете, что десериализованные объекты имеют адреса, отличные от первоначальных. Но обратите внимание, что в animals1 и animals2 появляется один и тот же адрес, включая ссылки на объект House, который они оба разделяют. С другой стороны, когда восстанавливается animals3, у системы нет способа узнать, что объекты в этом потоке являются алиасами объектов первого потока, так что при этом создается полностью отличная паутина объектов.

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

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

Класс Shape реализует интерфейс Serializable, так что все, что наследуется от Shape, автоматически реализует Serializable. Каждый Shape содержит данные, а каждый наследуемый от Shape класс содержит статическое поле, определяющее цвет всех этих Shape. (Помещение статического поля в базовый класс приведет к тому, что будет существовать только одно поле, так как статическое поле не дублируется для наследуемых классов.) Методы базового класса могут быть перекрыты для установки цвета для различных типов (статические методы не имеют динамических ограничений, так что это обычные методы). Метод randomFactory( ) создает различные объекты Shape при каждом вызове, используя случайные значения для данных Shape.

Circle и Square являются прямым расширением Shape; отличия только в том, что Circle инициализирует color в точке определения, а Square инициализирует его в конструкторе. Дискуссию относительно Line пока отложим.

В main( ) используется один ArrayList для хранения объектов Class, а другой для хранения образов. Если вы не задействовали аргумент командной строки, создается shapeTypes ArrayList, и добавляются объекты Class, а затем создается ArrayList shapes, и в него добавляются объекты Shape. Далее, все значения static color устанавливаются равными GREEN, и все сериализуется в файл CADState.out.

Вы можете видеть, что значения xPos, yPos и dim были успешно сохранены и восстановлены, но при восстановлении статической информации произошли какие-то ошибки. Везде на входе имели “3”, но на выходе этого не получили. Circle имеет значение 1 (RED, как это определено), а Square имеет значение 0 (Помните, что он инициализировался в конструкторе). Это похоже на то, что static не сериализовался совсем! Это верно, несмотря на то, что класс Class реализует интерфейс Serializable, он не делает того, что вы от него ожидаете. Так что если вы хотите сериализовать statics, вы должны сделать это сами.

Это то, для чего нужны статические методы serializeStaticState( ) и deserializeStaticState( ) в Line. Вы можете видеть, что они явно вызываются как часть процесса сохранения и восстановления. (Обратите внимание, что порядок записи в файл сериализации и чтения из него должен сохранятся). Таким образом, чтобы CADState.java работал корректно, вы должны :

Токенизация(Tokenizing) ввода


StreamTokenizer

Рассмотрим программу, подсчитывающую встречающихся слов в текстовом файле:

Представление слов в сортированном виде проще выполнить при хранении данных в TreeMap, который автоматически организует ключи в сортированном порядке (смотрите Главу 9). Когда вы получите набор ключей, используя keySet( ), они также будут отсортированы.

Для открытия файла используется FileReader, а для деления файла на слова, создается StreamTokenizer из FileReader, помещенного в BufferedReader. Для StreamTokenizer, существует стандартный список разделителей, и вы можете добавить еще с помощью нескольких методов. Здесь используется ordinaryChar( ) для того, чтобы сказать: “Этот символ не является тем, чем я интересуюсь”, так что синтаксический анализатор не будет включать его, как часть любого слова, которые он создает. Например, фраза st.ordinaryChar(‘.’) означает, что точка не будет включаться, как часть анализируемого слова. Вы можете найти более подробную информацию в HTML документации по JDK на java.sun.com.

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

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

WordCount не является типом TreeMap, так как она не была унаследована. Она выполняет определенный тип функциональности, так что даже хотя методы keys( ) и values( ) должны быть открытыми, это все еще не означает, что должно использоваться наследование, так как некоторые методы TreeMap здесь не подходят. Кроме того, другие методы, такие как getCounter( ), возвращающие Counter для определенной String, и sortedKeys( ), производящие Iterator, завершают изменения в интерфейсе WordCount.

StringTokenizer

Хотя он не является частью библиотеки ввода/вывода, StringTokenizer имеет во многом сходную функциональность, что и описанный здесь StreamTokenizer.

StringTokenizer возвращает значащие элементы из строки по одной. Эти значащие элементы являются последовательностью символов, разделенных символами табуляции, пробелами и символами перевода строки. Таким образом, значащими элементами строки “Куда делась моя кошка?” являются “Куда”, “делась”, “моя” и “кошка?”. Как и в случае StreamTokenizer, вы можете настроить StringTokenizer, чтобы он разбивал ввод любым способом, который вам нужен, но с помощью StringTokenizer вы можете сделать это, передав второй аргумент в конструктор, который имеет тип String и является разделителем, который вы хотите использовать. В общем, если вам нужна большая изощренность, используйте StreamTokenizer.

Вы запрашиваете у объекта StringTokenizer следующий значащий элемент строки, используя метод nextToken( ), который возвращает либо следующий значащий элемент, либо пустую строку, которая указывает, что более элементов не осталось.

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

Анализ происходит для каждой строки, происходит вход в цикл while и из строки извлекается значащий элемент. Обратите внимание, что первая инструкция if, которая командует continue (вернуться назад к началу цикла и начать его заново), если значащий элемент не является ни словом «I», ни “Are”. Это означает, что будут извлекаться значащие элементы до тех пор, пока не будет найдено “I” или “Are”. Вы можете решить, что нужно использовать == вместо метода equals( ), но этот оператор не будет работать корректно, так как == сравнивает значения ссылок, а метод equals( ) сравнивает содержимое.

Логика оставшейся части метода analyze( ) заключается в поиске шаблона, с которого начинается фраза “I am sad”, “I am not happy” или “Are you sad?”. Без использования инструкции break этот код был бы еще грязнее, чем он есть. Вы должны знать, что типичный синтаксический анализатор (это примитивный пример одного из них) обычно имеет таблицу таких значащих элементов и часть кода, проходящую по всем состояниям таблицы, после чтения каждого элемента.

Проверка стиля капитализации

В этом разделе мы взглянем на более сложный пример использования ввода/вывода в Java, который также использует токенизацию. Этот проект весьма полезен, потому что он выполняет проверку стиля, чтобы убедится, что ваша капитализация соответствует стилю Java, который можно найти на java.sun.com/docs/codeconv/index.html. Он открывает .java файл в текущем директории и извлекает все имена классов и идентификаторов, затем показывает, если какой-то из них не соответствует стилю Java.

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

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

Вы должны знать, что программа не является точной; есть несколько моментов, когда она будет указывать на то, что она считает проблемой, но, взглянув на код, вы увидите, что ничего не нужно менять. Это немного раздражает, но это гораздо легче, чем пытаться найти все эти случаи, пристально вглядываясь в код.

Класс MultiStringMap является инструментом, позволяющим вам ставить в соответствие группу строк и каждое ключевое включение. Он использует HashMap (в этот раз через наследование). В качестве ключевых значений используются единичные строки, которые ставятся в соответствие значению ArrayList. Метод add( ) просто проверяет, есть ли уже такое ключевое значение в HashMap, а если его нет, помещает его туда. Метод getArrayList( ) производит ArrayList определенных ключей, а printValues( ), который особенно полезен для отладки, печатает все значения ArrayList, получая ArrayList.

Для облегчения жизни все имена классов стандартной библиотеки Java помещаются в объект Properties (из стандартной библиотеки Java). Помните, что объект Properties является типом HashMap, который хранит только объекты String и для ключевого значения, и для хранимого элемента. Однако он может быть сохранен на диске и восстановлен с диска в одном вызове метода, так что он идеален в качестве хранилища имен. На самом деле нам нужен только список имен, но HashMap не может принимать null ни для ключевых значений, ни для хранящихся значений. Так что один и тот же объект будет использоваться и для ключа, и для значения.

Для классов и идентификаторов, которые будут обнаружены в определенном директории, используются две MultiStringMap: classMap и identMap. Также, когда запускается программа, она загружает хранилище стандартных имен классов в объект Properties, называемый classes, а когда обнаруживается новое имя класса в локальном директории, то оно добавляется и в classes, и в classMap. Таким образом, classMap может использоваться для обхода всех классов в локальном директории, а classes может использоваться для проверки, является ли текущий значащий элемент именем класса (что указывается определением объекта или началом метода, так как захватывается следующий значащий элемент — до точки с запятой — и помещается в identMap).

Конструктор по умолчанию для ClassScanner создает список имен, используя JavaFilter, показанный в конце файла, который реализует интерфейс FilenameFilter. Затем вызывается scanListing( ) для каждого имени файла.

Внутри scanListing( ) открывается файл исходного кода и передается в StreamTokenizer. В документации есть функции slashStarComments( ) и slashSlashComments( ), предназначенные для отсеивания коментариев, которым передается true, но это выглядит некорректно, так как это плохо работает. Поэтому эти строки закомментированы, а комментарии извлекаются другим методом. Чтобы извлечь комментарий, “/” должен трактоваться как обычный символ, и нужно не позволять StreamTokenizer собирать его как часть комментария, поэтому метод ordinaryChar( ) говорит StreamTokenizer, чтобы он не делал это. Это также верно в отношении точки (“.”), так как мы хотим иметь метод, который бы извлекал индивидуальные идентификаторы. Однако символ подчеркивания, который трактуется StreamTokenizer как индивидуальный символ, должен оставляться как часть идентификатора, так как он появляется в таких значениях типа static final, как TT_EOF, и т. д., очень популярных в этой программе. Метод wordChars( ) принимает диапазон символов, которые вы хотите добавить к остающимся внутри значащего элемента, анализирующегося одним словом. Наконец, когда анализируете однострочный комментарий или обнаруживаете строку, для которой необходимо определить конец строки, то при вызове eolIsSignificant(true) конец строки будет обнаружен раньше, чем он будет получен StreamTokenizer.

Оставшаяся часть scanListing( ) читает и реагирует на значащие элементы, пока не встретится конец файла, которых будет обнаружен, когда nextToken( ) вернет значение final static StreamTokenizer.TT_EOF.

Если значащим элементом является “/”, он потенциально может быть комментарием, так что вызывается eatComments( ), чтобы разобраться с этим. Но нас будут интересовать другие ситуации, когда мы имеем дело со словом, для которого есть несколько специальных случаев.

Если это слово class или interface, то следующий значащий элемент представляет имя класса или интерфейса, и оно помещается в classes и classMap. Если это слово import или package, то нам не нужна оставшаяся часть строки. Все остальное должно быть идентификатором (которые нас интересуют) или ключевым словом (которые нас не интересуют, но все они написаны в нижнем регистре, так что они не портят рассматриваемые нами вещи). Они добавляются в identMap.

Метод discardLine( ) является простым инструментом, ищущим конец строки. Обратите внимание, что при каждом получении значащего элемента вы должны проверять конец строки.

Метод eatComments( ) вызывается всякий раз, когда обнаружен слеш в главном цикле анализа. Однако это не обязательно означает, что обнаружен комментарий, так что должен быть извлечен следующий значащий элемент, чтобы проверить, не является ли он слешем (в этом случае строка пропускается) или звездочкой. Но если это ни то, ни другое, это означает, что тот значащий элемент, который вы только что извлекли, необходимо вернуть в главный цикл анализа! К счастью, метод pushBack( ) позволяет вам “втолкнуть назад” текущий элемент во входной поток, поэтому, когда главный цикл анализа вызовет nextToken( ), то он получит то, что вы только что втолкнули обратно.

По соглашению, метод classNames( ) производит массив из всех имен, содержащихся в classes. Этот метод не используется в программе, но он очень полезен для отладки.

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

Оставшаяся часть текста программы занимается методом main( ), занимается обработкой аргументов командной строки и определяет, хотите ли вы создать хранилище имен из стандартной библиотеки Java, или хотите проверить написанный вами код. В обоих случаях он создает объект ClassScanner.

Резюме

Библиотека потоков ввода/вывода java удовлетворяет основным требованиям: вы можете выполнить чтение и запись с консолью, файлом, блоком памяти или даже через Internet (как вы увидите в Главе 15). С помощью интерфейсов вы можете создать новые типы объектов ввода и вывода. Вы также можете использовать простую расширяемость объектов потоков, имея в виду, что метод toString( ) вызывается автоматически, когда вы передаете объект в метод, который ожидает String (ограничение Java на “автоматическое преобразование типов”).

Есть несколько вопросов, оставшихся без ответа в документации и дизайне библиотеке потоков ввода/вывода. Например, было бы неплохо, если бы вы могли сказать, что хотите появление исключения при попытке перезаписи существующего файла, когда вы открываете его для вывода — некоторые системы программирования позволяют вам открыть файл только для вывода, только если он еще не существует. В Java это означает, что вы способны использовать объект File для определения существования файла, потому что, если вы откроете его, как FileOutputStream или FileWriter, он всегда будет перезаписан.

Библиотека потоков ввода/вывода вызывает смешанные чувства; она делает много работы и она компактна. Но если вы не готовы понимать шаблон декоратора, то дизайн становится интуитивно не понятен, поэтому есть простор для дополнительных исследований и обучения. Это то же не все: нет поддержки определенного рода форматированного вывода, который поддерживают практически все пакеты ввода/вывода других языков.

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

Упражнения

Решения для выбранных упражнений могут быть найдены в электронной документации The Thinking in Java Annotated Solution Guide, доступной за малую плату на www.BruceEckel.com.

[58] XML является другим способом решения проблемы перемещения данных между различными платформами, которая не зависит от того, есть ли Java на всех платформах. Однако инструментарий Java поддерживает XML.

[59] Глава 13 покажет даже более последовательное решение этого: GUI программу со скроллируемой текстовой областью.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

cСоздает новый или пустой архив.
tСписок содержания.
xИзвлечь все файлы.
x fileИзвлекает указанный файл.
fГоворит: “Я дам тебе имя файла”. Если вы не используете это, jar поймет, что ввод должен идти через стандартный ввод или, если создается файл, вывод происходит через стандартный вывод.
mГоворит о том, что первый аргумент будет именем файла манифеста, созданного пользователем.
vГенерирует подробный вывод, описывающий то, что делает jar.
0Только хранение файлов; не компрессирует файлы (используйте для создания JAR файла, который вы можете поместить в ваш classpath).
M