RandomAccessFile随机访问文件
RandomAccessFile是Object的子类。一般IO流都是成对出现的,但是RandomAccessFile和打印流、序列流一样都是单独存在的。
特点:
1. 和打印流序列流不同的是RandomAccessFile既可以读也可以写。
2. RandomAccessFile的内部其实是一个byte类型的数组,数组可以延长,该数组存在一个指向数组的索引,称之为文件指针。
3. 可以通过getFilePointer方法获取指针的位置,通过seek方法设置指针的位置。
4. 它可以对数组进行读和写,数组是byte类型的,所以RandomAccessFile类其实是对字节输出流和字节输入流进行了封装
5. RandomAccessFile对象的源和目的只能是文件。
写
RandomAccessFile raf = new RandomAccessFile("E:\\raf.txt","rw");
raf.write("张三".getBytes());
raf.writeInt(97);
raf.write("李四".getBytes());
raf.write(98);
raf.close();
输出结果:
张三 a李四b
这里是a和b是因为记事本把写进去的97和98解析了,不管怎么解析,最底层的97和98是不变的,所以这里没有问题。而如果我们修改代码
raf.write(609);
输出结果却也是a。因为int类型是4个字节,它只写最低位的字节。如果我们想写原字节,就要用writeInt方法, 所以a前面有3个空格。
并且如果使用RandomAccessFile创建文件,文件不存在,则创建,文件存在,则不创建。这和一般的输出流不一样,其他的输出流都是不管文件存在不存在都会创建。如果文件存在,继续写入数据,就会把原数据覆盖。
读
RandomAccessFile raf = new RandomAccessFile("E:\\raf.txt","rw");
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println("name="+name);
System.out.println("age="+age);
raf.close();
输出结果:
name=张三
age=97
在读age的时候我们不能拿数组读,读完转成String然后打印出来,因为age应该是一个int类型的值,不能用String类型。所以需要一个专门读int类型值的方法readInt(),读完就是97了。
操作指针读写
设置指针:seek
获取指针:getFilePointer
因为内部其实是一个数组,所以指针位置从0开始。
RandomAccessFile raf = new RandomAccessFile("E:\\raf.txt","rw");
raf.seek(8);
raf.write("王五".getBytes());
raf.writeInt(99);
System.out.println("指针位置在"+raf.getFilePointer()) ;
raf.close();
输出结果:
指针位置在16
//记事本中内容:张三 a王五c,覆盖掉了李四,但是张三依然保留。
PipedInputStream和PipedOutputStream管道流
管道流的主要作用就是可以进行两个线程之间的通讯,一个线程作为管道输出流,一个线程作为管道输入流。它不同于其他的输入输出流,管道输出流只能连接到管道输入流。不建议对这两个对象使用单线程,因为这两个输入输出流是相互关联的。如果恰好先执行的是输入流,而输出流还没有往外写数据,read方法是一个阻塞式方法,这时候就会死锁。
public static void main(String[] args) throws IOException {
PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream();
input.connect(output);
new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}
//管道输入流线程
class Input implements Runnable{
private PipedInputStream in;
Input(PipedInputStream in){
this.in = in;
}
public void run(){
try {
byte[] buf = new byte[1024];
int len = in.read(buf);
String s = new String(buf,0,len);
System.out.println("s="+s);
in.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}
//管道输出流线程
class Output implements Runnable{
private PipedOutputStream out;
Output(PipedOutputStream out){
this.out = out;
}
public void run(){
try {
Thread.sleep(5000);
out.write("hi,管道来了!".getBytes());
} catch (Exception e) {
// TODO: handle exception
}
}
}
输出结果:
hi,管道来了!
DateInputStream和DateOutputStream
DataInputStream和DataOutputStream都是Java中输入输出流的装饰类。数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
这两个流对象也要对应着使用。
这两个流对象中的writeInt、readInt等方法其实在RandomAccessFile类和ObjectInputStream、ObjectOutputStream类中也都存在,但是这些方法的使用场景不同,一个是专门用来随机访问文件的,一个是专门用来对对象序列化的。
如果只想操作Java的基本数据类型,就可以使用DateInputStream和DateOutputStream。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("G:\\datastreamdemo.txt"));
dos.writeUTF("你好");
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("G:\\datastreamdemo.txt"));
String str = dis.readUTF();
System.out.println(str);
dis.close();
输出结果:
你好
ByteArrayInputStream和ByteArrayOutputStream
这两个是操作字节数组的流对象,他们的源和目的都是内存,所以不需要调用系统的底层资源,不需要关流。
ByteArrayOutputStream:
此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
ByteArrayInputStream :
包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("abcd".getBytes());
byte[] buf = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
int ch = 0;
while((ch = bis.read())!=-1){
System.out.println(ch);
}
输出结果:
97
98
99
100