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库,还有内存映射文件。
上一篇: 编程思想 之「容器深入研究」
下一篇: 谭浩强第四版的第7章课后习题第15题