推回输入流:
PushbackInputStream和PushbackReader提供了如下三个方法:
Void unread(byte[]/char[] buf):将一个字节/字符数组内容推回缓冲区里,从而允许重复读取刚刚读取的内容
Void unread(byte[]/char[] b,int off,int len):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到缓冲区里,从而允许重复读取刚刚读取的内容。
Void unread(int b):将一个字节/字符推回到缓冲区里,从而允许重复读取刚刚读取的内容。
当我们创建一个PushbackInputStream和PushbackReader时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1,如果程序中推回到推回区缓冲区的内容超出了推回缓冲区的大小,程序将回引发Pushback buffer overflow 的IOException
推回缓冲区的长度与read方法的数组参数的长度没有任何关系。
public class PushbackTest {
public static void main(String[] args) {
PushbackReader pr=null;
try {
//创建一个PushbackReader对象,指定推回缓冲区长度为64
pr=new PushbackReader(new FileReader(“PushbackTest.java”),64);
char[] buf=new char[32];
//用以保存上次读取的字符串内容
String lastContent="";
int hasRead=0;
//循环读取文件内容
while((hasRead=pr.read(buf))>0)
{
//将读取的内容转换成字符串
String content=new String(buf,0,hasRead);
int targetIndex=0;
//将上次读取的字符串和本次读取的字符串拼接起来,查看是否包含目标字符串
//如果包含目标字符串
if((targetIndex=(lastContent+content).indexOf(“new PushbackReader”))>0)
{
//将本次内容和上次内容一起推回缓冲区
pr.unread((lastContent+content).toCharArray());
//再次读取指定长度的内容(就是目标字符串之前的内容)
pr.read(buf,0,targetIndex);
//打印输出内容
System.out.println(new String (buf,0,targetIndex));
System.exit(0);
}
else
{
//打印上次读取的内容
System.out.println(lastContent);
//将本次内容设为上次读取的内容
lastContent=content;
}
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(pr!=null)
pr.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
重定向标准输入/输出:
在System类里提供了三个重定向标准输入/输出的方法:
Static void setErr(PrintStream err):重定向“标准”错误输出流。
Static void setIn(InputStream in):重定向“标准”输入流。
Static void setOut(PrintStream out):重定向“标准”输出流
public class RedirectOut {
public static void main(String[] args) {
PrintStream ps=null;
try {
//一次性创建PrintStream输出流
ps=new PrintStream(“out.txt”);
//将标准输出重定向ps输出流
System.setOut(ps);
//向标准输出输出一个字符串
System.out.println(“普通字符串”);
//向标准输出输出一个对象
System.out.println(new RedirectOut());
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
if(ps!=null)
{
ps.close();
}
}
}
}
如:
public class RedirectOut {
public static void main(String[] args) {
FileInputStream fis=null;
try {
//一次性创建PrintStream输出流
fis=new FileInputStream(“FileOutputStreamTest.java”);
//将标准输出重定向fis到输入流
System.setIn(fis);
//使用Scanner 创建对象,用于获取标准输入
Scanner sc=new Scanner(System.in);
sc.useDelimiter("\n");
//判断是否有下一项
while(sc.hasNext())
{
//输出输入项
System.out.println(“键盘输入的是:”+sc.next());
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
if(fis!=null)
{
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Java虚拟机读写其他进程的数据
使用Runtime对象的exec方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程,Process类提供了如下三个方法,用于让程序其子进程进行通信:
1. InputStream getErrorStream():获取子进程的错误流。
2. InputStream getInputStream():获取子进程的输入流
3. OutputStream getOutputStream():获取子进程的输出流。
如:public class ReadFromProcess {
public static void main(String[] args) {
BufferedReader br=null;
try {
//运行javac命令,返回运行该命令的子进程
Process p=Runtime.getRuntime().exec(“javac”);
//以p进程的错误流创建BufferedReader对象
//这个错误流对本程序时输入流,对p进程则是输出流
br=new BufferedReader(new InputStreamReader(p.getErrorStream()));
String buff=null;
//采取循环方式来读取p进程的错误输出
while((buff=br.readLine())!=null)
{
System.out.println(buff);
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(br!=null)
{
br.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
如:在Java程序中启动Java虚拟机运行另一个Java程序,并向另一个Java程序中输入
数据。
public class WriterToProcess {
public static void main(String[] args) {
PrintStream ps=null;
try {
//运行Java ReadStandard命令,返回运行该命令的子进程
Process p=Runtime.getRuntime().exec(“java ReadStandard”);
// Process p=Runtime.getRuntime().exec(“java -classpath bin File.ReadStandard”);
//这个输出流对本程序是输出流,对p进程则是输入流
ps=new PrintStream(p.getOutputStream());
//向ReadStandard程序写入内容们这些内容将被Read Standard读取
ps.println(“你儿子的爸爸”);
ps.println(new WriterToProcess());
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
if(ps!=null)
ps.close();
}
}
}
//定义一个ReadStandard类,该类接受标准输入
//并将标准输入写入Out.txt文件
class ReadStandard{
public static void main(String[] args) throws Exception {
//使用System.in创建Scanner对象,用于获得标准输入
Scanner sc=new Scanner(System.in);
PrintStream ps=new PrintStream(new FileOutputStream(“but.txt”));
//增加下面一行将回车符作为分隔符
sc.useDelimiter("\n");
//判断是否还有下一个输入项
while(sc.hasNext())
{
//输出输入项
ps.println(“键盘输入的是:”+sc.next());
}
ps.close();
}
}
RandomAccessFile:
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意方法来读写数据。
RandomAccessFile允许自由定位文件记录指针,所以RandomAccessFile可以不从开始的地方开始输出,所以RandomAccessFile可以向己存在的文件后追加内容。
RandomAccessFile对象也包含了一个记录指针,用以表示当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读写n个字节后,文件指针将会向后移动n个字节。除此之外,RandomAccessFile可以自由移动该记录指针,既可以向前移动,也可以向后移动,RandomAccessFile包含了如下两个方法来操作文件记录指针:
1. long getFilePointer():返回文件记录指针的当前位置
2. Void seek(long pos):将文件记录指针定位到Pos位置
RandomAccessFile既可以读文件,也可以写文件。包含了系列的readXXX和writeXXX方
法来完成输入,输出。
RandomAccessFile有两个构造器,这两个构造器基本相同,只是对文件的形式不同:一
个使用String参数来指定文件名,一个使用File参数来指定文件本身,除此之外,创建RandomAccessFile对象时还需要制定一个mode参数,该参数指定了RandomAccessFile的访问模式,该参数有如下四个值:
1.“r”:以只读的方式打开指定文件,如果试图对该RandomAccessFile执行写入方法都将会抛出IOException
2.“rw”:以读写方式打开指定文件。如果该文件不错在,则尝试创建该文件
3.“rws”:以读写方式打开指定文件。还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
如:public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf=null;
try {
//以只读方式打开一个RandomAccessFile对象
raf=new RandomAccessFile(“RandomAccessFileTest.java”, “r”);
//获取RandomAccessFile对象文件指针的位置,初始值时0
System.out.println(“RandomAccessFile的文件初始位置:”+raf.getFilePointer());
//移动raf的文件记录指针的位置
raf.seek(300);
byte[] bbuf=new byte[1024];
//用于保存实际读取的字节数
int hasRead=0;
while((hasRead=raf.read(bbuf))>0)
{
//串联成字符串
System.out.println(new String(bbuf,0,hasRead));
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(raf!=null)
{
raf.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
为了追加内容程序应该先将记录指针移动到文件末尾,然后再开始向文件输出内容:
public class AppendContent {
public static void main(String[] args) {
RandomAccessFile raf=null;
try {
//以读,写方式打开一个RandomAccessFile对象
raf=new RandomAccessFile(“out.txt”, “rw”);
//将文件指针移动到out.txt文件的最后
raf.seek(raf.length());
raf.write(“追加内容:\r\n”.getBytes());
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(raf!=null)
{
raf.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置开始输出,则新数据会覆盖文件中原有的内容。如果向文件指定位置插入内容,程序需要先把插入点后面的内容读取到缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。
如:public class InsertContent {
public static void insert(String fileName,long pos,String insertContent) throws Exception
{
RandomAccessFile raf=null;
//创建一个临时文件来保存插入点后的数据
File tmp=File.createTempFile(“tmp”, null);
FileOutputStream tmpOut=null;
FileInputStream tmpIn=null;
tmp.deleteOnExit();
try {
raf=new RandomAccessFile(fileName, “rw”);
tmpOut=new FileOutputStream(tmp);
tmpIn=new FileInputStream(tmp);
raf.seek(pos);
//将插入节点后的内容读入临时文件保存
byte[] bbuf=new byte[64];
//用于保存的实际读写数
int hasRead=0;
//使用循环方式读取插入点后的数据
while((hasRead=raf.read(bbuf))>0)
{
//写入临时文件
tmpOut.write(bbuf,0,hasRead);
}
//插入:
//重新定位插入位置
raf.seek(pos);
//需要插入的内容
raf.write(insertContent.getBytes());
while((hasRead=tmpIn.read(bbuf))>0) {
raf.write(bbuf,0,hasRead);
}
} finally {
raf.close();
}
}
public static void main(String[] args) throws Exception {
insert("InsertContent.java", 45, "插入的内容\r\n");
}
}
对象序列化:
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络这将这种二进制文件传输到另一个网络节点,其他程序一旦获得了这种二进流,都可以将这种二进制流恢复成原来的Java对象。
序列化的含义和意义:
序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(De Serialize)则指从IO流中恢复该Java对象。
如果需要让某个对象可以支持序列化机制,必须让他的类是可序列化的(Serializable),为了让某个类是可序列化的,该类必须实现如下两个接口:
1. Serializable
2. Externalizable
Java的很多类已经实现了Serializable,该接口是一个标记接口,实现该接口无需实现任
何方法,他只表名该类的实例是可序列化的。所有可能在网络上传输的对象的类可能是可序列化的,否则程序将出现异常。所有需要保存到磁盘里的对象的类都必须可序列化。
因为序列化是RMI过程的参数和返回值都必须实现的机制,而RMI又是JavaEE的基础:所有分布式应用常常需要跨平台,跨网络,因此要求所有传递参数,返回值必须实现序列化。通常程序创建每个JavaBean类都是先Serializable
使用对象流实现序列化:
如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。
使用Serializable来实现序列化,主要让目标类实现Serializable标记接口即可,无需实现任何方法。
1. 创建一个ObjectOutputStream,这个输出流是一个处理流没所以必须建立在其他节点流
的基础之上。
//创建一个ObjectoutputStream
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(“object.txt”))
2. 调用ObjectOutputStream对象的writeObject方法输出可序列化的对象,如:
//将一个person对象输出到始输出流中
Oos.write(per);
如:
public class Preson implements java.io.Serializable{
private String name;
private int age;
//不提供无参构造器
public Preson(String name, int age) {
System.out.println(“有参构造”);
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class WriteObject {
public static void main(String[] args) {
ObjectOutputStream oos=null;
try {
//创建一个ObjectOutputStream输出流
oos=new ObjectOutputStream(new FileOutputStream(“object.txt”));
Preson per=new Preson(“猴子”, 500);
//将per对象写入输出流
oos.writeObject(per);
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(oos!=null)
{
oos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
如果希望从二进制流中恢复Java对象,则需要反序列化,步骤:
1. 创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上。如:
//创建一个ObjectInputStream输入流
ObjectInputStream osi=new ObjectInputStream(new FileInputStream(“object.txt”));
2. 调用ObjectInputStream对象的Read Object方法读取流中的对象,该方法返回一个Object类型的Java对象。
//从输入流中读取一个Java对象,并将其强制类型转化为Person类
Person p=(person)Ois.readObject();
如:
public class ReadObject {
public static void main(String[] args) throws ClassNotFoundException {
ObjectInputStream ois=null;
try {
//创建一个ObjectInputStream输出流
ois=new ObjectInputStream(new FileInputStream(“object.txt”));
//从输入流种凡读取一个Java对象
Preson p=(Preson)ois.readObject();
System.out.println(“名字:”+p.getName()+“年龄:”+p.getAge());
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
try {
if(ois!=null)
{
ois.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
反序列化仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。
在objectInputStream输入流中的readObject方法声明抛出了ClassNotFoundException异常,也就是说当反序列化时找不到对应的Java类时将会引发异常。
反序列化机制无需通过构造器来初始化Java对象。如果我们向文件中使用序列化机制写入了多个Java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取。如果一个可序列化类有多个父类(包括直接或间接父类),则该类的所有父类要么是可序列化的,要么是无参数的构造器——否则会反序列化时抛出InvalidClassException异常
当程序创建子类实例时,系统会隐式的为他的所有父类都创建实例(并建立和此子类实例的关联)!当我们反序列化某个子类的实例时,反序列化机制需要恢复其关联的父类实例,恢复这些父类实例有两种方式:
(1):使用反序列化机制
(2):使用父类的无参构造器
反序列化优先采用第一种机制。