欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Thking in java(第四版)-查缺补漏(第18章)

程序员文章站 2024-03-17 17:45:04
...

背景

继续查缺补漏,加油

1.InputStream类型

Thking in java(第四版)-查缺补漏(第18章)

2.OutputStream类型

 Thking in java(第四版)-查缺补漏(第18章)

 3.FilterInputStream和FilterOutputStream

它们是用来提供装饰器类接口以控制特定输入流InputStream和输出流OutputStream。分别从

InputStream和OutputStream派生而来

(1).FilterInputStream类型

Thking in java(第四版)-查缺补漏(第18章)

(2).FilterOutputStream类型

Thking in java(第四版)-查缺补漏(第18章)

 4.Reader和Writer

InputStream和OutputStream在以面向字节形式的I/O中仍可以提供极有价值的功能,Reader和

Writer则提供兼容Unicode与面向字符的I/O功能。

InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream

转换为Writer。设计Reader和Writer继承层次结构主要是为了国际化。

java.util.zip类库就是面向字节的而不是面向字符的。

Thking in java(第四版)-查缺补漏(第18章)

 更改流的行为:

 

 Thking in java(第四版)-查缺补漏(第18章)

无论我们何时使用readLine(),都应该使用BufferedReader。

PrintWriter 的构造器能接受writer对象又能接受任何OutputStream对象

PrintWriter构造器有一个“自动自行清空”选项,如果构造器设置此选项,则在每个println()执行之后,便会自动清空。

5.典型使用方式

(1)缓冲输入文件

package io;
import java.io.*;
import java.util.*;
public class BufferedInputFile {
	//Throw exceptions to console:
	public static String read(String filename) throws IOException{
		//Reading input by line:
		BufferedReader in=new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb=new StringBuilder();
		while((s=in.readLine())!=null)
			sb.append(s+"\n");
		in.close();
		return sb.toString();
	}
	public static void main(String[] args)throws IOException{
			System.out.println(read("BufferedInputFile.java"));
	}
}

字符串sb必须添加换行符,因为readLine()已经将它们删除

(2)从内存输入

package io;
import java.io.*;
public class MemoryInput {
	public static void main(String[] args) throws IOException{
		StringReader in=new StringReader("BufferenInputFile.java");
		int c;
		while((c=in.read())!=-1)
			System.out.print((char)c);
	}
}

(3)格式化的内存输入

package io;
import java.io.*;
public class FormattedMemoryInput {
	public static void main(String[] args)throws IOException{
		try{
			DataInputStream in=new DataInputStream(
					new ByteArrayInputStream(BufferedInputFile.read
							("src/io/BufferedInputFile.java").getBytes()));
			while(true)
				System.out.print((char)in.readByte());
		}catch(EOFException e){
			System.err.println("End of stream");
		}
	}
}

可以使用available()方法查看还有多少可供存取的字符。

(4)基本文件的输出

package io;
import java.io.*;
public class BasicFileOutput {
	static String file="BufferedInputFile.out";
	public static void main(String[] args)throws IOException{
        BufferedReader in=new BufferedReader(new 
             StringReader(BufferedInputFile.read("BufferedInputFile.java")));
		PrintWriter out=new PrintWriter(new BufferedWriter(new FileWriter(file)));
		int lineCount=1;
		String s;
		while((s=in.readLine())!=null)
			out.println(lineCount++ +": "+s);
		out.close();
	}
}

(5)文本文件输出的快捷方式

static String file="file.out";
PrintWriter out=new PrintWriter(file);

这里还是会进行缓存,简化了代码。

6.存储和恢复数据

当使用DataOutputStream时,写字符串并且让DataInputStream能够恢复它的惟一可靠做法就是使用

UTF-8编码。

DataOutputStream out=new DataOutputStream(new BufferedOutputStream(new 
    FileOutputStream("data.out")));
out.writeUTF("That was pi");
DataInputStream in=new DataInputStream(new BufferedInputStream(new 
    FileInputStream("data.out")));
System.out.println(in.readUTF());

7.读写随机访问文件

RandomAccessFile实现了DataInput和DataOutput接口,类似于组合使用了DataInoutStream和DataOutputStream。

8.从标准输入中读取


BufferedReader stdin=new BufferedReader(new InputStreamReader(System.in));
String s;
while((s=stdin.readLine())!=null&&s.length()!=0)
	System.out.println(s);

将System.out转换成PrintWriter

PrintWriter out=new PrintWriter(System.out,true);
out.println("hello");

9.标准I/O重定向

setIn setOut setErr

如果我们突然开始在显示器上创建大量输出,而这些输出滚动得太快以至于无法阅读时,重定向输出就显得

极为有用。

package io;
import java.io.*;
public class Redirecting {
	public static void main(String[] args)throws IOException{
		PrintStream console=System.out;
		BufferedInputStream in=new BufferedInputStream(new FileInputStream("src/io/Redirecting.java"));
		PrintStream out=new PrintStream(new BufferedOutputStream(new FileOutputStream("src/io/Redirecting.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();
		System.setOut(console);
	}
}

I/O重定向操纵的是字节流,不是字符流。

10.进程控制

在Java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出。一项常见的任务是运行程序,

并将产生的输出发送到控制台。例如:

package tools;

public class OSExecuteException extends RuntimeException {
	public OSExecuteException(String why){	super(why);}
}


///////////////////////////////////////////////////////////////
package tools;
import java.io.*;
public class OSExecute {
	public static void command(String command){
		boolean err=false;
		try{
			Process process=new ProcessBuilder(command.split(" ")).start();
			BufferedReader results=new BufferedReader(new InputStreamReader(process.getInputStream()));
			String s;
			while((s=results.readLine())!=null)
				System.out.println(s);
			BufferedReader errors=new BufferedReader(new InputStreamReader(process.getErrorStream()));
			//Report errors and return nonzero value
			//to calling process if there are problems:
			while((s=errors.readLine())!=null){
				System.err.println(s);
				err=true;
			}
		}catch(Exception e){
			//Compensate for Windows 2000,which throws an
			//exception for the default command line:
			if(!command.startsWith("CMD /C"))
				command("CMD /C"+command);
			else
				throw new RuntimeException(e);
		}
		if(err)
			throw new OSExecuteException("Errors executing"+command);
	}
}

/////////////////////////////////////////////////////////////////////
package io;
import tools.*;
public class OSExecuteDemo {
	public static void main(String[] args){
		OSExecute.command("javap bin/io/OSExecuteDemo");
	}
}

11.新I/O

在java.nio.*包中。

速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们可以把它想象成一个煤矿

,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获

得煤炭。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓

冲器获得数据,要么向缓冲器发送数据。

唯一直接与通道交互的缓冲器是ByteBuffer,它可以存储未加工字节的缓冲器。只能用于原始的字节形式或基本数据

类型输出和读取数据。没办法输出或读取对象,即使是字符串也不行。

有三个类可以产生FileChannel :FileInputStream FileOutputStream RandomAccessFile 这些是字节操纵流,与底层

uio性质一致。Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了使用方法,

用以在通道中Reader和Writer。

例如:

package io;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class GetChannel {
	private static final int BSIZE=1024;
	public static void main(String[] args)throws Exception{
		//Write a file:
		FileChannel fc=new FileOutputStream("data.txt").getChannel();
		fc.write(ByteBuffer.wrap("Some text ".getBytes()));
		fc.close();
		//Add to the end of the file:
		fc=new RandomAccessFile("data.txt","rw").getChannel();
		fc.position(fc.size());
		fc.write(ByteBuffer.wrap("Some more".getBytes()));
		fc.close();
		//Read the file:
		fc=new FileInputStream("data.txt").getChannel();
		ByteBuffer buff=ByteBuffer.allocate(BSIZE);
		fc.read(buff);
		buff.flip();
		while(buff.hasRemaining())
			System.out.print((char)buff.get());
	}
}

为了达到更高的速度,可以使用allocateDirect()代替allocate()。

一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。

如果打算使用缓冲器执行进一步的read()操作,就必须调用clear()来为每个read()做好准备。

FileChannel in=new FileInputStream(args[0]).getChannel();
		FileChannel out=new FileOutputStream(args[1]).getChannel();
		ByteBuffer buffer=ByteBuffer.allocate(BSIZE);
		while(in.read(buffer)!=-1){
			buffer.flip();
			out.write(buffer);
			buffer.clear();
		}

当FileChannel.read()返回-1时,表示我们已经到达了输入的末尾。

特殊方法transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连:

FileChannel in=new FileInputStream(args[0]).getChannel();
FileChannel out=new FileOutputStream(args[1]).getChannel();
in.transferTo(0,in.size(),out);

转换数据:缓冲器容纳的是普通的字节,为了把它们转换成字符,我们可以在输入它们的时候对其进行编码,或者

将其从缓冲器输出时对它们进行解码。这里用到了java.nio.charset.Charset类来进行解码。

//Decode using this system's default Charset:
buff.rewind();
String encoding=System.getProperty("file.encoding");
System.out.println("Decoding using "+encoding+": "+
Charset.forName(encoding).decode(buff));

//or we could encode with something that will print:
fc=new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("some text".getBytes("UTF-16BE")));
fc.close();

调用rewind()方法可以返回到数据开始部分。

获取基本类型:

		//Store and read a char array:
		bb.asCharBuffer().put("Howdy!");
		char c;
		while((c=bb.getChar())!=0)
			printnb(c+" ");
		print();
		bb.rewind();

		//Store and read an int:
		bb.asIntBuffer().put(99471142);
		print(bb.getInt());
		bb.rewind();

		//Store and read a long:
		bb.asLongBuffer().put(99471142);
		print(bb.getLong());
		bb.rewind();

		//Store and read a float:
		bb.asFloatBuffer().put(99471142);
		print(bb.getFloat());
		bb.rewind();

		//Store and read a double:
		bb.asDoubleBuffer().put(99471142);
		print(bb.getDouble());
		bb.rewind();
		System.out.println(System.currentTimeMillis()-start);

向ByteBuffer插入基本类型数据的最简单办法是:利用asCharBuffer() asShortBuffer等获得该缓冲器上的视图,然后

使用视图的put()方法。

(1).视图缓冲器:可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。对视图的任何修改都会映射

成为对ByteBuffer中数据的修改。

ByteBuffer bb=ByteBuffer.allocate(BSIZE);
//IntBuffer ib=bb.asIntBuffer();
DoubleBuffer db=bb.asDoubleBuffer();
//Store an array of int:
db.put(new double[]{11,42,47,99,143,811,1016});
//Absolute location read and write:
System.out.println(db.get(3));
db.put(3,1811);
//Setting a new limit before rewinding the buffer
db.flip();
while(db.hasRemaining()){
	double i=db.get();
	System.out.println(i);
}

ByteBuffer通过一个被“包装”过的8字节数组产生,通过各种不同的基本类型的视图缓冲器显示数来:

Thking in java(第四版)-查缺补漏(第18章)

 (2).字节存放次序:”big endian“高位优先,将最重要的字节存放在地址最低的存储单元。

“little endian”低位优先将最重要的字节放在地址最高存储器单元。ByteBuffer是以高位

优先的形式存储数据的。我们可以使用ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN的

order()方法改变ByteBuffer的字节排序方式:

ByteBuffer bb=ByteBuffer.wrap(new byte[12]);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));

(3).缓冲器的细节:Buffer由数据和可以高效地访问和操纵这些数据的四个索引组成,mark(标记)

position(位置) limit(界限) capacity(容量)。

下面是用于设置和复位索引以及查询它们的方法:

 

Thking in java(第四版)-查缺补漏(第18章)

 Thking in java(第四版)-查缺补漏(第18章)

 例如:

package io;
import java.nio.*;
import static tools.Print.*;
public class UsingBuffers {
	private static void symmetricScramble(CharBuffer buffer){
		while(buffer.hasRemaining()){
			buffer.mark();
			char c1=buffer.get();
			char c2=buffer.get();
			buffer.reset();
			buffer.put(c2).put(c1);
		}
	}
	public static void main(String[] args){
		char[] data="UsingBuffers".toCharArray();
		ByteBuffer bb=ByteBuffer.allocate(data.length*2);
		CharBuffer cb=bb.asCharBuffer();
		cb.put(data);
		print(cb.rewind());
		symmetricScramble(cb);
		print(cb.rewind());
		symmetricScramble(cb);
		print(cb.rewind());
	}
}

 使用mark()来设置mark值,用reset()方法把position值设置为mark的值。

12.内存映射文件

允许我们创建和修改那些因为太大而不能放进内存的文件。有了内存映射文件,我们就可以假定整个文件都

放在内存中。例如:

package io;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import static tools.Print.*;
public class LargeMappedFiles {
	static int length=0x8ffffff; //128MB
	public static void main(String[] args) throws Exception{
		MappedByteBuffer out=new RandomAccessFile("data.txt","rw").getChannel()
				.map(FileChannel.MapMode.READ_WRITE,0,length);
		for(int i=0;i<length;i++)
			out.put((byte)'x');
		print("Finished Writing");
		for(int i=length/2;i<length/2+6;i++)
			printnb((char)out.get(i));
	}
}

映射文件中的所有输出必须使用RandomAccessFile。

13.文件加锁

允许我们同步访问某个作为公共资源的文件。例如:

package io;
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
	public static void main(String[] args)throws Exception{
		FileOutputStream fos=new FileOutputStream("file.txt");
		FileLock fl=fos.getChannel().tryLock();
		if(fl!=null){
			System.out.println("Locked File");
			TimeUnit.MILLISECONDS.sleep(100);
			fl.release();
			System.out.println("Released Lock");
		}
	}
}

tryLock()是非阻塞式的,它设法获取锁,当时如果不能获得,他将直接从方法调用返回;

lock()是阻塞式的,它要阻塞进程直到锁可以获得。使用FileLock.release()可以释放锁。

tryLock(long position,long size,boolean shared)

lock(long position,long size,boolean shared)

加锁的区域由size-position决定,第三个参数决定是否共享锁。

无参数的加锁方法将根据文件尺寸的变化而变化,但是具有固定尺寸的锁不随文件尺寸的变化而变化。

对映射文件部分加锁:

package io;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class LockingMappedFiles {
	static final int LENGTH=0x8FFFFF; //128 MB
	static FileChannel fc;
	public static void main(String[] args)throws Exception{
		fc=new RandomAccessFile("data.txt","rw").getChannel();
		MappedByteBuffer out=fc.map(FileChannel.MapMode.READ_WRITE,0,LENGTH);
		for(int i=0;i<LENGTH;i++)
			out.put((byte)'x');
		new LockAndModify(out,0,0+LENGTH/3);
		new LockAndModify(out,LENGTH/2,LENGTH/2+LENGTH/4);
	}
	private static class LockAndModify extends Thread{
		private ByteBuffer buff;
		private int start,end;
		LockAndModify(ByteBuffer mbb,int start,int end){
			this.start=start;
			this.end=end;
			mbb.limit(end);
			mbb.position(start);
			buff=mbb.slice();
			start();
		}
		public void run(){
			try{
				//Exclusive lock with no overlap:
				FileLock fl=fc.lock(start,end,false);
				System.out.println("Locked:"+start+" to "+end);
				//Perform modification:
				while(buff.position()<buff.limit()-1)
					buff.put((byte)(buff.get()+1));
				fl.release();
				System.out.println("Released: "+start+" to "+end);
				
			}catch(IOException e){
				throw new RuntimeException(e);
			}
		}
	}
}

slice()创建了缓冲区。lock()调用类似于获得一个对象的线程锁--我们处在“临界区”,对该部分的文件具有独占访问权。

14.XML

对象序列化只有Java程序才能反序列化这种对象,一种更具互操做性的解决方案是将数据转换为XML格式。

可以使用开源XOM类库(从www.xom.nu下载)。例如:

package io;
import nu.xom.*;
import java.io.*;
import java.util.*;

public class Person {
	private String first,last,address;
	public Person(String first,String last,String address){
		this.first=first;
		this.last=last;
		this.address=address;
	}
	//Produce an XML Element from this Person object:
	public Element getXML(){
		Element person=new Element("person");
		Element firstName=new Element("first");
		firstName.appendChild(first);
		Element lastName=new Element("last");
		lastName.appendChild(last);
		Element addressName=new Element("address");
		addressName.appendChild(address);
		person.appendChild(firstName);
		person.appendChild(lastName);
		person.appendChild(addressName);
		return person;
	}
	public Person(Element person){
		first=person.getFirstChildElement("first").getValue();
		last=person.getFirstChildElement("last").getValue();
		address=person.getFirstChildElement("address").getValue();
	}
	public String toString(){	return first+" "+last+" "+address;}
	
	//Make it human-readable:
	public static void format(OutputStream os,Document doc) throws Exception{
		Serializer serializer=new Serializer(os,"ISO-8859-1");
		serializer.setIndent(4);
		serializer.setMaxLength(60);
		serializer.write(doc);
		serializer.flush();
	}
	public static void main(String[] args)throws Exception{
		List<Person> people =Arrays.asList(
				new Person("Dr Bunsen","Honeydew","1"),
				new Person("Gonzo","The Great","2"),
				new Person("Phillip j","Fry","3")
				);
		System.out.println(people);
		Element root=new Element("people");
		for(Person p: people)
			root.appendChild(p.getXML());
		Document doc=new Document(root);
		format(System.out,doc);
		format(new BufferedOutputStream(new FileOutputStream("People.xml")),doc);
		
	}
}

反序列化:

package io;
import nu.xom.*;
import java.io.*;
import java.util.*;
public class People extends ArrayList<Person>{

	public People(String fileName)throws Exception{
		File file=new File(fileName);
		Document doc=new Builder().build(file);
		Elements elements=doc.getRootElement().getChildElements();
		for(int i=0;i<elements.size();i++)
			add(new Person(elements.get(i)));
	}
	public static void main(String[] args) throws Exception{
		People p=new People("People.xml");
		System.out.println(p);
	}
}

15.Preferences

Preferences API与对象序列化相比,前者与对象持久性更密切,它可以自动存储和读取信息。

只能存储基本类型和字符串,并且每个字符串的存储长度不超过8K。

Preferences API用于存储和读取用户的爱好以及程序配置项的设置。

Preferences是一个键-值集合,存储在一个节点层次结构中。通常创建以你的类命名的单一节点,

将信息存储与其中。例如:

package io;
import java.util.prefs.*;
import static tools.Print.*;
public class PreferencesDemo {
	public static void main(String[] args)throws Exception{
		Preferences prefs=Preferences.userNodeForPackage(PreferencesDemo.class);
		prefs.put("Location","Oz");
		prefs.put("Footwear", "Ruby Slippers");
		prefs.putInt("Companions", 4);
		prefs.putBoolean("Are there witches?",true);
		int usageCount =prefs.getInt("UsageCount",0);
		usageCount++;
		prefs.putInt("UsageCount",usageCount);
		for(String key:prefs.keys())
			print(key+": "+prefs.get(key,null));
		//You must always provide a default value:
		print("How many companions does Dorothy hava? "+prefs.getInt("Companions",0));
	}
}

userNodeForPackage() systemNodeForPackage() 两个都可以用,user用于用户的爱好;system用于通用安装配置。

Preferences API利用合适的系统资源把数据存储到本地,例如Windows里就使用了注册表。

总结

这一章的收获最大的是新的io库,还有内存映射文件。