gb-java-devel/jtc5-05-abstract.tex

827 lines
99 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[j-spec.tex]{subfiles}
\begin{document}
\setcounter{section}{4}
\setlength{\columnsep}{22pt}
\pagestyle{plain}
\sloppy
\tableofcontents
\section{Специализация: тонкости работы}
\subsection*{В предыдущем разделе}
Рассмотрены понятия внутренних и вложенных классов; процессы создания, использования и расширения перечислений. Подробно рассмотрены исключения с точки зрения ООП, их философия и тесная связь с многопоточностью в Java, обработка, разделение понятия штатных и нештатных ситуаций.
\subsection*{В этом разделе}
Файловые системы и представление данных в запоминающих устройствах; Начало рассмотрения популярных пакетов ввода-вывода \code{java.io}, \code{java.nio}. Более подробно будет разобран один из самых популярных ссылочных типов данных \code{String} и механики вокруг него.
\begin{itemize}
\item \nom{Файл}{именованная область данных на носителе информации, используемая как базовый объект взаимодействия с данными в операционных системах.};
\item \nom{Загрузчик}{системное программное обеспечение, обеспечивающее загрузку операционной системы непосредственно после включения компьютера (процедуры POST) и начальной загрузки.};
\item \nom{Разделы}{(англ. partition), pаздел диска (англ. disk partition) -- часть долговременной памяти накопителя данных (жёсткого диска, SSD, USB-накопителя), логически выделенная для удобства работы, и состоящая из смежных блоков.};
\item \nom{BIOS}{(англ. basic input/output system -- «базовая система ввода-вывода») -- набор микропрограмм, реализующих низкоуровневые API для работы с аппаратным обеспечением компьютера, а также создающих необходимую программную среду для запуска операционной системы у IBM PC-совместимых компьютеров.};
\item \nom{ФС}{Файловая система (File System) -- один из ключевых компонентов всех операционных систем. В ее обязанности входит структуризация, чтение, хранение, запись файловой документации. Она напрямую влияет на физическое и логическое строение данных, особенности их формирования, управления, допустимый объем файла, количество символов в его названии и прочие.};
\item \nom{FAT}{(англ. File Allocation Table «таблица размещения файлов») -- классическая архитектура файловой системы, которая из-за своей простоты всё ещё широко применяется для флеш-накопителей.};
\item \nom{NTFS}{(аббревиатура от англ. new technology file system -- «файловая система новой технологии») -- стандартная файловая система для семейства операционных систем Windows NT фирмы Microsoft.};
\item \nom{Ввод-вывод}{взаимодействие между обработчиком информации (например, компьютер) и внешним миром, который может представлять как человек (субъект), так и любая другая система обработки информации};
\item \nom{Потоки в-в}{объекты, из которых можно считать данные и в которые можно записывать данные};
\item \nom{Строка}{ряд знаков, написанных или напечатанных в одну линию. Строка может также означать строковый тип данных в программировании.};
\item \nom{String}{Класс в Java, предназначенный для работы со строками. Все строковые литералы, определенные в Java программе -- это экземпляры класса String};
\end{itemize}
\subsection{Файловая система}
Повествование вплотную подошло к работе языка в части общения программы со внешним миром, а делать это не разобравшись в тонкостях работы файловой системы не эффективно. В этом подразделе находится материал, напрямую не относящийся к Java.
\begin{frm} \info Файловая система (File System) -- FS, ФС -- один из ключевых компонентов всех операционных систем. В ее обязанности входит структуризация, чтение, хранение, запись файловой документации. Она напрямую влияет на физическое и логическое строение данных, особенности их формирования, управления, допустимый объем файла, количество символов в его названии и прочие.
\end{frm}
\subsubsection{MBR, GPT}
ОС Linux, например, предоставляет возможность разбивки жесткого диска компьютера на отдельные разделы. Пользователи могут определить их границы по так называемым таблицам разделов. \textbf{Основная загрузочная запись (MBR)} и \textbf{таблица разделов GUID (GPT)} -- это два стиля формата разделов, которые позволяют компьютеру загружать операционную систему с жесткого диска, а также индексировать и упорядочивать данные.
Основная загрузочная запись (MBR) -- устаревшая форма разделения загрузочного сектора, первый сектор диска, который содержит информацию о том, как разбит диск. Он также содержит загрузчик, который сообщает компьютеру, как загрузить ОС. Main Boot Record состоит из трех частей:
\begin{itemize}
\item Основной загрузчик -- MBR резервирует первые байты дискового пространства для основного загрузчика. Windows размещает здесь очень упрощенный загрузчик, в то время как другие ОС могут размещать более сложные многоступенчатые загрузчики.
\item Таблица разделов диска -- таблица разделов диска находится в нулевом цилиндре, нулевой головке и первом секторе жёсткого диска. Она хранит информацию о том, как разбит диск. MBR выделяет 16 байт данных для каждой записи раздела и может выделить всего 64 байта. Таким образом, Main Boot Record может адресовать не более четырёх основных разделов или трёх основных раздела и один расширенный раздел.
\item Конечная подпись -- это 2-байтовая подпись, которая отмечает конец MBR. Всегда устанавливается в шестнадцатеричное значение \code{0x55AA}.
\end{itemize}
\begin{frm} \info Вообще, программисты любят всякие шестнадцатеричные подписи вроде \code{0xDEADBEEF}, \code{0xA11F0DAD}, \code{0xDEADFA11}, одну из таких подписей можно было увидеть в первом разделе, \code{0xcafebabe}. Они называются hexspeak.
\end{frm}
Таблица разделов GUID (GPT) -- это стиль формата раздела, который был представлен в рамках инициативы United Extensible Firmware Interface (UEFI). GPT был разработан для архитектурного решения некоторых ограничений MBR. Стиль GPT новее, гибче и надежнее, чем MBR. GPT использует логическую адресацию блоков для указания блоков данных. Первый блок помечен как LBA0, затем LBA1, LBA2, и так далее GPT хранит защитную запись MBR в LBA0, свой основной заголовок в логическом блоке и записи разделов в блоках со второго по тридцать третий.
GPT:
\begin{itemize}
\item Защитная MBR;
\item Основной тег GPT;
\item Записи разделов;
\item Дополнительный GPT.
\item Максимальная емкость раздела 9.4ZB;
\item 128 первичных разделов;
\item Устойчив к повреждению первичного раздела, так как имеет вторичный GPT;
\item Возможность использовать функции UEFI.
\end{itemize}
\textbf{Файловая система} -- это архитектура хранения информации, размещенной на жестком диске и в оперативной памяти. С её помощью пользователь получает доступ к структуре ядра системы.
На что влияет файловая система?
\begin{enumerate}
\item скорость обработки данных;
\item сбережение файлов;
\item оперативность записи;
\item допустимый размер блока;
\item возможность хранения информации в оперативной памяти;
\item способы корректировки пользователями ядра
\item и пр.
\end{enumerate}
\subsubsection{Linux}
ОС Linux предоставляет возможность устанавливать в каждый отдельный блок свою файловую систему, которая и будет обеспечивать порядок поступающих и хранящихся данных, поможет с их организацией. Каждая ФС работает на наборе правил. Исходя из этого и определяется в каком месте и каким образом будет выполняться хранение информации. Эти правила лежат в основе иерархии системы, то есть всего корневого каталога. От того, насколько правильно администратор компьютера выберет тип файловой системы для каждого раздела, зависит ряд параметров, таких как: оперативность записи, скорость обработки данных, сбережение файлов, допустимый размер блока, возможность хранения информации в оперативной памяти и другие.
В файловой системе, хранятся файлы. Файлы в Linux -- это особенная отдельная категория данных. С точки зрения ОС -- всё файл. Все файлы делятся на категории.
\begin{itemize}
\item Regular File -- Предназначены для хранения двоичной и символьной информации. Это жесткая ссылка, ведущая на фактическую информацию, размещенную в каталоге. Если этой ссылке присвоить уникальное имя, получим Named Pipe, то есть именованный канал.
\item Device File -- файлы для устройств, туннелей. Речь идет о физических устройствах, представленных в ОС файлами. Могут классифицировать специальные символы и блоки. Обеспечивают мгновенный доступ к дисководам, принтерам, модемам, воспринимая их как простой файл с данными.
\item Soft Link -- мягкая (символьная) ссылка. Отвечает за мгновенный доступ к файлам, размещенным на любых носителях информации. В процессе копирования, перемещения и других действий пользователя, работающего по ссылке, будет выполняться операция над документом, на который ссылаются.
\item Directories -- Каталоги. Обеспечивают быстрый и удобный доступ к каталогам. Представляет собой файл с директориями и указателями на них. Это некого рода картотека: в папках размещаются документы, а в директориях дополнительные каталоги.
\item Block devices and sockets -- Блочные и символьные устройства. Выделяют интерфейс, необходимый для взаимодействия приложений с аппаратной составляющей. Каналы и сокеты. Отвечают за взаимодействие внутренних процессов в операционной системе.
\end{itemize}
\subsubsection{Windows}
Линейка файловых систем для Windows: какую роль они играют в работе системы и как они развивались.
\textbf{FAT(16) File Allocation Table}
Использовалась для MS-DOS 3.0, Windows 3.x, Windows 95, Windows 98, Windows NT/2000. Была разработана достаточно давно и предназначалась для работы с небольшими дисковыми и файловыми объемами, простой структурой каталогов. Таблица размещается в начале тома, причем хранятся две ее копии (в целях обеспечения большей устойчивости). Данная таблица используется операционной системой для поиска файла и определения его физического расположения на жестком диске. В случае повреждения и таблицы и ее копии чтение файлов операционной системой становится невозможно.
\textbf{Файловая система FAT32}
\begin{itemize}
\item возможность перемещения корневого каталога
\item возможность хранения резервных копий
\end{itemize}
Начиная с Windows 95, компания Microsoft начинает активно использовать в своих операционных системах FAT32 -- тридцатидвухразрядную версию FAT. FAT32 стала обеспечивать более оптимальный доступ к дискам, более высокую скорость выполнения операций ввода/вывода, а также поддержку больших файловых объемов (объем диска до 2 Тбайт).
\textbf{Файловая система NTFS} -- (от англ. New Technology File System), файловая система новой технологии
\begin{itemize}
\item права доступа
\item Шифрование данных
\item Дисковые квоты
\item Хранение разреженных файлов
\item Журналирование
\end{itemize}
Ни одна из версий FAT не обеспечивает хоть сколько-нибудь приемлемого уровня безопасности. Это, а также необходимость в добавочных файловых механизмах (сжатия, шифрования) привело к необходимости создания принципиально новой файловой системы. NTFS. В ней для файлов и папок могут быть назначены права доступа (на чтение, на запись и т.д.). Благодаря этому существенно повысилась безопасность данных и устойчивость работы системы. Одной из основных целей создания NTFS было обеспечение скоростного выполнения операций над файлами (копирование, чтение, удаление, запись), а также предоставление дополнительных возможностей: сжатие данных, восстановление поврежденных файлов системы на больших дисках и т.д. Другой основной целью создания NTFS была реализация повышенных требований безопасности.
Файловая система FAT для современных жестких дисков просто не подходит (ввиду ее ограниченных возможностей). Что касается FAT32, то ее еще можно использовать, но уже с оговорками.
\subsubsection{Файловые системы}
\begin{itemize}
\item Ext (extended) FS. Это расширенная файловая система, одна из первых. Была запущена в работу еще в 1992 году. В основе ее функциональности лежала ФС UNIX. Основная задача состояла в выходе за рамки конфигурации классической файловой системы MINIX, исключить ее ограничения и повысить эффективность администрирования. Сегодня она применяется крайне редко.
\item Ext2. Вторая, более расширенная вервия ФС, паявшаяся на рынке в 1993 году. По своей структуре продукт аналогичный Ext. Изменения коснулись интерфейса, конфигурации. Увеличился объем памяти, производительность. Максимально допустимый объем файлов для хранения (указывается в настройках) -- 2 ТБ. Ввиду невысокой перспективности применяется на практике редко.
\item Ext3. Третье поколение Extended FS, введенное в использование в 2001 году. Уже относится к журналируемой. Позволяет хранить логи изменения, обновления файлов данных записываются в отдельный журнал еще до того, как эти действия будут завершены. После перезагрузки ПК, такая ФС позволит восстановить файлы благодаря внедрению в систему специального алгоритма.
\item Ext4. Четвертое поколение Extended FS, запущенное в 2006 году. Здесь максимально убраны всевозможные ограничения, присутствующие в предыдущих версиях. Сегодня именно она по умолчанию входит в состав большей части дистрибутивов Линукс. Передовой ее нельзя назвать, но стабильность и надежность работы здесь в приоритете. В Unix системах применяется повсеместно.
\item JFS. Журналируемая система, первый аналог продуктам из основной группы, разработанная специалистами IBM под AIX UNIX. Отличается постоянством и незначительными требованиями к работе. Может использоваться на многопроцессорных ПК. Но в ее журнале сохраняются ссылки лишь на метаданные. Поэтому если произойдет сбой, автоматически подтянутся устаревшие версии данных.
\item ReiserFS. Создана Гансом Райзером исключительно под Linux и названа в его честь. По своей структуре -- это продукт, похожий на Ext3, но с более расширенными возможностями. Пользователи могут соединять небольшие файлы в более масштабные блоки, исключая фрагментацию, повышая эффективность функционирования. Но в случае непредвиденного отключения электроэнергии есть вероятность потерять данные, которые будут группироваться в этот момент.
\item XFS. Еще один представитель группы журналируемых файловых систем. Отличительная особенность: в логи программа будет записывать только изменения в метаданных. Из преимуществ выделяют быстроту работы с объемной информацией, способность выделять место для хранения в отложенном режиме. Позволяет увеличивать размеры разделов, а вот уменьшать, удалять часть нельзя. Здесь также есть риск потери данных при отключении электроэнергии.
\item Btrfs. Отличается повышенной стойкостью к отказам и высокой производительностью. Удобная в работе, позволяет легко восстанавливать информацию, делать скриншоты. Размеры разделов можно менять в рабочем процессе. По умолчанию входит в OpenSUSE и SUSE Linux. Но обратная совместимость в ней нарушена, что усложняет поддержку.
\item F2FS. Разработка Samsung. Уже входит в ядро Linux. Предназначена для взаимодействия с хранилищем данных флеш-памяти. Имеет особую структуру: носитель разбивается на отдельные части, которые в свою очередь дополнительно еще делятся.
\item OpenZFS. Это ответ на вопрос какую файловую систему выбрать для Ubuntu -- она автоматически включена в поддержку ОС уже более 6 лет назад. Отличается высоким уровнем защиты от повреждения информации, автоматическим восстановлением, поддержкой больших объемов данных.
\item EncFS. Шифрует данные и пересохраняет их в этом формате в указанную пользователем директорию. Надо примонтировать ФС чтобы обеспечить доступ в расшифрованной информации.
\item Aufs. С ее помощью отдельные File Systems можно группировать в один раздел.
\item NFS. Позволит через сеть примонтировать ФС удаленного устройства.
\item Tmpfs. Предусмотрена возможность размещения пользовательских файлов непосредственно в оперативной памяти ПК. Предполагает создание блочного узла определенного размера с последующим подключением к папке. При необходимости данные можно будет удалять.
\item Procfs. По умолчанию размещена в папке proc. Буде содержать полный набор данных относительно процессов, запущенных в системе и непосредственно в ядре в режиме реального времени.
\item Sysfs. Такая ФС позволит пользователю задавать и отменять параметры ядра во время выполнения задачи.
\end{itemize}
\subsubsection{Задание для самопроверки}
\begin{enumerate}
\item MBR -- это
\begin{enumerate}
\item main boot record;
\item master BIOS recovery;
\item minimizing binary risks.
\end{enumerate}
\item Что такое GPT?
\begin{enumerate}
\item General partition trace;
\item GUID partition table;
\item Greater pass timing.
\end{enumerate}
\item Открытый вопрос: Что такое файловая система?
\item Возможно ли использовать разные файловые системы в рамках одной ОС?
\end{enumerate}
\subsection{Файловая система и представление данных}
\subsubsection{File}
До появления Java 1.7 все операции проводились с помощью класса \code{File}.
В Java есть специальный класс (File), с помощью которого можно управлять файлами на диске компьютера. Для того чтобы управлять содержимым файлов, есть другие классы: \code{FileInputStream}, \code{FileOutputStream} и другие. Под управлением файлами понимается, что их можно создавать, удалять, переименовывать, узнавать их свойства и пр.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Создание объекта файла}]
File file = new File("file.txt");
\end{lstlisting}
Здесь происходит привычное создание объекта, причём в параметре конструктора задано так называемое относительное имя файла, то есть только имя и расширение, без указания полного пути, а значит файл будет открыт там, где исполняется программа.
С помощью объекта файла возможно работать и с директориями.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использования файла для директории}]
File folder = new File(".");
for (File file : folder.listFiles())
System.out.println(file.getName());
\end{lstlisting}
Такой код выведет на экран всё содержимое \textit{текущей} директории, о чём говорит точка в строке с именем файла. Если просто указать имя файла, то будет использован файл в той же папке, что исполняемая программа, а если указана точка, то это означает использование непосредственно данной папки. метод \code{listFiles()} возвращает список файлов из указанной папки. А метод \code{getName()} выдаёт имя файла с расширением.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Методы класса \code{File}}]
System.out.println("Is it a folder - " + folder.isDirectory());
System.out.println("Is it a file - " + folder.isFile());
File file = new File("./Dockerfile");
System.out.println("Length file - " + file.length());
System.out.println("Absolute path - " + file.getAbsolutePath());
System.out.println("Total space on disk - " + folder.getTotalSpace());
System.out.println("File deleted - " + file.delete());
System.out.println("File exists - " + file.exists());
System.out.println("Free space on disk - " + folder.getFreeSpace());
\end{lstlisting}
Класс файл предоставляет ряд методов для манипуляции файлами и директориями.
\begin{itemize}
\item проверить, является ли объект файлом или директорией;
\item узнать размер файла в байтах и его абсолютный путь;
\item порядковый номер жёсткого диска, на котором расположен файл;
\item удалить файл (метод возвращает истину или ложь, как результат);
\item проверить, существует ли такой файл;
\item узнать, сколько ещё свободного места доступно на диске;
\item и другие.
\end{itemize}
\begin{frm} \info Фактически у класса \code{File} почти все методы дублированы: одна версия возвращает (и принимает в качестве параметра) \code{String}, вторая \code{File}.
\end{frm}
\subsubsection{Paths, Path, Files, FileSystem}
В Java 1.7 создатели языка решили изменить работу с файлами и каталогами.
У класса \code{File} существует ряд недостатков. Например, в нем нет метода \code{copy()}, который позволил бы скопировать файл. В классе \code{File} достаточно много методов, которые возвращают \code{boolean} значения.
Вместо единого класса \code{File} появились три класса: \code{Paths}, \code{Path} и \code{Files}. Также появился класс \code{FileSystem}, который предоставляет интерфейс к файловой системе.
\begin{frm} \excl
\begin{itemize}
\item [] \textbf{URI} -- Uniform Resource Identifier (унифицированный идентификатор ресурса);
\item [] \textbf{URL} -- Uniform Resource Locator (унифицированный определитель местонахождения ресурса);
\item [] \textbf{URN} -- Unifrorm Resource Name (унифицированное имя ресурса).
\end{itemize}
\end{frm}
\code{Paths} -- это совсем простой класс с единственным статическим методом \code{get()}. Его создали исключительно для того, чтобы из переданной строки или URI получить объект типа Path.
Фактически, \code{Path} -- это переработанный аналог класса \code{File}. Работать с ним значительно проще, чем с \code{File}. Например, метод \code{getParent()}, возвращает родительский путь для текущего файла в виде строки. Но при этом есть метод \code{getParentFile()}, который возвращал то же самое, но в виде объекта \code{File}. Это явно избыточно.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование классов \code{Path} и \code{Paths}}]
Path filePath = Paths.get("pics/logo.png");
Path fileName = filePath.getFileName();
System.out.println("Filename: " + fileName);
Path parent = filePath.getParent();
System.out.println("Parent directory: " + parent);
boolean endWithTxt = filePath.endsWith("logo.png");
System.out.println("Ends with filepath: " + endWithTxt);
endWithTxt = filePath.endsWith("png");
System.out.println("Ends with string: " + endWithTxt);
boolean startsWithPics = filePath.startsWith("pics");
System.out.println("Starts with filepath: " + startsWithPics);
\end{lstlisting}
\begin{frm} \excl В методы \code{startsWith()} и \code{endsWith()} нужно передавать путь, а не просто набор символов: в противном случае результатом всегда будет \code{false}, даже если текущий путь действительно заканчивается такой последовательностью символов.
\end{frm}
\begin{verbatim}
Filename: logo.png
Parent directory: pics
Ends with filepath: true
Ends with string: false
Starts with filepath: true
\end{verbatim}
Метод \code{normalize()} -- «нормализует» текущий путь, удаляя из него ненужные элементы.
\begin{frm} \info При обозначении путей часто используются символы \code{"."} (для обозначения текущей директории) и \code{".."} (для родительской директории).
\end{frm}
Если в программе появился путь, использующий \code{"."} или \code{".."}, метод \code{normalize()} позволит удалить их и получить путь, в котором они не будут содержаться.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Метод \code{normailze()}}]
Path path = Paths.get("./sources-draft/../pics/logo.png");
System.out.println(path.normalize());
\end{lstlisting}
\begin{verbatim}
pics/logo.png
\end{verbatim}
\code{Files} — это утилитарный класс, куда были вынесены статические методы из класса \code{File}. Он сосредоточен на управлении файлами и директориями.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование класса \code{Files}}]
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
Path file = Files.createFile(Paths.get("../pics/file.txt"));
System.out.print("Was the file captured successfully in pics directory? ");
System.out.println(Files.exists(Paths.get("../pics/file.txt")));
Path testDirectory = Files.createDirectory(Paths.get("./testing"));
System.out.print("Was the test directory created successfully? ");
System.out.println(Files.exists(Paths.get("./testing")));
file = Files.move(file, Paths.get("./testing/file.txt"), REPLACE_EXISTING);
System.out.print("Is our file still in the pics directory? ");
System.out.println(Files.exists(Paths.get("../pics/file.txt")));
System.out.print("Has our file been moved to testDirectory? ");
System.out.println(Files.exists(Paths.get("./testing/file.txt")));
Path copyFile = Files.copy(file, Paths.get("../pics/file.txt"), REPLACE_EXISTING);
System.out.print("Has our file been copied to pics directory? ");
System.out.println(Files.exists(Paths.get("../pics/file.txt")));
Files.delete(file);
System.out.print("Does the file exist in test directory? ");
System.out.println(Files.exists(Paths.get("./testing/file.txt")));
System.out.print("Does the test directory exist? ");
System.out.println(Files.exists(Paths.get("./testing")));
\end{lstlisting}
\begin{verbatim}
Was the file captured successfully in pics directory? true
Was the test directory created successfully? true
Is our file still in the pics directory? false
Has our file been moved to testDirectory? true
Has our file been copied to pics directory? true
Does the file exist in test directory? false
Does the test directory exist? true
\end{verbatim}
Класс Files позволяет не только управлять самими файлами, но и работать с его содержимым. Для записи данных в файл у него есть метод \code{write()}, а для чтения: \code{read()}, \code{readAllBytes()} и \code{readAllLines()}.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование класса \code{Files}}]
List<String> lines = Arrays.asList(
"The cat wants to play with you",
"But you don't want to play with it");
Path file = Files.createFile(Paths.get("cat.txt"));
if (Files.exists(file)) {
Files.write(file, lines, StandardCharsets.UTF_8);
lines = Files.readAllLines(
Paths.get("cat.txt"), StandardCharsets.UTF_8);
for (String s : lines) {
System.out.println(s);
}
}
\end{lstlisting}
\subsubsection{Задание для самопроверки}
Ссылка на местонахождение -- это:
\begin{enumerate}
\item URI;
\item URL;
\item URN.
\end{enumerate}
\subsection{Потоки ввода-вывода, пакет \code{java.io}}
Подавляющее большинство программ обменивается данными со внешним миром. Это делают любые сетевые приложения -- они передают и получают информацию от других компьютеров и специальных устройств, подключенных к сети. Можно таким же образом представлять обмен данными между устройствами внутри одной машины. Программа может считывать данные с клавиатуры и записывать их в файл, или, наоборот - считывать данные из файла и выводить их на экран. Таким образом, устройства, откуда может производиться считывание информации, могут быть самыми разнообразными файл, клавиатура, входящее сетевое соединение и т.д. То же касается и устройств вывода это может быть файл, экран монитора, принтер, исходящее сетевое соединение и т.п. В конечном счете, все данные в компьютерной системе в процессе обработки передаются от устройств ввода к устройствам вывода.
Реализация системы ввода/вывода осложняется не только широким спектром источников и получателей данных, но еще и различными форматами передачи информации. Ею можно обмениваться в двоичном представлении, символьном или текстовом, с применением некоторой кодировки (кодировок только для русского языка их более 4 типов), или передавать числа в различных представлениях. Доступ к данным может потребоваться как последовательный, так и произвольный. Зачастую для повышения производительности применяется буферизация.
\begin{frm} \info В Java для описания работы по вводу/выводу используется специальное понятие потока данных (stream). Поток данных это абстракция, физически никакие потоки в компьютере никуда не текут.
\end{frm}
Поток связан с некоторым источником, или приемником, данных, способным получать или предоставлять информацию. Потоки делятся на входящие -- читающие данные и исходящие -- передающие (записывающие) данные. Введение концепции stream позволяет абстрагировать основную логику программы, обменивающейся информацией с любыми устройствами одинаковым образом, от низкоуровневых операций с такими устройствами ввода/вывода. Для программы нет разницы, передавать данные в файл или в сеть, принимать с клавиатуры или со специализированного устройства.
В Java потоки естественным образом представляются объектами. Описывающие их классы составляют основную часть пакета \code{java.io}. Все классы разделены на две части одни осуществляют ввод данных, другие вывод.
\subsubsection{Классы \code{InputStream} и \code{OutputStream}}
\begin{frm} \info Почти все классы пакета, осуществляющие ввод-вывод, так или иначе наследуются от InputStream для входных данных, и для выходных от OutputStream.
\end{frm}
\code{InputStream} -- это базовый абстрактный класс для потоков ввода, т.е. чтения.
\code{InputStream} описывает базовые методы для работы со входящими байтовыми потоками данных. Простейшая операция представлена методом \code{read()} (без аргументов). Согласно документации, этот метод предназначен для считывания ровно одного байта из потока, однако возвращает при этом значение типа \code{int}. В том случае, если считывание произошло успешно, возвращаемое значение лежит в диапазоне от 0 до 255 и представляет собой полученный байт (значение \code{int} содержит 4 байта и получается простым дополнением нулями в двоичном представлении). Если достигнут конец потока, то есть в нем больше нет информации для чтения, то возвращаемое значение равно -1. Если же считать из потока данные не удается из-за каких-то ошибок, или сбоев, будет брошено исключение \code{java.io.IOException}. Дело в том, что каналы передачи информации, будь то Internet или, например, жёсткий диск, могут давать сбои независимо от того, насколько хорошо написана программа. А это означает, что нужно быть готовым к ним, чтобы пользователь не потерял нужные данные. Когда работа с входным потоком данных окончена, его следует закрыть. Для этого вызывается метод \code{close()}. Этим вызовом будут освобождены все системные ресурсы, связанные с потоком.
\code{OutputStream} -- это базовый абстрактный класс для потоков вывода, т.е. записи.
В классе \code{OutputStream} аналогичным образом определяются три метода \code{write()} -- один принимающий в качестве параметра \code{int}, второй -- \code{byte[]} и третий -- \code{byte[]}, и два \code{int}-числа. Все эти методы ничего не возвращают. Для записи в поток сразу некоторого количества байт методу \code{write()} передается массив байт. Или, если мы хотим записать только часть массива, то передаем массив \code{byte[]} и два числа -- отступ и количество байт для записи. Реализация потока вывода может быть такой, что данные записываются не сразу, а хранятся некоторое время в памяти. Чтобы убедиться, что данные записаны в поток, а не хранятся в буфере, вызывается метод \code{flush()}. Когда работа с потоком закончена, его следует закрыть, для этого вызывается метод \code{close()}.
\begin{frm} \excl Закрытый поток не может выполнять операции вывода и не может быть открыт заново.
\end{frm}
\subsubsection{Классы \code{ByteArrayInputStream} и \code{ByteArrayOutputStream}}
Самый естественный для программы и простой для понимания источник, откуда можно считывать байты -- это, конечно, массив байт. Класс \code{ByteArrayInputStream} представляет собой поток, считывающий данные из массива байт. Этот класс имеет конструктор, которому в качестве параметра передается массив \code{byte[]}. Соответственно, при вызове методов \code{read()} возвращаемые данные будут браться именно из этого массива. Аналогично, для записи байт в массив применяется класс ByteArrayOutputStream. Этот класс использует внутри себя объект \code{byte[]}, куда записывает данные, передаваемые при вызове методов \code{write()}. Чтобы получить записанные в массив данные, вызывается метод \code{toByteArray()}. В примере будет создан массив, который состоит из трёх элементов: 1, -1 и 0. Затем, при вызове метода \code{read()} данные считывались из массива, переданного в конструктор \code{ByteArrayInputStream}. Обратите внимание, в данном примере второе считанное значение равно 255, а не -1, как можно было бы ожидать. Чтобы понять, почему это произошло, нужно вспомнить, что метод \code{read()} считывает \code{byte}, но возвращает значение \code{int}, полученное добавлением необходимого числа нулей (в двоичном представлении).
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование Byte Array Stream}]
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(1);
out.write(-1);
out.write(0);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
int value = in.read();
System.out.println("First element is - " + value);
value = in.read();
System.out.println("Second element is - " + value +
". If (byte)value - " + (byte)value);
value = in.read();
System.out.println("Third element is - " + value);
\end{lstlisting}
\subsubsection{Классы \code{FileInputStream} и \code{FileOutputStream}}
Класс \code{FileInputStream} используется для чтения данных из файла. Конструктор такого класса в качестве параметра принимает название файла, из которого будет производиться считывание. При указании строки имени файла нужно учитывать, что она будет напрямую передана операционной системе, поэтому формат имени файла и пути к нему может различаться на разных платформах. Если при вызове этого конструктора передать строку, указывающую на несуществующий файл или каталог, то будет брошено \code{java.io.FileNotFoundException}. Если же объект успешно создан, то при вызове его методов \code{read()} возвращаемые значения будут считываться из указанного файла.
Для записи байт в файл используется класс \code{FileOutputStream}. При создании объектов этого класса, то есть при вызовах его конструкторов, кроме имени файла, также можно указать, будут ли данные дописываться в конец файла, либо файл будет полностью перезаписан.
\begin{frm} \excl Если не указан флаг добавления, то всегда сразу после создания \code{FileOutputStream} файл будет \textbf{создан} (содержимое существующего файла будет стёрто).
\end{frm}
При вызовах методов \code{write()} передаваемые значения будут записываться в этот файл. По окончании работы необходимо вызвать метод \code{close()}, чтобы сообщить системе, что работа по записи файла закончена.
\subsubsection{Другие потоковые классы}
\begin{itemize}
\item Классы \code{PipedInputStream} и \code{PipedOutputStream} характеризуются тем, что их объекты всегда используются в паре -- к одному объекту \code{PipedInputStream} привязывается (подключается) один объект \code{PipedOutputStream}. Они могут быть полезны, если в программе необходимо организовать обмен данными между модулями. Более явно выгода от использования проявляется при разработке многопоточных (multithread) приложений.
\item \code{StringBufferInputStream} (deprecated). Иногда бывает удобно работать с текстовой строкой как с потоком байт. Для этого возможно воспользоваться классом \code{StringBufferInputStream}. При создании объекта этого класса необходимо передать конструктору объект \code{String}.
\item Класс \code{SequenceInputStream} объединяет поток данных из других двух и более входных потоков. Данные будут вычитываться последовательно -- сначала все данные из первого потока в списке, затем из второго, и так далее. Конец потока \code{SequenceInputStream} будет достигнут только тогда, когда будет достигнут конец потока, последнего в списке.
\item FilterInputStream и FilterOutputStream и их наследники. Задачи, возникающие при вводе/выводе весьма разнообразны -- это может быть считывание байтов из файлов, объектов из файлов, объектов из массивов, буферизованное считывание строк из массивов и т.д. В такой ситуации решение с использованием простого наследования приводит к возникновению слишком большого числа подклассов. Более эффективно применение надстроек (в ООП этот шаблон называется адаптер). Надстройки -- наложение дополнительных объектов для получения новых свойств и функций. Таким образом, необходимо создать несколько дополнительных объектов -- адаптеров к классам ввода/вывода. В терминах \code{java.io} их называют фильтрами.
\item Класс \code{LineNumberInputStream} во время чтения данных производит подсчет, сколько строк было считано из потока. Номер строки, на которой в данный момент происходит чтение, можно узнать путем вызова метода \code{getLineNumber()}. Также можно и перейти к определенной строке вызовом метода \code{setLineNumber(int lineNumber)}. Этот класс практически разу объявили устаревшим и вместо него используется \code{LineNumberReader} с аналогичным функционалом.
\item \code{PushBackInputStream}. Этот фильтр позволяет вернуть во входной поток считанные из него данные. Такое действие производится вызовом метода \code{unread()}. Понятно, что обеспечивается подобная функциональность за счет наличия в классе специального буфера -- массива байт, который хранит считанную информацию.
\item PrintStream используется для конвертации и записи строк в байтовый поток. В нем определен метод \code{print()}, принимающий в качестве аргумента различные примитивные типы Java, а также тип \code{Object}. При вызове передаваемые данные будут сначала преобразованы в строку, после чего записаны в поток. Если возникает исключение, оно обрабатывается внутри метода \code{print()} и дальше не бросается (узнать, произошла ли ошибка, можно с помощью метода \code{checkError()}). Данный класс также считается устаревшим, и вместо него рекомендуется использовать \code{PrintWriter}, однако старый класс продолжает активно использоваться, поскольку статические поля \code{out} и \code{err} класса \code{System} имеют именно это тип.
\end{itemize}
\subsubsection{\code{BufferedInputStream} и \code{BufferedOutputStream}}
На практике при считывании с внешних устройств ввод данных почти всегда необходимо буферизировать. \code{BufferedInputStream} содержит массив байт, который служит буфером для считываемых данных. То есть, когда байты из потока считываются, либо пропускаются (методом \code{skip()}), сначала заполняется буферный массив, причём, из потока загружается сразу много байт, чтобы не требовалось обращаться к нему при каждой операции \code{read()} или \code{skip()}. \code{BufferedOutputStream} предоставляет возможность производить многократную запись небольших блоков данных без обращения к устройству вывода при записи каждого из них. Сначала данные записываются во внутренний буфер. Непосредственное обращение к устройству вывода и, соответственно, запись в него, произойдет, когда буфер заполнится. Инициировать передачу содержимого буфера на устройство вывода можно и явным образом, вызвав метод \code{flush()}. Для наглядности заполним небольшой файл данными, буквально 10 миллионов символов.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Сравнение простого и буферизующего потоков (шаг 1)}]
String fileName = "test.txt";
InputStream inStream = null;
OutputStream outStream = null;
try {
long timeStart = System.currentTimeMillis();
outStream = new BufferedOutputStream(new FileOutputStream(fileName));
for (int i = 1000000; --i >= 0;) { outStream.write(i); }
long time = System.currentTimeMillis() - timeStart;
System.out.println("Writing time: " + time + " millisec");
outStream.close();
} catch (IOException e) {
System.out.println("IOException: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
\begin{verbatim}
Writing time: 41 millisec
\end{verbatim}
На следующем шаге, прочитав эти символы из файла простым потоком ввода из файла, увидим, что это заняло сколько-то времени.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Сравнение простого и буферизующего потоков (шаг 2)}]
try {
long timeStart = System.currentTimeMillis();
InputStream inStream = new FileInputStream(fileName);
while (inStream.read() != -1) { }
long time = System.currentTimeMillis() - timeStart;
inStream.close();
System.out.println("Direct read time: " + (time) + " millisec");
} catch (IOException e) {
System.out.println("IOException: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
\begin{verbatim}
Direct read time: 2726 millisec
\end{verbatim}
На третьем шаге становится очевидно, что буферизующий поток справляется ровно с той-же задачей на пару порядков быстрее. Выгода использования налицо.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Сравнение простого и буферизующего потоков (шаг 3)}]
try {
long timeStart = System.currentTimeMillis();
inStream = new BufferedInputStream(new FileInputStream(fileName));
while (inStream.read() != -1) { }
long time = System.currentTimeMillis() - timeStart;
inStream.close();
System.out.println("Buffered read time: " + (time) + " millisec");
} catch (IOException e) {
System.out.println("IOException: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
\begin{verbatim}
Buffered read time: 23 millisec
\end{verbatim}
\subsubsection{Усложнение данных}
До сих пор речь шла только о считывании и записи в поток данных в виде \code{byte}. Для работы с другими типами данных Java определены интерфейсы \code{DataInput} и \code{DataOutput} и их реализации -- классы-фильтры \code{DataInputStream} и \code{DataOutputStream}, реализующие методы считывания и записи значений всех примитивных типов. При этом происходит конвертация этих данных в набор \code{byte} и обратно. Чтение необходимо организовать так, чтобы данные запрашивались в виде тех же типов, в той же последовательности, как и производилась запись. Если записать, например, \code{int} и \code{long}, а потом считывать их как \code{short}, чтение будет выполнено корректно, без исключительных ситуаций, но числа будут получены совсем другие.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование Data Stream (шаг 1)}]
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
DataOutputStream outData = new DataOutputStream(out);
outData.writeByte(128);
outData.writeInt(128);
outData.writeLong(128);
outData.writeDouble(128);
outData.close();
} catch (Exception e) {
System.out.println("Impossible IOException occurs: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
Далее прочитаем данные так, как они были записаны, все значения прочитаны корректно.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование Data Stream (шаг 2)}]
try {
byte[] bytes = out.toByteArray();
InputStream in = new ByteArrayInputStream(bytes);
DataInputStream inData = new DataInputStream(in);
System.out.println("Reading in the correct sequence: ");
System.out.println("readByte: " + inData.readByte());
System.out.println("readInt: " + inData.readInt());
System.out.println("readLong: " + inData.readLong());
System.out.println("readDouble: " + inData.readDouble());
inData.close();
} catch (Exception e) {
System.out.println("Impossible IOException occurs: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
\begin{verbatim}
Reading in the correct sequence:
readByte: -128
readInt: 128
readLong: 128
readDouble: 128.0
\end{verbatim}
Затем, всё ломаем и видим, что всё послушно сломалось.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование Data Stream (шаг 3)}]
try {
byte[] bytes = out.toByteArray();
InputStream in = new ByteArrayInputStream(bytes);
DataInputStream inData = new DataInputStream(in);
System.out.println("Reading in a modified sequence:");
System.out.println("readInt: " + inData.readInt());
System.out.println("readDouble: " + inData.readDouble());
System.out.println("readLong: " + inData.readLong());
inData.close();
} catch (Exception e) {
System.out.println("Impossible IOException occurs: " + e.toString());
e.printStackTrace();
}
\end{lstlisting}
\begin{verbatim}
Reading in a modified sequence:
readInt: -2147483648
readDouble: -0.0
readLong: -9205252085229027328
\end{verbatim}
Ещё сложнее \code{ObjectInputStream} и \code{ObjectOutputStream}. Для объектов процесс преобразования в последовательность байт и обратно организован несколько сложнее -- объекты имеют различную структуру, хранят ссылки на другие объекты и т.д. Поэтому такая процедура получила специальное название -- сериализация (serialization), обратное действие, -- то есть воссоздание объекта из последовательности байт -- десериализация.
\subsubsection{Классы Reader и Writer}
Рассмотренные классы -- наследники \code{InputStream} и \code{OutputStream} -- работают с байтовыми данными. Если с их помощью записывать или считывать текст, то сначала необходимо сопоставить каждому символу его числовой код. Такое соответствие называется кодировкой. Java предоставляет классы, практически полностью дублирующие байтовые потоки по функциональности, но называющиеся \code{Reader} и \code{Writer}, соответственно. Различия между байтовыми и символьными классами весьма незначительны. Классы-мосты \code{InputStreamReader} и \code{OutputStreamWriter} при преобразовании символов также используют некоторую кодировку.
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item Возможно ли чтение совершенно случайного байта данных из объекта \code{BufferedInputStream}?
\item Возможно ли чтение совершенно случайного байта данных из потока, к которому подключен объект \code{BufferedInputStream}?
\end{enumerate}
\subsection{\code{java.nio} и \code{nio2}}
Основное отличие между двумя подходами к организации ввода/вывода в том, что Java IO является потокоориентированным, а Java NIO буфер-ориентированным.
\subsubsection{io vs nio}
Потокоориентированный ввод-вывод подразумевает чтение или запись из потока и в поток одного или нескольких байт в единицу времени поочередно. Таким образом, невозможно произвольно двигаться по потоку данных вперед или назад. Если есть необходимость произвести подобные манипуляции, придётся сначала кэшировать данные в буфере.
Подход, на котором основан Java NIO немного отличается. Данные считываются в буфер для последующей обработки. Становится возможно двигаться по буферу вперед и назад. Это дает больше гибкости при обработке данных. В то же время, появляется необходимость проверять содержит ли буфер необходимый для корректной обработки объем данных. Также необходимо следить, чтобы при чтении данных в буфер не были уничтожены ещё не обработанные данные, находящиеся в буфере.
Потоки ввода/вывода (streams) в Java IO являются блокирующими. Это значит, что когда в потоке выполнения вызывается \code{read()} или \code{write()} метод любого класса из пакета \code{java.io.*}, происходит блокировка до тех пор, пока данные не будут считаны или записаны. Поток выполнения (thread) в данный момент не может делать ничего другого. Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала (channel) и получать только то, что доступно на данный момент, или вообще ничего, если доступных данных пока нет.
\begin{frm} \info Каналы это логические порталы, через которые осуществляется ввод/вывод данных, а буферы являются источниками или приёмниками этих переданных данных. При организации вывода, данные, которые вы хотите отправить, помещаются в буфер, а он передается в канал. При вводе, данные из канала помещаются в предоставленный вами буфер.
\end{frm}
Также, в Java NIO появилась возможность создать поток, который будет знать, какой канал готов для записи и чтения данных и может обрабатывать этот конкретный канал. Данные считываются в буфер для последующей обработки.
\begin{lstlisting}[language=Java,style=JCodeStyle,caption={Использование буфера и канала}]
try (RandomAccessFile catFile = new RandomAccessFile("cat.txt", "rw")) {
FileChannel inChannel = catFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(100);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead + " bytes");
// Set read mode
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
} catch (IOException e) { e.printStackTrace(); }
\end{lstlisting}
Подготовив всё необходимое приложение прочитало данные в буфер, сохранив число прочитанных байт, а затем прочитанные байты посимвольно были выведены в консоль. Для чтения данных из файла используется файловый канал. Объект файлового канала может быть создан только вызовом метода \code{getChannel()} для файлового объекта, поскольку нельзя напрямую создать объект файлового канала. При этом, \code{FileChannel} нельзя переключить в неблокирующий режим.
\subsection{String}
Класс String отвечает за создание строк, состоящих из символов. Если быть точнее, заглянув в реализацию и посмотрев способ их хранения, то строки (до Java 9) представляют собой массив символов
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final char value[];
\end{lstlisting}
а начиная с Java 9 строки хранятся как массив байт.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final byte[] value;
\end{lstlisting}
Данный материал в первую очередь направлен на Java 1.8. В отличие от других языков программирования, где символьные строки представлены последовательностью символов, в Java они являются объектами класса \code{String}. В результате создания объекта типа String получается неизменяемая символьная строка, т.е. невозможно изменить символы имеющейся строки. При любом изменении строки создается новый объект типа \code{String}, содержащий все изменения. А значит у String есть две фундаментальные особенности: это immutable (неизменный) класс; это final класс (у класса String не может быть наследников).
\begin{lstlisting}[language=Java,style=JCodeStyle]
import java.nio.charset.StandardCharsets;
String s1 = "Java";
String s2 = new String("Home");
String s3 = new String(new char[] { 'A', 'B', 'C' });
String s4 = new String(s3);
String s5 = new String(new byte[] { 65, 66, 67 });
String s6 = new String(new byte[] { 0, 65, 0, 66 }, StandardCharsets.UTF_16);
\end{lstlisting}
Экземпляр класса \code{String} можно создать множеством способов. Несмотря на кажущуюся простоту, класс строки -- это довольно сложная структура данных и огромный набор методов.
C помощью операции конкатенации, которая записывается, как знак \code{+}, можно соединять две символьные строки, порождая новый объект типа \code{String}. Операции такого сцепления символьных строк можно объединять в цепочку. Строки можно сцеплять с другими типами данных, например, целыми числами. Так происходит потому, что значение типа \code{int} автоматически преобразуется в своё строковое представление в объекте типа \code{String}.
\begin{frm} \excl Сцепляя разные типы данных с символьными строками, следует быть внимательным, иначе можно получить неожиданные результаты.
\begin{verbatim}
String s0 = "Fifty five is " + 50 + 5; // Fifty five is 505
String s1 = 50 + 5 + " = Fifty five"; // 55 = Fifty five
\end{verbatim}
Такой результат объясняется ассоциативностью оператора сложения. Оператор сложения «работает» слева направо, а расширение типа до максимального происходит автоматически, так если складывать целое и дробное, в результате будет дробное, если складывать любое число и строку получится строка. Однажды получив строку, дальнейшее сложение превратится в конкатенацию.
\end{frm}
\subsubsection{StringBuffer, StringBuilder}
Поскольку строка это достаточно неповоротливо, были придуманы классы, которые позволяют ускорить работу с ними.
Если в программе планируется работа со строками в больших циклах, следует рассмотреть возможность использования \code{StringBuilder}.
\begin{frm} \info Помните, что если что-то «тормозит», то с 90\% вероятностью уже придумали способ это ускорить. Все строковые классы работают с массивами символов, но \code{String} делает это примитивно, выделяя каждый раз новый блок памяти под массив с длиной равной длине строки, а \code{StringBuilder} сразу выделяет большой блок памяти под массив, добавляет к нему элементы по индексу, а если массив заканчивается, то тогда делает массив в два раза больше, копирует в него старый, и заполняет уже его.
\end{frm}
Разработчики Java знали, что перед программистами будут стоять задачки и посерьёзнее, чем обработка нескольких тысяч символьных строк, например, при разборе текстовых файлов, и поиске информации в электронных книгах. Поэтому придумали \code{StringBuilder} и \code{StringBuffer}. Создают они изменяемые строки и динамические ссылки на них. Их разница в том, что \code{StringBuilder} не потокобезопасный, и работает чуть быстрее, а \code{StringBuffer} -- используется в многопоточных средах, но в одном потоке работает чуть медленнее.
\begin{lstlisting}[language=Java,style=JCodeStyle]
String s = “Example”;
long timeStart = System.nanoTime();
for (int i = 0; i < 30000; ++i) {
s = s + i;
}
double deltaTime = (System.nanoTime() - timeStart) * 0.000000001;
System.out.println("Delta time: " + deltaTime);
StringBuilder sb = new StringBuilder("Example");
long timeStart = System.nanoTime();
for (int i = 0; i < 100_000; ++i) {
sb = sb.append(i);
}
double deltaTime = (System.nanoTime() - timeStart) * 0.000000001;
System.out.println("Delta time: " + deltaTime);
\end{lstlisting}
В конструктор передано начальное значение строки. То есть это всё ещё строка, но представленная другим классом
\subsubsection{String pool}
Экземпляр класса String хранится в памяти, именуемой куча (heap), но есть некоторые нюансы. Если строка, созданная при помощи конструктора хранится непосредственно в куче, то строка, созданная как строковый литерал, уже хранится в специальном месте кучи — в так называемом пуле строк (string pool). В нем сохраняются исключительно уникальные значения строковых литералов. Процесс помещения строк в пул называется интернирование (от англ. interning, внедрение, интернирование). Когда объявляется переменная типа \code{String} ей присваивается строковый литерал, то JVM обращается в пул строк и ищет там такое же значение. Если пул содержит необходимое значение, то компилятор просто возвращает ссылку на соответствующий адрес строки без выделения дополнительной памяти. Если значение не найдено, то новая строка будет интернирована, а ссылка на нее возвращена и присвоена переменной.
\begin{lstlisting}[language=Java,style=JCodeStyle]
String cat0 = "BestCat";
String cat1 = "BestCat";
String cat2 = "Best" + "Cat";
String cat30 = "Best";
String cat3 = cat30 + "Cat";
\end{lstlisting}
В строке «Best» + «Cat» создаются два строковых объекта со значениями «Best» и «Cat», которые помещаются в пул. «Склеенные» строки образуют еще одну строку со значением «BestCat», ссылка на которую берется из пула строк (а не создается заново), т.к. она была интернирована в него ранее. Значения всех строковых литералов из данного примера известно на этапе компиляции. А если предварительно поместить один из фрагментов строки в переменную, можно «запутать» пул строк и заставить его думать, что в результате получится совсем новая строка.
\begin{verbatim}
cat0 equal to cat1? true
cat0 equal to cat2? true
cat0 equal to cat3? false
\end{verbatim}
Когда создаётся экземпляр класса \code{String} с помощью оператора \code{new}, компилятор размещает строки в куче. При этом каждая строка, созданная таким способом, помещается в кучу (и имеет свою ссылку), даже если такое же значение уже есть в куче или в пуле строк. Это нерационально. В Java существует возможность вручную выполнить интернирование строки в пул путем вызова метода \code{intern()} у объекта типа \code{String}.
\begin{frm} \excl «Почему бы все строки сразу после их создания не добавлять в пул строк? Ведь это приведет к экономии памяти». Среди большого количества программистов присутствует такое заблуждение, поскольку не все учитывают дополнительные затраты виртуальной машины на процесс интернирования, а также падение производительности, связанное с аппаратными ограничениями памяти, ведь невозможно читать ячейку памяти одновременно бесконечным числом процессов.
\end{frm}
Можно сказать, что интернирование в виде применения метода \code{intern()} рекомендуется не использовать. Вместо интернирования необходимо использовать дедупликацию. Если коротко, во время сборки мусора Garbage Collector проверяет живые (имеющие рабочие ссылки) объекты в куче на возможность провести их дедупликацию. Ссылки на подходящие объекты вставляются в очередь для последующей обработки. Далее происходит попытка дедупликации каждого объекта \code{String} из очереди, а затем удаление из нее ссылок на объекты, на которые они ссылаются.
\subsubsection{Задания для самопроверки}
\begin{enumerate}
\item String -- это:
\begin{enumerate}
\item объект;
\item примитив;
\item класс;
\end{enumerate}
\item Строки в языке Java -- это:
\begin{enumerate}
\item классы;
\item массивы;
\item объекты.
\end{enumerate}
\end{enumerate}
\subsection*{Практическое задание}
\begin{enumerate}
\item Cоздать пару-тройку текстовых файлов. Для упрощения (чтобы не разбираться с кодировками) внутри файлов следует писать текст только латинскими буквами.
\item Написать метод, осуществляющий конкатенацию (объединение) переданных ей в качестве параметров файлов (не важно, в первый допишется второй или во второй первый, или файлы вовсе объединятся в какой-то третий);
\item Написать метод поиска слова внутри файла.
\end{enumerate}
\newpage
\printnomenclature[40mm]
\end{document}
% Основная загрузочная запись (MBR) — это устаревшая форма разделения загрузочного сектора. Это первый сектор диска, который содержит информацию о том, как разбит диск. Он также содержит загрузчик, который сообщает вашей машине, как загрузить ОС.
% MBR состоит из трех частей:
% Основной загрузчик;
% Таблица разделов диска;
% Конечная подпись.
% Смещение
% Длина, байт
% Описание
% 0000h
% 446
% Код загрузчика
% 01BEh
% 16
% Раздел 1
% Таблица разделов
% 01CEh
% 16
% Раздел 2
% 01DEh
% 16
% Раздел 3
% 01EEh
% 16
% Раздел 4
% 01FEh
% 2
% Сигнатура (55h AAh)
% Таблица разделов GUID (GPT)
% Таблица разделов GUID (GPT) — это стиль формата раздела, который был представлен в рамках инициативы United Extensible Firmware Interface (UEFI). GPT был разработан для архитектурного решения некоторых ограничений MBR.
% КАРТИНКА
% Стиль GPT новее, гибче и надежнее, чем MBR. GPT использует логическую адресацию блоков (LBA) для указания блоков данных. Первый блок помечен как LBA0, затем LBA1, LBA2, … и так далее.
% Каждый логический блок имеет размер 512 байт. GPT хранит защитную MBR в LBA0, основной заголовок GPT в LBA1 и записи разделов в LBA2 — LBA33.
% Структура GPT состоит из:
% Защитная MBR;
% Основной тег GPT;
% Записи разделов;
% Дополнительный GPT.
% Защитная MBR
% Защитная MBR — это пространство, зарезервированное в GPT для устаревших целей. Оно находится в LBA0. Система, которая не распознает GPT, скорее всего, перезапишет диски GPT. Это обеспечивает обратную совместимость с системами, которые не распознают GPT. Защитная MBR охватывает либо весь диск, либо 2 ТБ, в зависимости от того, что меньше.
% Основной тег GPT
% Первичный GPT охватывает LBA1LBA33 GPT.
% LBA1 состоит из основного заголовка GPT, который содержит указатель на таблицу разделов. Он также определяет объем свободного места на диске. Соответствующие записи раздела расположены в LBA2 LBA33. Каждая запись имеет длину 128 байт, и в одной LBA может храниться 4 записи. Теоретически GPT может иметь бесконечное количество разделов. Однако в Windows GPT может хранить информацию о 128 разделах (32 LBA x 4 записи раздела в каждом LBA).
% Эти LBA хранят информацию о разделах диска и их расположении.
% Разделительные блоки
% Это используемые блоки диска, отформатированные в разделе в стиле GPT, где хранятся фактические данные.
% На диске с 512-байтовыми секторами первый используемый блок находится в LBA34. Каждый блок разделов на диске в формате GPT представляет собой отдельный том. Таким образом, в соответствии с записями в Primary GPT, диск в формате GPT может иметь 128 томов. В отличие от MBR, каждый том в GPT может быть основным томом.
% Таким образом, пользователь может иметь до 128 первичных томов, способных разместить 128 загрузчиков на диске в формате GPT.
% Дополнительный GPT
% Схема GPT требует, чтобы копия основного GPT хранилась в последних секторах диска.
% Обычно они имеют маркировку LBA33 LBA1.
% Это обеспечивает избыточность схемы GPT, которую можно использовать в качестве резервной на случай повреждения или сбоя основного GPT.
% Плюсы:
% Максимальная емкость раздела 9.4ZB (Зеттабайт);
% Максимум 128 первичных разделов;
% Устойчив к повреждению первичного GPT, поскольку он также имеет вторичный GPT;
% Возможность использовать функции UEFI, такие как безопасная загрузка, быстрый запуск и т. д.
% Устройство NTFS. Главная таблица файлов MFT
% Как и любая другая файловая система, NTFS делит все полезное место на кластеры - минимальные блоки данных, на которые разбиваются файлы. NTFS поддерживает почти любые размеры кластеров - от 512 байт до 64 Кбайт. Однако общепринятым стандартом считается кластер размером 4 Кбайт. Именно он используется по умолчанию. Принцип существования кластеров можно проиллюстрировать следующим примером.
% Если у вас размер кластера составляет 4 Кбайт (что скорее всего), а нужно сохранить файл, размером 5 Кбайт, то реально под него будет выделено 8 Кбайт, так как в один кластер он не помещается, а под файл дисковое пространство выделяется только кластерами.
% Для каждого NTFS-диска имеется специальный файл - MFT (Master Allocation Table - главная таблица файлов). В этом файле содержится централизованный каталог всех имеющихся на диске файлов. При создании файла NTFS создает и заполняет в MFT соответствующую запись, в которой содержится информация об атрибутах файла, содержимом файла, имя файла и т.п.
% Помимо MFT, имеется еще 15 специальных файлов (вместе с MFT - 16), которые недоступны операционной системе и называются метафайлами. Имена всех метафайлов начинаются с символа $, но стандартными средствами операционной системы просмотреть их и вообще увидеть не представляется возможным. Далее для примера представлены основные метафайлы:
% SMFT - сам MFT.
% $MFTmirr - копия первых 16 записей MFT, размещенная посередине диска (зеркало).
% $LogFile - файл поддержки журналирования.
% $Volume - служебная информация: метка тома, версия файловой системы, и т.д.
% $AttrDef - список стандартных атрибутов файлов на томе.
% $ - корневой каталог.
% $Bitmap - карта свободного места тома.
% $Boot - загрузочный сектор (если раздел загрузочный).
% $Quota - файл, в котором записаны права пользователей на использование дискового пространства.
% $Upcase - файл-таблица соответствия заглавных и прописных букв в именах файлов на текущем томе.
% Нужен в основном потому, что в NTFS имена файлов записываются в кодировке Unicode, которую составляют 65 тысяч различных символов, искать большие и малые эквиваленты которых очень нетривиально.
% Что касается принципа организации данных на диске NTFS, то он условно делится на две части. Первые 12% диска отводятся под так называемую MFT-зону - пространство, в которое растет метафайл MFT.
% Запись каких-либо пользовательских данных в эту область невозможна. MFT-зона всегда держится пустой. Это делается для того, чтобы самый главный служебный файл (MFT) не фрагментировался при своем росте. Остальные 88% диска представляют собой обычное пространство для хранения файлов.
% Однако при нехватке дискового пространства MFT-зона может сама уменьшаться (если это возможно), так что никакого дискомфорта вы замечать не будете. При этом новые данные уже будут записываться в бывшую MFT-зону.
% В случае последующего высвобождения дискового пространства MFT-зона снова будет увеличиваться, однако в дефрагментированном виде (то есть не единым блоком, а несколькими частями на диске). В этом нет ничего страшного, просто считается, что система более надежна, когда MFT-файл не дефрагментирован.
% Кроме того, при не дефрагментированном MFT-файле вся файловая система работает быстрее. Соответственно чем более дефрагментированным является MFT-файл, тем медленней работает файловая система.
% Что касается размера MFT-файла, то он примерно вычисляется, исходя из 1 МБ на 1000 файлов.