Книга е още в много ранна фаза на написване


Типично използване на IO потоци



страница61/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   57   58   59   60   61   62   63   64   ...   73

Типично използване на IO потоци


Макар и да има множество IO потокови класове в библиотеката, които могат да бъдат комбинирани по много начини, има само няколко, които ще се случи ве­ро­ятно да използвате. Те изискват внимание при подбора на коректните ком­би­на­ции, обаче. Следващия малко дълъг пример показва използването на типични IO конфигурации така че може да го ицползвате за справки като пишете соб­ствен код. Забележете че всяка комбинация започва с изкоментиран номер и за­гла­вие, което кореспондира на това от обясненията, които следват текста.

//: c10:IOStreamDemo.java

// Typical IO Stream Configurations

import java.io.*;

import com.bruceeckel.tools.*;
public class IOStreamDemo {

public static void main(String[] args) {

try {

// 1. Buffered input file



DataInputStream in =

new DataInputStream(

new BufferedInputStream(

new FileInputStream(args[0])));

String s, s2 = new String();

while((s = in.readLine())!= null)

s2 += s + "\n";

in.close();


// 2. Input from memory

StringBufferInputStream in2 =

new StringBufferInputStream(s2);

int c;


while((c = in2.read()) != -1)

System.out.print((char)c);


// 3. Formatted memory input

try {


DataInputStream in3 =

new DataInputStream(

new StringBufferInputStream(s2));

while(true)

System.out.print((char)in3.readByte());

} catch(EOFException e) {

System.out.println(

"End of stream encountered");

}
// 4. Line numbering & file output

try {


LineNumberInputStream li =

new LineNumberInputStream(

new StringBufferInputStream(s2));

DataInputStream in4 =

new DataInputStream(li);

PrintStream out1 =

new PrintStream(

new BufferedOutputStream(

new FileOutputStream(

"IODemo.out")));

while((s = in4.readLine()) != null )

out1.println(

"Line " + li.getLineNumber() + s);

out1.close(); // finalize() not reliable!

} catch(EOFException e) {

System.out.println(

"End of stream encountered");

}
// 5. Storing & recovering data

try {

DataOutputStream out2 =



new DataOutputStream(

new BufferedOutputStream(

new FileOutputStream("Data.txt")));

out2.writeBytes(

"Here's the value of pi: \n");

out2.writeDouble(3.14159);

out2.close();

DataInputStream in5 =

new DataInputStream(

new BufferedInputStream(

new FileInputStream("Data.txt")));

System.out.println(in5.readLine());

System.out.println(in5.readDouble());

} catch(EOFException e) {

System.out.println(

"End of stream encountered");

}
// 6. Reading/writing random access files

RandomAccessFile rf =

new RandomAccessFile("rtest.dat", "rw");

for(int i = 0; i < 10; i++)

rf.writeDouble(i*1.414);

rf.close();


rf =

new RandomAccessFile("rtest.dat", "rw");

rf.seek(5*8);

rf.writeDouble(47.0001);

rf.close();
rf =

new RandomAccessFile("rtest.dat", "r");

for(int i = 0; i < 10; i++)

System.out.println(

"Value " + i + ": " +

rf.readDouble());

rf.close();
// 7. File input shorthand

InFile in6 = new InFile(args[0]);

String s3 = new String();

System.out.println(

"First line in file: " +

in6.readLine());

in6.close();
// 8. Formatted file output shorthand

PrintFile out3 = new PrintFile("Data2.txt");

out3.print("Test of PrintFile");

out3.close();


// 9. Data file output shorthand

OutFile out4 = new OutFile("Data3.txt");

out4.writeBytes("Test of outDataFile\n\r");

out4.writeChars("Test of outDataFile\n\r");

out4.close();
} catch(FileNotFoundException e) {

System.out.println(

"File Not Found:" + args[0]);

} catch(IOException e) {

System.out.println("IO Exception");

}

}



} ///:~

Входни потоци


Разбира се, едно нещо което много се използва е извеждането на форматирани данни на конзолата, но това беше вече разгледано в пакета com.bruceeckel.tools съз­даден в глава 5.

Части 1 до 4 демонстрират създаването и използването на входни потоци (ма­кар и част 4 също да показва и прост начин на използване на изхозен поток като ин­струмент за тестване).


1. Буфериран вход от файл


За да отворите файл за вход използвате FileInputStream със String или File обект като име на файл. За скорост бихте желали входът да бъде буфериран та­ка че давате получения манипулатор на BufferedInputStream. За да се чете вхо­дът форматиран, давате получения манипулатор на конструктора на DataInputStream, който е вашият краен обект и от чийто интерфейс четете.

В този пример само readLine( ) метода се използва, но разбира се налице са всич­ките методи на DataInputStream. Като достигнете края на файла, readLine( ) връща null и то се използва така, че се прекъсва while цикъла.



String-ът s2 се използва за събиране на цялото съдържание на файла (вклю­чи­тел­но знаците за нов ред които трябва да се добавят, понеже readLine( ) ги ма­ха). s2 после се използва в други части на програмата. Накрая се вика close( ) за да затвори файла. Технически, close( ) ще се извика когато се пусне finalize( ) и то­ва се предполага да се случи (независимо дали ще има събиране на боклука) при завършването на програмата. Обаче Java 1.0 има един важен бъг, така че то­ва няма да се случи. В Java 1.1 трябва явно да се извика System.runFinalizersOnExit(true) за да се гарантира, че finalize( ) ще се извика за всеки обект в системата. Най-безопасният подход е явно да се вика close( ) за фай­лове.

2. Вход от паметта


Това парче взема String s2 който сега съдържа целия файл и го използва за съз­да­ване на StringBufferInputStream. (String, не StringBuffer, се изисква за ар­гу­мент на конструктора.) Тогава read( ) се използва за четене на знаците един по един и изпращането им на конзолата. Забележете че read( ) връща следващия байт като int и трябва да се превърне в char за правилно извеждане.

3. Форматиран вход от паметта


Интерфейсът на StringBufferInputStream е огпаничен, така че обикновено го раз­ширяваме чрез обгръщане в DataInputStream. Обаче ако сте избрали да про­читате байт по байт чрез readByte( ), всяка стойност е валидна и връщаната стой­ност не може да се използва за определяне края на въвеждането. Вместо то­ва може да се използва метода available( ) за намиране колко знака остават. Ето пример който показва как да се чете файл байт по байт:

//: c10:TestEOF.java

// Testing for the end of file while reading

// a byte at a time.

import java.io.*;
public class TestEOF {

public static void main(String[] args) {

try {

DataInputStream in =



new DataInputStream(

new BufferedInputStream(

new FileInputStream("TestEof.java")));

while(in.available() != 0)

System.out.print((char)in.readByte());

} catch (IOException e) {

System.err.println("IOException");

}

}



} ///:~

Забележете че available( ) работи различно според това какъв е видът на ме­дия­та с която се работи – буквално “броят байтове който може да се прочете не бло­ково.” С файл това значи целия файл, но с други видове устройства това мо­же да не е така, така че го използвайте внимателно.

Освен това бихте могли да хванете края на файла в подобни случаи чрез из­клю­че­ние. Обаче използването на изключения за управление хода на програмата се счи­та за неправилно тяхно използване.

4. Номериране на редове и файлов изход


Този пример показва използването на LineNumberInputStream за да се следят но­мерата на редовете. Тук не може просто да се съберат всичките конструк­то­ри заедно, понеже трябва да се пази манипулатор към Line­NumberInputStream. (Забележете че това не е ситуация на наследяване, та­ка че не може да се направи просто кастинг на in4 към LineNumberInputStream.) Така, li съдържа манипулатор към LineNumberInputStream, който после се използва за създаване на DataInputStream за лесно четене.

Този пример също показва писането на форматирани данни във файл. Първо се съз­дава FileOutputStream за свързване с файла. За ефективност е направен BufferedOutputStream, което почти винаги ще е желания случай, но се налага да го правите явно. После за форматирането той е превърнат в PrintStream. Дан­новия файл създаден по този начин е четим като обикновен текстов файл.

Един от методите който показва че DataInputStream е изтощен е readLine( ), кой­то връща null когато повече няма стрингове за четене. Всеки ред се записва във файла заедно с номера си, който се следи чрез li.

Ще видите явен close( ) за out1, което би имало смисъл ако програмата щеше да се върне и да чете файла отново. Тази програма обаче завършва без да се обръ­ща към файла IODemo.out. Както се спомена преди, ако не извикате close( ) за всич­ки ваши изходни файлове, бихте могли да откриете че буферът не е за­пи­сан и файлът не е завършен.


Изходни потоци


Двата основни вида изходни потоци се различават по начина на записване на данните: при единия са предназначени за хората, а другият ги пише за да може да бъдат възстановени от DataInputStream. RandomAccessFile е отделно, ма­кар и форматът на данните при него да е съвместим с DataInputStream и DataOutputStream.

5. Запазване и възстановяване на данни


PrintStream форматира данните така, че са разбираеми за човек. За извеждане на данни които да могат да бъдат възстановени (прочетени) от друг поток из­полз­ваме DataOutputStream за запис на данните и DataInputStream за въз­ста­но­вяването им. Разбира се, тези потоци биха могли да бъдат всякакви, но тук се из­ползва файл.

Забележете че знаковият низ е написан чрез writeBytes( ) а не чрез writeChars( ). Ако използвате последния бихте писали 16-битовите Unicode зна­ци. Тъй като няма съответен “readChars” метод в DataInputStream, ограничени сте да вземате знаците един по един чрез readChar( ). Така за ASCII, по-лесно е да пишете знаците като байтове следвани от знак за нов ред; после чрез readLine( ) да четете байтовете обратно като обикновен ASCII ред.



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

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


Както бе отбелязано преди, RandomAccessFile е почти напълно изолиран от оста­налата част на IO йерархията, освен че прилага интерфейсите на DataInput и DataOutput. Така че не може да го комбинирате в никакъв аспект на InputStream и OutputStream подкласовете. Макар и да има смисъл да се тре­ти­ра ByteArrayInputStream като елемент с произволен достъп, може да из­полз­ва­те RandomAccessFile само да отвори файл. Трябва да предполагате че RandomAccessFile е правилно буфериран, понеже не може да добавите тази чер­та.

Едничкият избор който имате е аргументът на вторият конструктор: може да от­во­рите RandomAccessFile за четене (“r”) или четене и писане (“rw”).

Използването на RandomAccessFile е подобно на използването на комби­ни­ра­ни DataInputStream и DataOutputStream (понеже прилага еквивалентни ин­тер­фей­си). Освен това може да видите, че seek( ) се използва за разходки по файла и промяна на една от стойностите.

Кратък начин за операции с файлове


Тъй като има няколко типични форми, които се използват често с фойлове, бих­те могли да се чудите защо трябва да се пише толкова много код (всеки път-б.пр.) – това е един от недостатъците на декораторския подход. Тази част по­каз­ва създаването и използването на съкратени процедури за четене и писане от фай­лове. Тези неща са сложени в пакета package com.bruceeckel.tools който бе­ше започнат в глава 5 (Виж стр. 186). За да се добави всеки клас в библиотеката, про­сто го сложете в съответната директория и добавете оператор package.

7. Кратък начин за вход от файл


Създаването на обект чете файл от буфериран DataInputStream може да бъде кап­сулирано в клас наречен InFile:

//: com:bruceeckel:tools:InFile.java

// Shorthand class for opening an input file

package com.bruceeckel.tools;

import java.io.*;
public class InFile extends DataInputStream {

public InFile(String filename)

throws FileNotFoundException {

super(


new BufferedInputStream(

new FileInputStream(filename)));

}

public InFile(File file)



throws FileNotFoundException {

this(file.getPath());

}

} ///:~


И двете версии на String на конструктора и File версиите са включени, с цел да се прокара паралел със създаването на FileInputStream.

Сега може да намалите вероятността от повтарящ се стрес, както се вижда в при­мера.


8. Кратък път за форматирано писане във файл


Същият подход може да се приеме за създаване на PrintStream който пише в бу­фе­риран файл. Ето разширението за com.bruceeckel.tools:

//: com:bruceeckel:tools:PrintFile.java

// Shorthand class for opening an output file

// for human-readable output.

package com.bruceeckel.tools;

import java.io.*;


public class PrintFile extends PrintStream {

public PrintFile(String filename)

throws IOException {

super(


new BufferedOutputStream(

new FileOutputStream(filename)));

}

public PrintFile(File file)



throws IOException {

this(file.getPath());

}

} ///:~


Забележете че не е възможно конструктор да хване изключение, изхвърлено то конструктора на базовия клас.

9. Начин за писане в даннов файл


Накрая, същия вид кратък път може да се използва за запазване на данни (като про­тивоположност на читаемите от човек):

//: com:bruceeckel:tools:OutFile.java

// Shorthand class for opening an output file

// for data storage.

package com.bruceeckel.tools;

import java.io.*;


public class OutFile extends DataOutputStream {

public OutFile(String filename)

throws IOException {

super(


new BufferedOutputStream(

new FileOutputStream(filename)));

}

public OutFile(File file)



throws IOException {

this(file.getPath());

}

} ///:~


Любопитно е (и е за нещастие) че проектантите на Java библиотеката не счетоха за необходимо да добавят тези удобства в техния стандарт.

Четене от стандартния вход


Следвайки подхода започнат от Unix за “стандартен вход,” “стандартен изход,” и “стандартен изход за грешки,” Java има System.in, System.out и System.err. По протежение на книгата сте виждали как се извежда на System.out, което е вече обгърнато от PrintStream обект. System.err е подобен на PrintStream, но System.in е "суров" InputStream, без обгръщането. Това значи, че докато мо­же­те да използвате System.out и System.err, System.in трябва да бъде обгърнат (с друг обект - б.пр.) преди да го използвате.

Типично ще искате да се чете по ред на един път с readLine( ), така че ще тряб­ва да обгърнете System.in с DataInputStream. Този е “старие” начин на Java 1.0 за вход ред по ред. Малко по късно в главата ще видите решението в Java 1.1. Ето пример, който прави "ехо" на всеки въведен ред:

//: c10:Echo.java

// How to read from standard input

import java.io.*;
public class Echo {

public static void main(String[] args) {

DataInputStream in =

new DataInputStream(

new BufferedInputStream(System.in));

String s;

try {

while((s = in.readLine()).length() != 0)



System.out.println(s);

// An empty line terminates the program

} catch(IOException e) {

e.printStackTrace();

}

}

} ///:~



Причината да има try блок е че readLine( ) може да изхвърли IOException. Забележете че System.in трябва също да бъде буфериран, както с повечето по­то­ци.

Не е много удобно че сте принудени да обгръщате System.in в Data­InputStream във всяка програма, но може би е избран този начин заради мак­симална гъвкавост.


Скачени потоци


PipedInputStream и PipedOutputStream само накъсо се споменаха в тази глава. Това не е защото не са полезни, а защото ползата от тях не е явна докато не започнете да разбирате многонишковостта, понеже свързаните потоци се из­полз­ват за връзка между нишките. Това е изяснено чрез пример в глава 14.



Сподели с приятели:
1   ...   57   58   59   60   61   62   63   64   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница