推回输入流
在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackReader和PushbackInputStream:
这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
当程序创建一个PushbackReader和PushbackInputStream时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow的IOException异常。
PushbackTest.java
public class PushbackTest
{
public static void main(String[] args)
{
try(PushbackReader pr = new PushbackReader(new FileReader("PushbackTest.java") , 64);)
{
//创建一个PushbackReader对象,指定推回缓冲区的长度为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.print(new String(buf , 0 ,targetIndex));
System.exit(0);
}
else
{
//打印上次读取的内容
System.out.print(lastContent);
//将本次内容设为上次读取的内容
lastContent = content;
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
重定向标准输入/输出
Java的标准输入/输出分别通过System.in和System.out来代表,在默认的情况下它们分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到屏幕;
在System类里提供了如下三个重定向标准输入/输出的方法:
- static void setErr(PrintStream err):重定向“标准”错误输出流;
- static void setIn(InputStream in):重定向“标准”输入流;
- static void setOut(PrintStream out):重定向“标准”输出流;
RedirectOut.java
public class RedirectOut
{
public static void main(String[] args)
{
try(PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));) //创建PrintStream输出流
{
//将标准输出重定向到ps输出流
System.setOut(ps);
//向标准输出输出一个字符串
System.out.println("普通字符串");
//向标准输出输出一个对象
System.out.println(new RedirectOut());
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
RedirectIn.java
public class RedirectIn
{
public static void main(String[] args)
{
try(FileInputStream fis = new FileInputStream("RedirectIn.java"))
{
//将标准输入重定向到fis输入流
System.setIn(fis);
//使用System.in创建Scanner对象,用于获取标准输入
Scanner sc = new Scanner(System.in);
//增加下面一行将只把回车作为分隔符
sc.useDelimiter("\n");
//判断是否还有下一个输入项
while(sc.hasNext())
{
//输出输入项
System.out.println("键盘输入的内容是:" + sc.next());
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
运行,程序不会等待用户输入,而是直接输出了RedirectIn.java文件的内容,这表明程序不再使用键盘作为标准输入,而是使用RedirectIn.java作为标准输入源。
RandomAccessFile
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输出流不同的是,RandomAccessFile支持“随机访问”的形式,程序可以直接跳转到文件的任意地方来读写数据。
由于 RandomAccessFile可以自由访问文件的任意位置,所以如果只需要访问文件部分内容,而不是把文件从头都到尾,使用RandomAccessFile将是更好的选择。
RandomAccessFile允许自由定位文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。
注:RandomAccessFile的方法虽多,但它有一个很大的局限,就是只能读写文件,不能读写其他IO节点。
RandomAccessFileTest.java
public class RandomAccessFileTest
{
public static void main(String[] args)
{
//以只读方式打开一个RandomAccessFile对象
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.print(new String(bbuf , 0 , hasRead ));
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
程序将从300字节处开始读。运行,看到程序只读取后面部分的效果;
AppendContent.java
public class AppendContent
{
public static void main(String[] args)
{
//以读、写方式打开一个RandomAccessFile对象
try(RandomAccessFile raf = new RandomAccessFile("out.txt" , "rw"))
{
//将记录指针移动的out.txt文件的最后
raf.seek(raf.length());
raf.write("追加的内容!\r\n".getBytes());
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
InsertContent.java
public class InsertContent
{
public static void insert(String fileName , long pos ,
String insertContent)throws IOException
{
//创建一个临时文件来保存插入点后的数据
File tmp = File.createTempFile("tmp" , null);
tmp.deleteOnExit();
try(RandomAccessFile raf = new RandomAccessFile(fileName , "rw");
FileOutputStream tmpOut = new FileOutputStream(tmp);
FileInputStream 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);
}
//----------下面代码插入内容----------
//把文件记录指针重新定位到pos位置
raf.seek(pos);
//追加需要插入的内容
raf.write(insertContent.getBytes());
//追加临时文件中的内容
while ((hasRead = tmpIn.read(bbuf)) > 0 )
{
raf.write(bbuf , 0 , hasRead);
}
}
}
public static void main(String[] args) throws IOException
{
insert("InsertContent.java" , 45 , "插入的内容\r\n");
}
}
每次运行,都会看到InsertContent.java中插入了一行字符串:
序列化
序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的。为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable
- Externalizable
大部分基本都采用Serializable接口方式来实现序列化;
使用对象流实现序列化
一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象:
1:创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上;
//创建一个ObjectOutputStream输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
2:调用ObjectOutputStream对象的writeObject()方法输出可序列化对象;
//将per对象写入输出流
oos.writeObject(per);
Person.java
public class Person
implements java.io.Serializable
{
private String name;
private int age;
public Person(String name , int age)
{
System.out.println("有参数的构造器");
this.name = name;
this.age = age;
}
}
WriteObject.java
public class WriteObject
{
public static void main(String[] args)
{
//创建一个ObjectOutputStream输出流
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt")))
{
Person per = new Person("孙悟空", 500);
//将per对象写入输出流
oos.writeObject(per);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
运行,将会生成一个object.txt文件,该文件的内容就是Person对象:
如果希望从二进制流中恢复Java对象,则需要使用反序列化。反序列化的步骤如下:
1:创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上;
//创建一个ObjectInputStream输出流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
2:调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型;
//从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person p = (Person)ois.readObject();
ReadObject.java
public class ReadObject
{
public static void main(String[] args)
{
//创建一个ObjectInputStream输出流
try(ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("object.txt"));)
{
//从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person p = (Person)ois.readObject();
System.out.println("名字为:" + p.getName()
+ "\n年龄为:" + p.getAge());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
对象引用的序列化
如果某个类的成员变量的类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的;
版本
反序列化Java对象时必须提供该对象的class文件,现在的问题是,随着项目的升级,系统的class文件也会升级,Java如何保证两个class文件的兼容性?
Java序列化机制允许为序列化类提供一个private static final的serialVersionUID值,该类变量的值用于标识该Java类的序列化版本,也就是说,如果一个类升级后,只要它的serialVersionUID类变量的值保持不变,序列化机制也会把它们当成同一个序列化版本。
private static final long serialVersionUID = 512L;
NIO
使用Buffer
在Buffer中有三个重要的概念:容量(capacity)、界限(limit)和位置(position)。
- 容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变;
- 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写;
- 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到 了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中第三个(第一个位置的索引为0)位置。
BufferTest.java
public class BufferTest
{
public static void main(String[] args)
{
//创建Buffer
CharBuffer buff = CharBuffer.allocate(8); //1
System.out.println("capacity: "
+ buff.capacity());
System.out.println("limit: "
+ buff.limit());
System.out.println("position: "
+ buff.position());
//放入元素
buff.put('a'); //2
buff.put('b'); //3
buff.put('c'); //4
System.out.println("加入三个元素后,position = "
+ buff.position());
//调用flip()方法
buff.flip(); //5
System.out.println("执行flip()后,limit = "
+ buff.limit());
System.out.println("position = "
+ buff.position());
//取出第一个元素
System.out.println("第一个元素(position=0):"
+ buff.get()); //6
System.out.println("取出一个元素后,position = "
+ buff.position());
//调用clear方法
buff.clear(); //7
System.out.println("执行clear()后,limit = "
+ buff.limit());
System.out.println("执行clear()后,position = "
+ buff.position());
System.out.println("执行clear()后,缓冲区内容并没有被清除:"
+ buff.get(2)); //8
System.out.println("执行绝对读取后,position = "
+ buff.position());
}
}
1号代码处: CharBuffer的一个静态方法allocate()创建了一个capacity为8的CharBuffer。此时:
4号代码处取出一个元素后position向后移动一位;
使用Channel
FileChannelTest.java
public class FileChannelTest
{
public static void main(String[] args)
{
FileChannel inChannel = null;
FileChannel outChannel = null;
try
{
File f = new File("FileChannelTest.java");
//创建FileInputStream,以该文件输入流创建FileChannel
inChannel = new FileInputStream(f)
.getChannel();
//将FileChannel里的全部数据映射成ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,
0 , f.length());
//使用GBK的字符集来创建解码器
Charset charset = Charset.forName("GBK");
//以文件输出流创建FileBuffer,用以控制输出
outChannel = new FileOutputStream("a.txt")
.getChannel();
//直接将buffer里的数据全部输出
outChannel.write(buffer);
//再次调用buffer的clear()方法,复原limit、position的位置
buffer.clear();
//创建解码器(CharsetDecoder)对象
CharsetDecoder decoder = charset.newDecoder();
//使用解码器将ByteBuffer转换成CharBuffer
CharBuffer charBuffer = decoder.decode(buffer);
//CharBuffer的toString方法可以获取对应的字符串
System.out.println(charBuffer);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
字符集和Charset
Charset类提供了一个availableCharsets()静态方法来获取当前JDK所支持的所有字符集。
CharsetTest.java
public class CharsetTest
{
public static void main(String[] args)
{
//获取全部字符集
SortedMap<String,Charset> map = Charset.availableCharsets();
for (String alias : map.keySet())
{
//输出字符集的别名和对应的Charset对象
System.out.println(alias + "----->"
+ map.get(alias));
}
}
}
CharsetTransform.java
public class CharsetTransform
{
public static void main(String[] args)
throws Exception
{
//创建简体中文对应的Charset
Charset cn = Charset.forName("GBK");
//获取cn对象对应的编码器和解码器
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
//创建一个CharBuffer对象
CharBuffer cbuff = CharBuffer.allocate(8);
cbuff.put('孙');
cbuff.put('悟');
cbuff.put('空');
cbuff.flip();
//将CharBuffer中的字符序列转换成字节序列
ByteBuffer bbuff = cnEncoder.encode(cbuff);
//循环访问ByteBuffer中的每个字节
for (int i = 0; i < bbuff.capacity() ; i++)
{
System.out.print(bbuff.get(i) + " ");
}
//将ByteBuffer的数据解码成字符序列
System.out.println("\n"
+ cnDecoder.decode(bbuff));
}
}
以上只是学习所做的笔记,不做任何用途(其实就是照着书抄啦)!!!
书籍:疯狂Java讲义