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

背景

继续查缺补漏,加油

1.InputStream类型

2.OutputStream类型

 

 3.FilterInputStream和FilterOutputStream

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

InputStream和OutputStream派生而来

(1).FilterInputStream类型

(2).FilterOutputStream类型

 4.Reader和Writer

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

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

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

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

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

 更改流的行为:

 

无论我们何时使用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字节数组产生,通过各种不同的基本类型的视图缓冲器显示数来:

 (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(容量)。

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

 

 例如:

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库,还有内存映射文件。

猜你喜欢

转载自blog.csdn.net/a614528195/article/details/82415439
今日推荐