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



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

IO потоци на Java 1.1


В тази точка може да започнете да си блъскате главата, чудейки се дали има друг възможен дизайн на IO потоци който би изисквал повече писане. Би ли мо­гъл някой да направи по-неподходящ дизайн?” Пригответе се: Java 1.1 прави ня­кои значителни промени в IO потоковата библиотека. Като видите Reader и Writer класовете първо мислите (както и аз) че може би те са предназначени да за­менят класовете InputStream и OutputStream. Но не е така. Макар и някои ас­пекти на старата библиотека да се смятат за остарели (ако ги използвате ще по­лучите предупреждение от компилатора), старите потоци са оставени заради обрат­ната съвместимост и понеже:

  1. Нови класове са били сложени в старата йерархия, та очевидно Sun не изо­ста­вя старите потоци.

  2. По някой път се предполага да се използват класовете от старата йерархия в комбинация с класовете от новата йерархия и за да се направи това има “мо­стови” класове: InputStreamReader преобразува InputStream в Reader и OutputStreamWriter преобразува OutputStream в Writer.

Като резултат има ситуации където са налице повече нива на обгръщане с но­вата IO потокова библиотека отколкото старата. Отново, това е недостатък на подхода с декораторите – цената, която се плаща заради повишената гъвкавост.

Най-важният мотив за добавяне на Reader и Writer йерархиите в Java 1.1 е интер­национализацията. Старата IO потокова йерархия поддържа само 8-би­то­ви знакови потоци и не работи с 16-битови Unicode знаци добре. Понеже Unicode се използва за интернационализация (и естественият за Java char е 16-би­тов Unicode), Reader и Writer йерархиите бяха добавени за поддръжка на Unicode във всичките IO операции. Освен това новите библиотеки са проек­ти­ра­ни по-бързи от старите.

Каквато е практиката в тази книга, аз ще се опитам да дам общ поглед върху кла­совете, като за пълните подробности и изчерпателен списък на методите ще из­ползвате онлайн документацията.

Източници и стоци на данни


Почти всичките IO потокови класове на Java 1.0 имат съответни Java 1.1 кла­со­ве за да осигурят естествена Unicode манипулация. Би било най-лесно да кажем “Използвайте винаги новите класове и никога старите,” но нещата не са толкова про­сти. Понякога се налага да се използват Java 1.0 IO потоковите класове по­ра­ди устройството на библиотеката; в частност, java.util.zip библиотеките са но­ви неща към старата библиотека и те разчитат на компоненти от старите по­то­ци. Така че най-смисления подход е да се опитаме да използваме Reader и Writer където е възможно, а ще се случи и да се върнем къъм старите би­блио­те­ки понеже иначе кодът няма да се компилира.

Ето таблица която показва съответствието между източниците и стоците на ин­фор­мация (тоест, местата откъдето физически идва и където физически отива ин­формацията) в старите и новите библиотеки.



Източници и стоци:
Java 1.0 клас

Съответен Java 1.1 клас

InputStream

Reader
преобразувател: InputStreamReader

OutputStream

Writer
преобразувател: OutputStreamWriter

FileInputStream

FileReader

FileOutputStream

FileWriter

StringBufferInputStream

StringReader

(няма съответен клас)

StringWriter

ByteArrayInputStream

CharArrayReader

ByteArrayOutputStream

CharArrayWriter

PipedInputStream

PipedReader

PipedOutputStream

PipedWriter

Изобщо, ще откриете че интерфейсите в новите и старите библиотеки много си при­личат, ако не са еднакви.

Променяне на поведението на потоците


В Java 1.0 потоците бяха приспособявани към конкретни нужди чрез из­полз­ва­не на "декораторните" класове FilterInputStream и FilterOutputStream. С IO по­тоците в Java 1.1 тази идея продължава да се прилага, но не се следва из­вли­ча­нето на всички декоратори от единствен базов "филтърен" клас. Това може да до­веде до смущения ако се опитвате да разберете библиотеката гледайки йе­рар­­хия­та на класовете.

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



Филтри:
Java 1.0 клас

Съответен Java 1.1 клас

FilterInputStream

FilterReader

FilterOutputStream

FilterWriter (abstract клас без подкласове)

BufferedInputStream

BufferedReader
(също има readLine( ))

BufferedOutputStream

BufferedWriter

DataInputStream

използвай DataInputStream
(Освен когато искате да използвате readLine( ), та ще трябва BufferedReader)

PrintStream

PrintWriter

LineNumberInputStream

LineNumberReader

StreamTokenizer

StreamTokenizer
(използвайте конструктор който взема Reader)

PushBackInputStream

PushBackReader

Има една насока която е доста ясна: Навсякъде където искате да използвате readLine( ), няма да го правите с DataInputStream повече (това се отбелязва със съобщение от компилатора), а с BufferedReader. Извън това Data­Input­Stream е все още “предпочитания” член на Java 1.1 IO библиотеката.

За да се направи преходът PrintWriter по-лесен, той има конструктори които прие­мат OutputStream обект. Обаче PrintWriter повече не поддържа фор­ма­ти­ра­нето което прави PrintStream; интерфейсите са практически същите.


Непроменени класове


Види се проектантите на Java библиотеката са чувствали, че някои класове са на­правени както трябва от самото начало и те са оставени без промяна:

Java 1.0 без съответни Java 1.1 класове

DataOutputStream

File

RandomAccessFile

SequenceInputStream

DataOutputStream, в частност, се използва без промяна, токо че за запомняне и че­тене на данни в транспортируем формат сте принудени да стоите в Input­Stream и OutputStream йерархиите.

Пример


За да видим ефекта от новите класове, нека да погледнем подходяща част от IOStreamDemo.java примера променен да използва класовете Reader и Writer:

//: c10:NewIODemo.java

// Java 1.1 IO typical usage

import java.io.*;


public class NewIODemo {

public static void main(String[] args) {

try {

// 1. Reading input by lines:



BufferedReader in =

new BufferedReader(

new FileReader(args[0]));

String s, s2 = new String();

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

s2 += s + "\n";

in.close();
// 1b. Reading standard input:

BufferedReader stdin =

new BufferedReader(

new InputStreamReader(System.in));

System.out.print("Enter a line:");

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


// 2. Input from memory

StringReader in2 = new StringReader(s2);

int c;

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



System.out.print((char)c);
// 3. Formatted memory input

try {


DataInputStream in3 =

new DataInputStream(

// Oops: must use deprecated class:

new StringBufferInputStream(s2));

while(true)

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

} catch(EOFException e) {

System.out.println("End of stream");

}
// 4. Line numbering & file output

try {


LineNumberReader li =

new LineNumberReader(

new StringReader(s2));

BufferedReader in4 =

new BufferedReader(li);

PrintWriter out1 =

new PrintWriter(

new BufferedWriter(

new FileWriter("IODemo.out")));

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

out1.println(

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

out1.close();

} catch(EOFException e) {

System.out.println("End of stream");

}
// 5. Storing & recovering data

try {

DataOutputStream out2 =



new DataOutputStream(

new BufferedOutputStream(

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

out2.writeDouble(3.14159);

out2.writeBytes("That was pi");

out2.close();

DataInputStream in5 =

new DataInputStream(

new BufferedInputStream(

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

BufferedReader in5br =

new BufferedReader(

new InputStreamReader(in5));

// Must use DataInputStream for data:

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

// Can now use the "proper" readLine():

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

} catch(EOFException e) {

System.out.println("End of stream");

}
// 6. Reading and writing random access

// files is the same as before.

// (not repeated here)


} catch(FileNotFoundException e) {

System.out.println(

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

} catch(IOException e) {

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

}

}



} ///:~

Изобщо ще видите, че преправянето е доста праволинейно и кодовете доста си при­личат. Все пак има важни разлики, обаче. Преди всичко, понеже файловете с произ­волен достъп не са променени,секция 6 не е повторена.

Секция 1 се свива малко, понеже ако искате само да четете трябва само да об­гър­нете FileReader с BufferedReader. Секция 1b показва новия начин да се на­прави System.in за четене от конзолата, а той се разширява защото System.in е DataInputStream и BufferedReader иска Reader аргумент, та Input­Stream­Reader се въвлича за изпълнение на транслацията.

В секция 2 може да се види че ако имате String и искате да четете от него просто използвате StringReader вместо StringBufferInputStream и останалото е същото.

Секция 3 показва бъг в новата IO потокова библиотека. Ако имате String и ис­ка­те да четете от него, не се очаква да използвате StringBufferInputStream по­ве­че. Когато компилирате код с StringBufferInputStream конструктор по­лу­ча­ва­те съобщение, което казва да не го използвате повече. Вместо това се очаква да използвате StringReader. Обаче ако искате да правите форматилан вход от па­метта както в секция 3, налага се да използвате DataInputStream – няма “DataReader” да го замести – и DataInputStream конструкторът изисква InputStream аргумент. Така че нямате избор освен използването на остарелия StringBufferInputStream клас. Компилаторът предупреждава че използвате оста­рял клас, но няма какво друго да се направи.2

Секция 4 е достатъчно праволинейно преработена от старата библиотека за но­ва­та, без изненади. В секция 5 се налага да използвате всичките стари потокови кла­сове понеже DataOutputStream и DataInputStream ги изискват и няма ал­тер­нативи. Не получавате никакво съобщение по време на компилация. Ако по­то­кът е остарял (ще се изоставя - б.пр.) неговият конструктор обикновено предиз­виква предупреждение по време на компилация, но в DataInputStream само методът readLine( ) е остарял, понеже се очаква да използвате Buffered­Reader за readLine( ) (но DataInputStream за всичкия останал форматиран вход).

Ако сравните секция 5 със съответната от IOStreamDemo.java, ще забележите че в нея версия данните са написани преди текста. Това е поради бъг допуснат в Java 1.1, който се показва със следващата програма:

//: c10:IOBug.java

// Java 1.1 (and higher?) IO Bug

import java.io.*;


public class IOBug {

public static void main(String[] args)

throws Exception {

DataOutputStream out =

new DataOutputStream(

new BufferedOutputStream(

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

out.writeDouble(3.14159);

out.writeBytes("That was the value of pi\n");

out.writeBytes("This is pi/2:\n");

out.writeDouble(3.14159/2);

out.close();


DataInputStream in =

new DataInputStream(

new BufferedInputStream(

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

BufferedReader inbr =

new BufferedReader(

new InputStreamReader(in));

// The doubles written BEFORE the line of text

// read back correctly:

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

// Read the lines of text:

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

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

// Trying to read the doubles after the line

// produces an end-of-file exception:

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

}

} ///:~


Видимо нищо след написано след извикването на writeBytes( ) не може да се из­вле­че обратно. Това е доста ограничаваща грешка и може да се надяваме че ще бъ­де отстранена по времето, когато четете това. Ще пуснете горната програма за да я изпробвате; ако не получите изключение и стойностите се извеждат пра­вил­но, работата е наред.

Генератор на справки


//: c10:CrossReference.java

// Generates cross-reference listing of tokens

import java.util.*;

import java.io.*;


class CrossReference {

// Comparator to ignore case:

static class NoCase implements Comparator {

public int compare(Object o1, Object o2) {

String s1 = (String) o1;

String s2 = (String) o2;

return s1.compareToIgnoreCase(s2);

}

}



static void process(BufferedReader r)

throws IOException {

TreeMap map = new TreeMap(new NoCase());

String line;

int lineno = 0;

// Build map, reading a line at a time:

while ((line = r.readLine()) != null) {

++lineno;

// Read each token:

String delim =

" `~!@#$%^&*()-_=+\\|[{]};:'\",<.>/?"

+ "0123456789";

StringTokenizer tokens =

new StringTokenizer(line, delim);

while (tokens.hasMoreTokens()) {

String token = tokens.nextToken();

if (!map.containsKey(token)) {

// Add token and empty list to map:

map.put(token, new LinkedList());

}

// See if this line is in there already:



LinkedList lines =

(LinkedList) map.get(token);

if (lines.isEmpty() ||

((Integer)lines.getLast()).intValue() !=

lineno) {

// Add line number to list:

lines.addLast(new Integer(lineno));

}

}



}

// Output:

Iterator p = map.entrySet().iterator();

while (p.hasNext()) {

Map.Entry e = (Map.Entry) p.next();

System.out.print(e.getKey() + ": ");

LinkedList lines = (LinkedList)e.getValue();

for (int i = 0; i < lines.size(); ++i) {

if (i > 0)

System.out.print(", ");

System.out.print(lines.get(i));

}

System.out.println();



}

}

public static void



main(String[] args) throws IOException {

// Process each file:

for (int i = 0; i < args.length; ++i) {

System.out.println("File: " + args[i]);

process(new BufferedReader(

new FileReader(args[i])));

System.out.println("====================");

}

}



} ///:~

Пренасочване на стандартния IO


Java 1.1 е добавил методи в класа System, които позволяват да се пренасочват стандартния входен, изходен и за грешки потоци чрез извикване на статични методи:

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

Пренасочването на изхода е особено полезно, когато много информация про­бяг­ва по вашия екран без да може да я проследите. Пренасочването на входа е цен­но когато имате да пускате програма с повтарящ се потребителски вход при вся­ко пускане. Ето прост пример за използване на тези методи:

//: c10:Redirecting.java

// Demonstrates the use of redirection for

// standard IO in Java 1.1

import java.io.*;


class Redirecting {

public static void main(String[] args) {

try {

BufferedInputStream in =



new BufferedInputStream(

new FileInputStream(

"Redirecting.java"));

// Produces deprecation message:

PrintStream out =

new PrintStream(

new BufferedOutputStream(

new FileOutputStream("test.out")));

System.setIn(in);

System.setOut(out);

System.setErr(out);
BufferedReader br =

new BufferedReader(

new InputStreamReader(System.in));

String s;

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

System.out.println(s);

out.close(); // Remember this!

} catch(IOException e) {

e.printStackTrace();

}

}



} ///:~

Тази програма насочва стандартния вход да е от файл, а стандартния изход истандартната грешка към друг файл.

Това е друг пример където предупреждението за остарялост е неизбежно. Съ­об­ще­нието което ще получите с помощта на флага -deprecation е:

Note: The constructor java.io.PrintStream(java.io.OutputStream)
has been deprecated.

Обаче и System.setOut( ) и System.setErr( ) изискват PrintStream обект за ар­гу­мент, така че се принуждавате да използвате PrintStream конструктор. Може да се чудите дали Java 1.1 смята за остарял целия PrintStream щом е остарял кон­структора, докато проектантите на библиотеката, по същото време когато са решили това, са добавили и нови методи към System изискващи PrintStream на­место PrintWriter, който е новият и предпочитан заместител. То­ва е мистерия.





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




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

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