Самый естественный и простой источник, откуда можно считывать байты - это, конечно, массив байт. Класс ByteArrayInputStream представляет поток, считывающий данные из массива байт. Этот класс имеет конструктор, которому в качестве параметра передается массив byte[]. Соответственно, при вызове методов read(), возвращаемые данные будут браться именно из этого массива.
byte[] bytes ={1,-1,0};
ByteArrayInputStream in =new ByteArrayInputStream(bytes);
int readedInt =in.read(); //readedInt=1
readedInt=in.read();//readedInt=255
readedInt=in.read();//readedInt=0
Для записи байт в массив, используется класс ByteArrayOutputStream.
Этот класс использует внутри себя объект byte[], куда записывает данные, передаваемые при вызове методов write(). Что бы получить записанные в массив данные, вызывается метод toByteArray().
ByteArrayOutputStream out =new ByteArrayOutputStream();
out.write(10);
out.write(11);
byte[] bytes =out.toByteArray();
FileInputStream и FileOutputStream
Классы FileInputStream используется для чтения данных из файла.
Конструктор этого класса в качестве параметра принимает название файла, из которого будет производиться считывание.
Для записи байт в файл используется класс FileOutputStream. При создании объектов этого класса, то есть при вызовах его конструкторов кроме указания файла, так же можно указать, будут ли данные дописываться конец файла либо файл будет перезаписан. При этом, если указанный файл не существует, то сразу после создания FileOutputStream он будет создан.
byte[] bytesToWrite ={1,2,3}; byte[] bytesReaded =new byte[10]; String fileName ="d:\\test.txt";
//что записываем
//куда считываем
try {
FileOutputStream outFile =new FileOutputStream(fileName);
outFile.write(bytesToWrite); //запись в файл outFile.close();
FileInputStream inFile =new FileInputStream(fileName);
int bytesAvailable =inFile.available(); //сколько можно считать int count =inFile.read(bytesReaded,0,bytesAvailable); inFile.close();}
catch (FileNotFoundException e){
System.out.println("Невозможно произвести запись в файл:"+fileName);} catch (IOException e){
System.out.println("Ошибка ввода/вывода:"+e.toString());}
При работе с FileInputStream метод available() практически наверняка вернет длину файла, то есть число байт, сколько вообще из него можно считать. Но не стоит закладываться на это при написании программ, которые должны устойчиво работать на различных платформах - метод available() возвращает число, сколько байт может быть на данный момент считано без блокирования.
Классы-фильтры
Задачи, возникающие при вводе/выводе крайне разнообразны - этот может быть считывание байтов из файлов, объектов из файлов, объектов из массивов, буферизованное считывание строк из массивов. В такой ситуации решение путем простого наследования приводит к возникновению слишком большого числа подклассов. Решение же, когда требуется совмещение нескольких свойств, высокоэффективно в виде надстроек. (В ООП этот паттерн называется адаптер.) Надстройки - наложение дополнительных объектов для получения новых свойств и функций. Таким образом, необходимо создать несколько дополнительных объектов - адаптеров к классам ввода/вывода. В java.io их еще называют фильтрами. При этом надстройка-фильтр, включает в себя интерфейс объекта, на который надстраивается, и поэтому может быть в свою очередь дополнительно быть надстроена.
java.io интерфейс для таких надстроек ввода/вывода предоставляют классы FilterInputStream (для входных потоков) и FilterOutputStream (для выходных потоков). Эти классы унаследованы от основных базовых классов ввода/вывода - InputStream и OutputStream соответственно. Конструкторы этих классов принимают в качестве параметра объект InputStream и имеют модификатор доступа protected. Сами же эти классы являются базовыми для надстроек. Поэтому только наследники могут вызывать его(при их создании), передавая переданный им поток. Таким образом обеспечивается некоторый общий интерфейс для надстраиваемых объектов.
Классом-фильтром являются классы BuffereInputStream и BufferedOutputStream. На практике, при считывании с внешних устройств, ввод данных почти всегда необходимо буферизировать. BufferedInputStream содержит в себе массив байт, который служит буфером для считываемых данных. То есть, когда байты из потока считываются (вызов метода read()) либо пропускаются (метод skip()), сначала перезаписывается этот буферный массив, при этом считываются сразу много байт за раз. Так же класс BufferedInputStream добавляет поддержку методов mark() и reset(). Эти методы определены еще в классе InputStream, но их реализация по умолчанию бросает исключение IOException. Метод mark() запоминает точку во входном потоке и метод reset() приводит к тому, что все байты, считанные после наиболее позднего вызова метода mark(), будут считаны заново, прежде чем новые байты будут считываться из содержащегося входного потока.
BufferedOutputStream - при использовании объекта этого класса, запись производится без необходимости обращения к устройству ввода/вывода при записи каждого байта. Сначала данные записываются во внутренний буфер. Непосредственное обращение к устройству вывода и, соответственно, запись него произойдет, когда буфер будет полностью заполнен. Освобождение буфера с записью байт на устройство вывода можно обеспечить и непосредственно - вызовом метода flush(). Так же буфер будет освобожден непосредственно перед закрытием потока (вызов метода close()). При вызове этого метода также будет закрыт и поток, над которым буфер надстроен.
String fileName ="d:\\file1";
InputStream inStream =null;
OutputStream outStream =null; //Записать в файл некоторое количество байт
long timeStart =System.currentTimeMillis();
outStream =new FileOutputStream(fileName); outStream =new BufferedOutputStream(outStream);
for(int i=1000000;--i>=0;){
outStream.write(i);}
inStream =new FileInputStream(fileName); inStream =new BufferedInputStream(inStream);
while(inStream.read()!=-1) {…//чтение данных }
Сериализация
В java имеется стандартный механизм превращения объекта в набор байт сериализации. Для того, что бы объект мог быть сериализован, он должен реализовать интерфейс java.io.Serializable (соответствующее объявление должно явно присутствовать в классе объекта или, по правилам наследования, неявно в родительском классе вверх по иерархии). Интерфейс java.io.Serializable не определяет никаких методов. Его присутствие только определяет, что объекты этого класса разрешено сериализовывать.
После того, как объект был сериализован, то есть превращен в последовательность байт, его по этой последовательности можно восстановить, при этом восстановление можно проводить на любой машине (вне зависимости от того, где проводилась сериализация), то есть последовательность байт можно передать на другую машину по сети или любым другим образом, и там провести десериализацию. При этом не имеет значения операционная система под которой запущена Java - например, можно создать объект на машине с ОС Windows, превратить его в последовательность байт, после чего передать их по сети на машину с ОС Unix, где восстановить тот же объект.
Для работы по сериализации в java.io определены интерфейсы ObjectInput, ObjectOutput и реализующие их классы ObjectInputStream и ObjectOutputStream соответственно. Для сериализации объекта нужен выходной поток OutputStream, который следует передать при конструировании ObjectOutputStream. После чего вызовом метода writeObject() сериализовать объект и записать его в выходной поток. Например:
сериализация объекта Integer(1) ByteArrayOutputStream os =new ByteArrayOutputStream();
Object objSave =new Integer(1);
ObjectOutputStream oos=new ObjectOutputStream(os); oos.writeObject(objSave);
Что бы просмотреть, во что превратился объект objSave, можно просмотреть содержимое массива
byte[] bArray = os.toByteArray();
А чтобы получить этот объект, можно десериализовать его из этого массива:
byte[] bArray =os.toByteArray(); //получение содержимого массива ByteArrayInputStream is =new ByteArrayInputStream(bArray); ObjectInputStream ois =new ObjectInputStream(is); Object objRead =ois.readObject();
Сериализация объекта заключается в сохранении и восстановлении состояния объекта. Состояние описывается значением полей. Причем не только описанных в классе, но и унаследованных. При попытке самостоятельно написать механизм восстановления возникли бы следующие проблемы:
Как установить значения полей, тип которых private
Объект создается с помощью вызова конструктора. Как восстановить ссылку в этом случае
Даже если существуют set-методы для полей, как выбрать значения и параметры.
Сериализуемый объект может хранить ссылки на другие объекты, которые в свою очередь так же могут хранить ссылки на другие объекты. И все они тоже должны быть восстановлены при десериализации. При этом, важно, что если несколько ссылок указывают на один и тот же объект, то в восстановленных объектах эти ссылки так же указывали на один и тот же объект. Если класс содержит в качестве полей другие объекты, то эти объекты так же будут сериализовываться и поэтому тоже должны быть сериализуемы. В свою очередь, сериализуемы должны быть и все объекты, содержащиеся в этих сериализуемых объектах и т.д. Полный путь ссылок объекта по всем объектным ссылкам, имеющимся у него и у всех объектов на которые у него имеются ссылки, и т.д. - называется графом исходного объекта.
Однако, вопрос, на который следует обратить внимание — что происходит с состоянием объекта, унаследованным от суперкласса. Ведь состояние объекта определяется не только значениями полей, определенными в нем самом, но так же и таковыми, унаследованными от супепркласса. Сериализуемый подтип берет на себя такую ответственность, но только в том случае, если у суперкласса определен конструктор по умолчанию, объявленный с модификатором доступа таким, что будет доступен для вызова из рассматриваемого наследника. Этот конструктор будет вызван при десериализации. В противном случае, во время выполнения будет брошено исключение java.io.InvalidClassException.
процессе десериализации, поля НЕ сериализуемых классов
(родительских классов, НЕ реализующих интерфейс Serializable) инициируются вызовом конструктора без параметров. Такой конструктор должен быть доступен из сериализуемого их подкласса. Поля сериализуемого класса будут восстановлены из потока.