IO(输入/输出)是比较抽象的,看不到明显的运行效果,但输入和输出是所有程 序都必需的部分。使用输入机制,允许程序读取外部数据(包括来自磁盘、光盘等存 储设备的数据、来自网络的数据)、用户输入数据;使用输出机制,允许程序记录运 行状态,将程序数据输出到磁盘、光盘等存储设备中和网络其他设备中。
Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入流、输 出流两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字 节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。除此之 外Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流, 其中节点流用于和底层的物理存储节点直接关联(不同的物理节点获取节点流的方式 可能存在一定的差异),但程序可以把不同的物理节点流包装成统一的处理流,从而 允许程序使用统一的输入、输出代码来读取不同的物理存储节点的资源。
File类是java.io包下代表与平台无关的文件和目录的类。在程序中操作文件和目 录,都可以通过File类来完成。需要注意的是,不管是文件还是目录都是使用File来操 作的,File能新建、删除、重命名文件和目录,但是File不能访问文件内容本身。如果 需要访问文件内容本身,则需要使用输入/输出流。
构造器:
构造器 | 说明 |
---|---|
File(String pathname) | 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实 例。 |
File(String parent, String child) | 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实 例。 |
File(File parent, String child) | 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实 例。 |
File(URI uri) | 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 |
常用方法:
分类 | 返回值 | 方法名 | 说明 |
---|---|---|---|
访问名称与路径相关 | String | getName() | 返回由此抽象路径名表示的文件或目录的名 称。 |
String | getPath() | 将此抽象路径名转换为一个路径名字符串。 | |
String | getParent() | 返回此抽象路径名父目录的路径名字符串; 如果此路径名没有指定父目录,则返回 null。 | |
File | getParentFile() | 返回此抽象路径名父目录的抽象路径名;如 果此路径名没有指定父目录,则返回 null。 | |
File | getAbsoluteFile() | 返回此抽象路径名的绝对路径名形式。 | |
String | getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 | |
检测相关方法 | boolean | exists() | 测试此抽象路径名表示的文件或目录是否存在。 |
boolean | canRead() | 测试应用程序是否可以读取此抽象路径名表示的文件。 | |
boolean | canWrite() | 测试应用程序是否可以修改此抽象路径名表示的文件。 | |
boolean | canExecute() | 测试应用程序是否可以执行此抽象路径名表示的文件。 | |
boolean | isHidden() | 测试此抽象路径名指定的文件是否是一个隐藏文件。 | |
boolean | isFile() | 测试此抽象路径名表示的文件是否是一个标准文件。 | |
boolean | isDirectory() | 测试此抽象路径名表示的文件是否是一个目 录。 | |
boolean | isAbsolute() | 测试此抽象路径名是否为绝对路径名。 | |
获取文件信息 | long | length() | 返回由此抽象路径名表示的文件的长度。 |
long | lastModified() | 返回此抽象路径名表示的文件最后一次被修改的时间。 | |
操作相关 | boolean | createNewFile() | 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 |
static File | createTempFile(String prefix, String suffix) | 在默认临时文件目录中创建一个空文件,使 用给定前缀和后缀生成其名称。 | |
static File | createTempFile(String prefix, String suffix, File directory) | 在指定目录中创建一个新的空文件,使用给 定的前缀和后缀字符串生成其名称。 | |
boolean | delete() | 删除此抽象路径名表示的文件或目录。 | |
void | deleteOnExit() | 在虚拟机终止时,请求删除此抽象路径名表 示的文件或目录。 | |
boolean | renameTo(File dest) | 重新命名此抽象路径名表示的文件。 | |
boolean | mkdir() | 创建此抽象路径名指定的目录。 | |
String[] | list() | 返回一个字符串数组,这些字符串指定此抽 象路径名表示的目录中的文件和目录。 | |
String[] | list(FilenameFilter filter) | 返回一个字符串数组,这些字符串指定此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。 | |
File[] | listFiles() | 返回一个抽象路径名数组,这些路径名表示 此抽象路径名表示的目录中的文件。 | |
File[] | listFiles(FileFilter filter) | 返回抽象路径名数组,这些路径名表示此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。 | |
File[] | listFiles(FilenameFilter filter) | 返回抽象路径名数组,这些路径名表示此抽 象路径名表示的目录中满足指定过滤器的文 件和目录。 | |
static File[] | listRoots() | 列出可用的文件系统根 |
遍历目录例子:
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
File file = new File("文件路径");
printDirectory(file,0);
}
public static void printDirectory(File file,int count){
if (file.exists()){
StringBuffer buffer=new StringBuffer();
if (count!=0) {
buffer.append("|");
}
for (int i = 0; i < count; i++) {
buffer.append("‐‐");
}
if (file.isDirectory()){
System.out.println(buffer.toString()+"["+file.getName()+"]");
File[] files=file.listFiles();
for (File file2:files) {
printDirectory(file2,count+1);
}
}else{
System.out.println(buffer.toString()+">"+file.getName());
}
}
}
}
Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在 Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述 为“流”(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入输出源。 stream是从起源(source)到接收(sink)的有序数据。 Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入输出功能。
流的分类
输入流和输出流
按照流的流向来分,可以分为输入流和输出流。输入、输出都是从程序运行所在 内存的角度来划分的。
输入流:只能从中读取数据,而不能向其写入数据。由 InputStream 和 Reader 作为基类
输出流:只能向其写入数据,而不能从中读取数据。由 OutputStream 和 Writer 作为基类
字节流和字符流
字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单 元不同。
字节流操作的数据单元是8位的字节,由 InputStream 和 OutputStream 作为基类。
字符流操作的数据单元是16位的字符,由 Reader 和 Writer 作为基类
节点流和处理流
按照流的角色来分,可以分为节点流和处理流。
节点流:可以从向一个特定的IO设备(如磁盘、网络)读/写数据的流。也 被称为低级流。
处理流:用于对一个已存在的流进行连接或封装,通过封装后的流来实现数 据读/写功能。也称为高级流。
流的概念模型
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
Java的IO流共涉及40多个类,这些类看上去芜杂而凌乱,但实际上非常规则,而且彼 此之间存在非常紧密的联系Java的IO流的40多个类都是从如下4个抽象基类派生的。
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
节点流和处理流
Java io 分类方式有很多,根据是否直接处理数据,Java io又分为节点流和处理流,节点流是真正直接处理数据的;处理流是装饰加工节点流的。
节点流
文件流:FileInputStream,FileOutputStrean,FileReader,FileWriter,它们都会直接操作文件,直接与 OS 底层交互。因此他们被称为节点流 ,注意:使用这几个流的对象之后,需要关闭流对象,因为 java 垃圾回收器不会主动回收。不过在 Java7 之后,可以在 try() 括号中打开流,最后程序会自动关闭流对象,不再需要显示地 close。
数组流:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter,对数组进行处理的节点流。
字符串流:StringReader,StringWriter,其中 StringReader 能从 String 中读取数据并保存到 char 数组。
管道流:PipedInputStream,PipedOutputStream,PipedReader,PipedWrite,对管道进行处理的节点流。
处理流
处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。
处理流的构造方法总是要带一个其他的流对象做参数。
常用处理流(通过关闭处理流里面的节点流来关闭处理流)
缓冲流 :把数据从原始 流成块读入或把数据积累到一个大数据块后再成批写出,通过减少通过资源的读写次 数来加快程序的执行。BufferedImputStream,BufferedOutputStream,BufferedReader ,BufferedWriter,需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用 flush 方法,另外,BufferedReader 提供一个 readLine( ) 方法可以读取一行,而 FileInputStream 和 FileReader 只能读取一个字节或者一个字符,因此 BufferedReader 也被称为行读取器。
转换流:InputStreamReader,OutputStreamWriter,要 inputStream 或 OutputStream 作为参数,实现从字节流到字符流的转换,我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类。其中InputStreamReader将字节输入流转换成字符输入流, OutputStreamWriter将字 节输出流转换成字符输出流
例:
public static void main(String[] args) throws IOException {
StringBuilder sb = new StringBuilder();
//读取文本文件
InputStream in = new FileInputStream("F:/temp.txt");
InputStreamReader inReader = new InputStreamReader(in);
char[] chars = new char[50];
int count = 0;
while ((count = inReader.read(chars,0,chars.length))!=-1){
sb.append(chars,0,count);
}
inReader.close();
//输出读取的内容
System.out.println(sb.toString());
//将读取到的内容写入文本
OutputStream os = new FileOutputStream("F:/test.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os);
outputStreamWriter.write(sb.toString());
outputStreamWriter.flush();
outputStreamWriter.close();
}
数据流:DataInputStream,DataOutputStream,提供将基础数据类型写入到文件中,或者读取出来。
字节流
字节输入流 InputStream
下面是 IO 中输入字节流的继承关系:
InputStream
ByteArrayInputStream
FileInputStream
FilterInputStream
PushbackInputStream
DataInputStream
BufferedInputStream
LineNumberInputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
StringBufferInputStream
总结:
InputStream 是所有的输入字节流的父类,它是一个抽象类。
PushbackInputStream、DataInputStream 和 BufferedInput Stream都是处理流,他们的父类是 FilterInputStream。
ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从 Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。
InputStream 中的方法:
修饰符/返回值类型 | 方法名 | 说明 |
---|---|---|
int | available() | 返回此输入流下一个方法调用可以不受阻塞地从此输入流读 取(或跳过)的估计字节数。 |
void | close() | 关闭此输入流并释放与该流关联的所有系统资源。 |
void | mark(int readlimit) | 在此输入流中标记当前的位置。 |
boolean | markSupported() | 测试此输入流是否支持 mark 和 reset 方法。 |
abstract int | read() | 读取一个字节数据,并返回读到的数据,如果返回 -1,表示读到了输入流的末尾 |
int | read(byte[] b) | 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。如果返回-1,表示读到了输入流的末尾。 |
int | read(byte[] b, int off, int len) | 将输入流中最多 len 个数据字节读入 byte 数组。如果返回 -1,表示读到了输入流的末尾。off 指定在数组 b 中存放数据的起始偏移位置;len 指定读取的最大字节数。 |
void | reset() | 将此流重新定位到最后一次对此输入流调用 mark 方法时的 位置。 |
long | skip(long n) | 跳过和丢弃此输入流中数据的 n 个字节 |
字节输出流 OutputStream
下面是 IO 中输出字节流的继承关系:
OutputStream
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
BufferedOutputStream
DataOutputStream
PrintStream
ObjectOutputStream
PipedOutputStream
总结:
OutputStream 是所有的输出字节流的父类,它是一个抽象类。
ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向 Byte 数组、和本地文件中写入数据。
PipedOutputStream 是向与其它线程共用的管道中写入数据。
BufferedOutputStream、DataOutputStream 和 PrintStream 都是处理流,他们的的父类是 FilterOutputStream。
outputStream 中的方法:
修饰符/返回值类型 | 方法名 | 说明 |
---|---|---|
void | close() | 关闭此输出流并释放与此流有关的所有系统资源。 |
void | flush() | 刷新此输出流并强制写出所有缓冲的输出字节。 |
void | write(byte[] b) | 将 b.length 个字节从指定的 byte 数组写入此输出 流。 |
void | write(byte[] b, int off, int len) | 将指定 byte 数组中从偏移量 off 开始的 len 个字节写 入此输出流。 |
abstract void | write(int b) | 将指定的字节写入此输出流。 |
字符流
字符输入流 Reader
下面是 IO 中输入字符流的继承关系:
Reader
BufferedReader
LineNumberReader
CharArrayReader
FilterReader
PushbackReader
InputStreamReader
FileReader
PipedReader
StringReader
总结:
Reader 是所有的输入字符流的父类,它是一个抽象类。
CharReader、StringReader 是两种基本的介质流,它们分别将 Char 数组、String 中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它 Reader 对象。
FilterReader 是所有自定义具体装饰流的父类,其子类 PushbackReader 对 Reader 对象进行装饰,会增加一个行号。
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。
Reader 中基本方法:
修饰符/返回值类型 | 方法名 | 说明 |
---|---|---|
abstract void | close() | 关闭该流并释放与之关联的所有资源。 |
void | mark(int readAheadLimit) | 标记流中的当前位置。 |
boolean | markSupported() | 判断此流是否支持 mark() 操作。 |
int | read() | 读取单个字符。 |
int | read(char[] cbuf) | 将字符读入数组。 |
abstract int | read(char[] cbuf, int off, int len) | 读取 len 个字符,从数组 cbuf[] 的下标 off 处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现 |
int | read(CharBuffer target) | 试图将字符读入指定的字符缓冲区。 |
boolean | ready() | 判断是否准备读取此流。 |
void | reset() | 重置该流 |
long | skip(long n) | 跳过字符 |
InputStream和Reader的方法基本一致,只是InputStream读取的是字节,使用的参数是byte数组(byte[]),而Reader读取的是字符,使用的参数是char数组(char[])
字符输出流 Writer
下面是 IO 中输出字符流的继承关系:
Writer
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
FileWriter
PipedWriter
PrintWriter
StringWriter
总结(和字节输出流对应):
Writer 是所有的输出字符流的父类,它是一个抽象类。
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向 Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据。
BufferedWriter 是一个装饰器为 Writer 提供缓冲功能。
PrintWriter 和 PrintStream 极其类似,功能和使用也非常相似。
OutputStreamWriter 是 OutputStream 到 Writer 转换的桥梁,它的子类 FileWriter 其实就是一个实现此功能的具体类。
Writer 中的基本写法:
修饰符/返回值类型 | 方法名 | 说明 |
---|---|---|
Writer | append(char c) | 将指定字符添加到此 writer。 |
Writer | append(CharSequence csq) | 将指定字符序列添加到此 writer。 |
Writer | append(CharSequence csq, int start, int end) | 将指定字符序列的子序列添加到此 writer.Appendable。 |
abstract void | close() | 关闭此流,但要先刷新它。 |
abstract void | flush() | 刷新该流的缓冲。 |
void | write(char[] cbuf) | 写入字符数组。 |
abstract void | write(char[] cbuf, int off, int len) | 写入字符数组的某一部分。 |
void | write(int c) | 写入单个字符。 |
void | write(String str) | 写入字符串。 |
void | write(String str, int off, int len) | 写入字符串的某一部分。 |
I/O 常见用法
1、读取键盘输入,打印到控制台
在刷题网站刷算法题的时候,在程序开头都需要和键盘进行交互,常常用到行夺取器 BufferedReader 和转换流 InputStreamReader。
public static void main(String[] args) throws IOException {
PrintWriter pw = null;
BufferedReader br = null;
try{
System.out.println("请输入:");
pw = new PrintWriter(System.out, true);
br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine())!=null){
if (line.equals("exit")){
System.exit(1);
}
pw.println(line);
}
}catch (IOException e){
e.printStackTrace();
}finally {
pw.close();
br.close();
}
}
运行结果:
2、用字节流读写文件
因为是用字节流来读媒介,所以对应的流是 InputStream 和 OutputStream,并且媒介对象是文件,所以用到子类是 FileInputStream 和 FileOutputStream,这里还可以通过 BufferedInputStream 用缓冲流来读取文件。
public static void main(String[] args) throws IOException {
InputStream is = null;
OutputStream os = null;
try{
is = new FileInputStream("F:\\temp.txt");
os = new FileOutputStream("F:\\FileOutputStream.txt");
byte[] bytes = new byte[4];
int hasRead = 0;
while ((hasRead = is.read(bytes))> 0){
os.write(bytes,0,hasRead);
}
System.out.println("success");
}catch (Exception e){
e.printStackTrace();
}finally {
os.close();
is.close();
}
}
3、用字符流进行读写操作
(1)FileReader 和 FileWriter
public static void main(String[] args) throws IOException {
Reader reader = null;
Writer writer = null;
try{
File rf = new File("F:/temp.txt");
reader = new FileReader(rf);
File wf = new File("F:/test.txt");
writer = new FileWriter(wf);
char[] chars = new char[(int) rf.length()];
int size = reader.read(chars);
System.out.println("大小:" + size + "个字符\n内容:" + new String(chars));
writer.write(chars);
}catch (Exception e){
e.printStackTrace();
}finally {
reader.close();
writer.close();
}
}
运行结果:
(2)StringReader 和 StringWriter
StringReader:用来将字符串转换成字符输入流。然后使用字符输入流提供的方式进 行操作,也可以提供给其他高级字符输入流来使用。如可以将该字符输入流提供给 BufferedReader输入流使用。
StringWriter:在内存中缓存读取到的所有字符串,然后使用通过toString方法一次性 全部输出字符串。
public static void main(String[] args) throws IOException {
StringReader sr = null;
StringWriter sw = null;
try{
String str = "好好学习,天天向上;";
char[] chars = new char[32];
int hasRead = 0;
// StringReader将以String字符串为节点读取数据
sr = new StringReader(str);
while ((hasRead = sr.read(chars))>0){
System.out.println(new String(chars,0,hasRead));
}
// 由于String是一个不可变类,因此创建StringWriter时,实际上是以一个StringBuffer作为输出节点
sw = new StringWriter();
sw.write("光阴似箭,日月如梭;");
// toString()返回sw节点内的数据
System.out.println(sw.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
sw.close();
sr.close();
}
}
运行结果:
4、字节流转换为字符流
在例 3 中用字符流读文件时,打印到控制台的中文会乱码,使用转换流可以解决这一问题。
public static void main(String[] args) throws IOException {
InputStream is = null;
Reader reader = null;
try{
File file = new File("F:/temp.txt");
is = new FileInputStream(file);
reader = new InputStreamReader(is,"utf-8");//或者gbk
char[] chars = new char[(int) file.length()];
int size = reader.read(chars);
System.out.println("大小:" + size + "个字符\n内容:" + new String(chars));
}catch (Exception e){
e.printStackTrace();
}finally {
reader.close();
is.close();
}
}
运行结果:
5、随机读写文件
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供 了众多的方法 来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。 RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来读 写数据。
RandomAccessFile四种访问模式:
"r":以只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法, 都将抛出 IOException 异常。
"rw":以读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。
"rws”:以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容或元 数据的每个更新都同步写入到底层存储设备。
"rwd": 以读、写方式打开指定文件。相对于"rw"模式,还要求对文件的内容的每 个更新都同步写入到底层存储设备。
元数据是文件的附加属性,如文件大小、创建时间、所有者等信息。
示例1:使用 RandomAccessFile 可以实现对文件的随机读取,主要是通过 seek() 方法实现指针偏移。
public static void main(String[] args) throws IOException {
RandomAccessFile randomAccessFile = null;
try{
// 创建一个RandomAccessFile对象
randomAccessFile = new RandomAccessFile("F:/temp.txt","rw");
// 通过seek方法来移动指针
randomAccessFile.seek(10);
// 获取当前指针
long pointerBegin = randomAccessFile.getFilePointer();
// 从当前指针开始读
byte[] bytes = new byte[10];//再读10,也就是20
randomAccessFile.read(bytes);
long pointerEnd = randomAccessFile.getFilePointer();
System.out.println("pointerBegin:" + pointerBegin + "\npointerEnd:" + pointerEnd + "\n" + new String(bytes));
//RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。
randomAccessFile.seek(20);
// 获取当前指针
long begin = randomAccessFile.getFilePointer();
randomAccessFile.write(bytes);
long end = randomAccessFile.getFilePointer();
System.out.println("begin:" + begin + "\nend:" + end);
}catch (Exception e){
e.printStackTrace();
}finally {
randomAccessFile.close();
}
}
运行结果:
操作前的文件内容:
操作后的控制台输出:
操作后的文件:
示例2:
public class RandomAccessFileDemo {
public static void main(String[] args) throws Exception {
demo1();
//demo2();
//demo3();
}
/**
* RandomAccessFile 读取(只读)
* @throws Exception
*/
public static void demo1() throws Exception {
// 实例化RandomAccessFile类,指定只读模式
RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt","r");
// 调到指定位置开始读
accessFile.seek(5);
// 读取文件
byte[] bytes = new byte[1024];
int count = 0;
while ((count = accessFile.read(bytes, 0, bytes.length)) != -1){
System.out.println(new String(bytes,0,count));
}
//关闭流
accessFile.close();
}
/**
* 在文件最后追加
* @throws Exception
*/
public static void demo2() throws Exception {
// 读写模式
RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt", "rw");
// 跳到文件末尾位置
accessFile.seek(accessFile.length());
accessFile.write("你好,RandomAccessFile.".getBytes(Charset.forName("UTF-8")));
//关闭流
accessFile.close();
}
/** 如果需要向指定位置插入内容,
* 程序需要先把插入点后面的内容读入缓冲区,
* 等把需要插入的数据写入文件后,
* 再将缓冲区的内容追加到文件后面。
*/
public static void demo3() throws Exception {
//存放读取到的 插入点后面的内容
ByteArrayOutputStream arrayout = new ByteArrayOutputStream();
RandomAccessFile accessFile = new RandomAccessFile("F:/temp.txt", "rw");
// 调到指定位置
accessFile.seek(5);
// 读取指定位置后的内容到 arrayout
byte[] bytes = new byte[1024];
int count = 0;
while ((count = accessFile.read(bytes, 0, bytes.length)) != -1) {
arrayout.write(bytes, 0, count);
}
// 调到指定位置
accessFile.seek(5);
// 插入想插入的内容
accessFile.write("(你好)".getBytes(Charset.forName("UTF-8")));
// 读取arrayout 里面的内容,追加会源文件
accessFile.write(arrayout.toByteArray());
//关闭流
accessFile.close();
}
}
输出结果:
文件:
demo1:
demo2:
demo3:
6、读写管道
管道流是用来在多个线程之间进行信息传递的Java流,被号称是最难使用的流,被使用的频率比较低
它提供了多线程间信息传输的一种有效手段。 管道流包括四个类 PipedOutputStream/PipedWriter 和 PipedInputStream/PipedReader。 其中 PipedOutputStream/PipedWriter 是写入者/生产者/发送者;PipedInputStream/PipedReader是读取者/消费者/接收者。
在使用管道流之前,需要注意以下要点:
管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;
管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或 者connect函数进行连接;
一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可 以使用带有参数的构造器;
管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时, 写操作阻塞;
管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到1 值。
示例1:管道流要成对使用
public static void main(String[] args) throws IOException {
final PipedOutputStream pos = new PipedOutputStream();
final PipedInputStream pis = new PipedInputStream(pos);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
pos.write("Hello world, pipe!".getBytes());
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
int data = pis.read();
while (data != -1){
System.out.print((char) data);
data = pis.read();
}
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
输出结果:
示例2:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {
PipedWriter pw = new PipedWriter();
PipedReader pr = new PipedReader();
//连接两个线程的管道流
pw.connect(pr);
ThreadWriter a = new ThreadWriter(pw);
ThreadReader b = new ThreadReader(pr);
a.start();
b.start();
}
}
class ThreadWriter extends Thread{
//声明管道流
private PipedWriter writer;
//构造器
public ThreadWriter(PipedWriter writer) {
super();
this.writer = writer;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
writer.write("这是ThreadWriter! count:"+i+"\r\n");
writer.flush();
}
writer.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
class ThreadReader extends Thread{
//声明管道流 发送着和接收者
private PipedReader reader;
//构造器
public ThreadReader(PipedReader reader) {
super();
this.reader = reader;
}
@Override
public void run() {
try {
String str="";
BufferedReader bReader=new BufferedReader(reader);
while ((str=bReader.readLine())!=null) {
System.out.println("ThreadReader:"+str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出结果:
7、将多个输入流当成一个输入流依次读取
public static void main(String[] args) throws IOException {
FileInputStream fis1 = new FileInputStream("F:/test1.txt");
FileInputStream fis2 = new FileInputStream("F:/test2.txt");
SequenceInputStream sis = new SequenceInputStream(fis1,fis2);
FileOutputStream fos = new FileOutputStream("F:/test3.txt");
int temp;
while ((temp= sis.read())!=-1){
System.out.print((char) temp);
fos.write(temp);
}
fos.close();
sis.close();
fis2.close();
fis1.close();
}
文件:
运行结果:
8、推回输入流使用实例
一般情况下使用输入流从磁盘,网络或者其它的物理介质读取数据都是顺序读取的, 在流的内部会维护一个指针,读取数据的同时,指针会向后移动,直到读完为止。
在一些实际常见中,如果读出来的数据不是想要的又不能再放回去怎么办?
可以使用io提供的推回输入流。使用普通的IO输入流如果读取到不想要的数据,只能 在程序里面处理掉,而使用IO里面的推回输入流读取数据则可以把数据给推回到输入 流的缓冲区中。
推回输入流主要为两个类: PushbackInputStream 和 PushbackReader
public static void main(String[] args) throws IOException {
// 创建推回输入流 指定推回缓冲区大小为64,
//如果不指定,默认缓冲区大小为1
PushbackReader pr = new PushbackReader(new FileReader("F:/test1.txt"),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("A")) > 0){
System.out.println(targetIndex);
// 找到目标字符串
// 将本次内容和上次内容一起推回缓冲区
// *****推回缓冲区的内容大小不能超过缓冲区的大小
pr.unread((lastContent + content).toCharArray());
// 判断 targetIndex 是否 > 32(临时数组大小)
if (targetIndex > 32) {
buf = new char[targetIndex];
}
// 再次读取指定长度的内容(就是目标字符串之前的内容)
pr.read(buf, 0, targetIndex);
System.out.println(new String(buf, 0, targetIndex));
System.out.println(new String(buf, targetIndex, buf.length-targetIndex));
System.exit(0);
}else{
System.out.println(lastContent);
lastContent = content;
}
}
}
运行结果: