什么是IO流?
IO是指应用程序对设备数据的输入输出操作。流的本质是数据传输。例如:键盘是输入设备,而显示器则是输出设备。在Java中定义了各种各样的输入输出方式,它们都被存放在java.io包中。
IO流的分类
- 根据处理数据类型分为:字符流和字节流,字符流操作的是16位二进制,字节流操作的是8位二进制。
- 根据数据流向分为:输入流和输出流
IO包中类层次关系图
File类
File类是IO包中唯一代表磁盘文件本身的对象。通过File来创建,删除,重命名文件,判断文件的读写权限以及文件是否存在,设置和查询文件的最近修改时间等。但File类不是InputStream、OutputStream或Reader、Writer的子类,因为它不负责数据的输入输出,而专门用来管理磁盘文件与目录。File类主要用于命名文件、查询文件属性和处理文件目录。
生成File对象的构造方法:
1)File (String directorypath)
例:File file=new File("test.txt"); //根据路径获得相应的File对象
相对路径:相对于某个文件的路径。
绝对路径:一个固定的路径,具体到某个盘。
2)File(URI uri)
3)File (String parent , String child)
例:File file=new File(“/Users/wq/Desktop/outInFile","test.txt") ;
4)File (File parent , String child)
例: File file=new File("/Users/wq/Desktop/outInFile");
File file1=new File(file,"test.txt"); //在如果/Users/wq/Desktop/outInFile目录不存在则需要先使用file.mkdir()先创建。
一个对应于某磁盘文件或目录的File对象一经创建, 就可以通过调用它的方法来获得文件或目录的属性。
1)boolean exists( ) 判断文件或目录是否存在
2)boolean isFile( ) 判断是文件还是目录
3)boolean isDirectory( ) 判断是文件还是目录
4)String getName( ) 返回文件名或目录名
5)String getPath( ) 返回文件或目录的路径
6)long length( ) 获取文件的长度
7)String getParent( ) 获得父文件夹名称
8)long lastModified( ) 获取文件的最后修改时间
9)String getAbsolutePath() 返回文件或目录的绝对路径
10) ......
File类中还定义了一些对文件或目录进行管理、创建、删除:
1) boolean renameTo( File newFile ); 重命名文件
2) void delete( ); 删除文件
3) boolean mkdir( ); 创建目录
4)boolean createNewFile(); 创建文件
RandomAccessFile类
RandomAccessFile类支持随机访问,可以跳转到文件的任意位置处读写数据。当要访问一个文件时,不想把文件从头访问到尾,RandomAccessFile类就是最佳的选择。但该类仅限于操作文件啊,不能访问其他的IO设备,如网络,内存映像等。
RandomAccessFile不属于InputStream和OutputStream类。
创建RandomAccessFile类对象的构造方法:
RandomAccessFile(File file, String mode);
RandomAccessFile(String name, String mode);
mode值:
r: 以只读方式打开指定文件 。
rw :以读写方式打开指定文件 。
rws: 读写方式打开,并对内容或元数据都同步写入底层存储设备 。
rwd: 读写方式打开,对文件内容的更新同步更新至底层存储设备 。
类对象的一些常用方法:
1 ) void seek(long pos):将文件记录的指针定位到pos位置。
2 )long getFilePointer( ):返回文件记录指针的当前位置,指针默认位置为0。
3 )int skipBytes(int n):指针跳过的字节数。
4 )void setLength(long newLength) :设置此文件的长度。
5 )......
e.g.
package IO;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) throws IOException {
RandomAccessFile accessFile = new RandomAccessFile(new File("/Users/wq/Desktop/outInFile/test.txt"), "r");
// accessFile.seek(8);//指定记录指针的位置
accessFile.skipBytes(9);//跳过多少个字节
System.out.println(accessFile.getFilePointer());//当前位置
byte[] buff = new byte[1024];
int len = 0;
while ((len = accessFile.read(buff))!=-1){
System.out.println(new String(buff,0,len));
System.out.println(new String(buff,0,len).getBytes("UTF-8").length);//UTF-8编码长度
}
accessFile.close();
}
}
运行结果:
9
候放学了
12
文件中的内容是“这个时候放学了”,但是指针跳过了9个字节,而不同的编码格式占字节数是不同的,UTF-8编码下一个中文所占字节也是不确定的,可能是2个、3个、4个字节。由运行结果可以看出在UTF-8的编码格式下,一个汉字占了3个字节,所以最后打印出来的结果是跳了三个汉字。
Java的输入输出流建立在4个抽象类的基础上:InputStream,OutputStream,Reader和Writer,它们无法直接创建实例。一般来说,处理字符串或者字符时应使用字符流,处理字节或二进制对象时应使用字节流。
字节流
字节流为处理字节的输入输出提供了丰富的环境,一个字节流可以和其他任意类型的对象合并,包括二进制数据。
输入字节流(InputStream)
InputStream是字节输入模式的抽象类,该类的所有方法在出错时都会抛出IOException异常。InputStream的常用方法:
方法 | 描述 |
---|---|
int read() | 如果下一个字节可读,则返回一个整型,遇见文件尾时,则返回-1 |
int read(byte[] b) | 从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数,遇见文件尾时返回-1 |
int read(byte[] b,int off,int len) | 从输入流中读取len个字符的数据,并将其存储在数组b中,从off位置开始,返回实际读取的字符数,遇见文件尾时返回-1。 |
void close() | 关闭输入流,关闭之后如果再读取则会抛出IOException 异常 |
void reset() | 重新设置输入指针到先前设置的标志处 |
void mark(int numBytes) | 在输入流的当前位置放置一个标志,在该流读取numBytes 个byte前都有效 |
boolean markSupported() | 判断输入流是否支持mark()/reset()操作,如果支持则返回true |
long skip(long n) | 忽略n个字节,返回实际忽略的字节 |
输出字节流(OutputStream)
该类的所有方法返回一个void 值,并且在出错的情况下抛出一个IOException异常。OutputStream的常用方法:
方法 | 描述 |
---|---|
void write(int b) | 将指定的字节/字符输出到输出流中 |
void write(byte[]/char[] buf) | 将字节数组/字符数组中的数据输出到指定输出流中 |
void write(byte[]/char[] buf, int off,int len ) | 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中 |
void close() | 关闭输出流 |
void flush() | 定制输出状态以使每个缓冲器都被清除,也就是刷新输出缓冲区 |
一般操作文件流时的步骤
- 使用File类找到一个文件。
- 通过File类的对象去实例化字节流或字符流的子类。
- 进行字节流或字符流的读写操作。
- 关闭文件流。
下面就以文件输入输出流为例,书写例子
package IO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStreamTest {
public static void main(String[] args) throws IOException {
//文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("/Users/wq/Desktop/outInFile/test.txt");//空白文件
//文件输入流
FileInputStream fileInputStream = new FileInputStream("/Users/wq/Desktop/outInFile/test.txt");
byte[] outB = "今天是个好日子".getBytes();
fileOutputStream.write(outB);
byte[] b = new byte[1024];//接收从输入流中读取出来的数据
if(fileInputStream.read(b) > 0) {
String result = new String(b);
System.out.println(result);
}
fileInputStream.close();//关闭文件输入流
fileOutputStream.close();//关闭文件输出流
}
}
在执行写入文件的时候,将“今天是个好日子”内容写入到test.txt文件中,在执行读取文件的时候将内容读取了出来。
- FileInputStream在读取文件的时候,传入的文件路径不存在,那么在执行read方法时会报FileNotFoundException异常。
- FileOutputStream在写入文件的时候,传入的文件路径不存在, 那么在执行write方法时, 会默认创建一个该路径下的文件并且不会报错。
字符流
字符流提供了处理任何类型的输入输出操作功能,但是它们不能直接操作Unicode字符。其层次结构的顶层是Reader和Writer。
字符输入流(Reader)
Reader类中的常用方法:
- int read(),int read(char[] c),abstract int read(char[] b,int off,int len),abstract void close(),long skip(long numChars)和InputStream中的方法用法相似,这里就不一一描述了。
- Boolean ready():如果下一个输入请求不等待则返回true,否则返回false。
字符输出流(Writer)
该类的所有方法都返回void的值并且在出错的时候抛出IOException异常。
-abstract void close(),abstract void fush(), void write(int char),void write(char ch[]),abstract void write(char[] buf, int off,int len ), void write(String str),void write(String str, int off,int len )其用法和OutputStream中方法的用法相似。
e.g. FileReader/Filewrite为例
package IO;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReadAndWrite {
public static void main(String[] args) throws IOException {
FileWriter fileWriter = new FileWriter("/Users/wq/Desktop/outInFile/test.txt");
FileReader fileReader = new FileReader("/Users/wq/Desktop/outInFile/newtest.txt");
String write = "这个时候放学了";
fileWriter.write(write);//写入文件
char[] reader = new char[1024];
int i =fileReader.read(reader);
if(i >0) {
System.out.print(new String(reader,0,i));//读出文件
}
fileWriter.close();
fileReader.close();
}
}
几种流的使用
1)管道流
管道流主要用于两个线程间的通信。管道流分为管道字节流(PipedInputStream,PipedOutputStream)和管道字符流(PipedReader,PipedWriter)。
e.g.
package IO;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStreamDemo {
public static void main(String[] args) {
try {
Sender sender = new Sender();
Receiver receiver = new Receiver();
PipedOutputStream out = sender.getPipedOut();//写入
PipedInputStream in = receiver.getPipedin();//读出
out.connect(in);//将输出发送到输入
Thread senderThread = new Thread(sender);
Thread receiverThread = new Thread(receiver);
senderThread.start();//启动线程
receiverThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Sender implements Runnable{
private PipedOutputStream pipedOut = new PipedOutputStream();
public PipedOutputStream getPipedOut() {
return pipedOut;
}
@Override
public void run() {
String message = "那谁,收到消息的人,你好!";
try {
pipedOut.write(message.getBytes());//写入内容
pipedOut.close();//关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receiver implements Runnable{
private PipedInputStream pipedin = new PipedInputStream();
public PipedInputStream getPipedin() {
return pipedin;
}
@Override
public void run() {
byte[] b = new byte[1024];
try {
pipedin.read(b);//读出数据
String messageIn = new String(b);
System.out.println("这是收到的信息: " +messageIn);
pipedin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
这是收到的信息: 那谁,收到消息的人,你好!
2 )对象流(ObjectInputStream,ObjectInputStream)
使用对象流写入或读入对象时,要保证对象是序列化的。这是为了保证能把对象写入到文件,并能再把对象读回到程序中的缘故。一个类如果实现了Serializable接口,那么这个类创建的对象就是所谓序列化的对象。
e.g.
package IO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("/Users/wq/Desktop/outInFile/student.txt");
ObjectOutputStream objectOutputStream =new ObjectOutputStream(fileOutputStream);
Student student = new Student("琪琪", 23);
objectOutputStream.writeObject(student);
FileInputStream fileInputStream=new FileInputStream("/Users/wq/Desktop/outInFile/student.txt");
ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);
Student tempStudent=(Student)objectInputStream.readObject();
System.out.println("Student对象为:"+tempStudent);
//关闭流
objectInputStream.close();
objectOutputStream.close();
}
}
ObjectOutputStream对象输出流在写入文件student.txt的时候,因为对象是被序列化了的,所以看不到你想让它写进去的内容,只有通过ObjectInputStream把文件读出来显示。
注意事项
1.读取顺序和写入顺序一定要一致,不然会读取出错。
2.保证对象是序列化的,必须实现Serializable接口。
3 )转换流
InputStreamReader和OutputStreamWriter这两个类是字节流和字符流之间相互装转换的类,其中InputStreamReader用于讲一个字节流中的字节解码成字符,OutputStreamWriter用于将写入的字符解码成字节后写入一个字节流。
e.g.
package IO;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class BufferStream {
public static void main(String[] args) throws IOException {
InputStreamReader reader=new InputStreamReader(System.in);
BufferedReader bufferedReader=new BufferedReader(reader);
System.out.println("请输入 : ");
String str = bufferedReader.readLine();//获取键盘输入的数据
if(str != null) {
System.out.println("打印输入的内容 :" +str);
}
bufferedReader.close();//关闭流
}
}
为了达到最高效率,避免频繁的进行字符与字节之间的转换,最好使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader类。
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufferedWriter=new BufferedWriter(new OutputStreamWriter(System.out));
4 )打印流
主要包含字节打印流PrintStream,字符打印流PrintWriter。PrintStream类提供了一系列的print和println方法,可以实现将基本数据类型的格式转换成字符串输出。
PrintStream类中构造方法:
1 ) PrintStream(OutputStream out)
2 ) PrintStream(OutputStream out,Boolean autoflush);//autoflush遇到换行符时是否自动清空缓冲区。
3 ) PrintStream(OutputStream out,Boolean autoflush,String econding);//econding编码方式
package IO;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class PrintStream {
public static void main(String[] args) throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/wq/Desktop/outInFile/PrintStreamTest.txt");//将内容输出到制定文件中
java.io.PrintStream printStream = new java.io.PrintStream(out);
printStream.print("对面的女孩看过来");
printStream.close();
}
}
PrintWriter类中构造方法:
1 ) PrintWriter(File file);//使用指定文件创建不具有自动行刷新的新 PrintWriter
2 ) PrintWriter(OutputStream out);
3 ) PrintWriter(OutputStream out,boolean autoFlush);
4 ) PrintWriter(String fileName);//创建具有指定文件名称的 PrintWriter
5 ) PrintWriter(Writer out,boolean autoFlush);
6 ) ......
//autoflush遇到换行符时是否自
e.g.
package IO;
public class PrintWriter {
public static void main(String[] args) {
java.io.PrintWriter printWriter = new java.io.PrintWriter(System.out);
printWriter.println("这是在屏幕上面输出的内容");
printWriter.close();
}
}
5 )合并流(SequenceInputStream)
将两个文件合并在一起,主要操作的是内容。
合并流的操作步骤:
1 )创建输入流
2 )建立一个FileOutputStream实例,用于写入合并的文件的内容。
3 )通过SequenceInputStream类,把文件的内容合并起来,放到这个类实例流中。但如果我们有超过两个输入流需要加入到合并之后,就不能直接传递输入流的引用到合并流,我们需要将输入流封装到一个枚举类型的对象中,将该对象的引用传递给合并流的构造函数。
4 )把SequenceInputStream实例的内容读取出来。
package IO;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
public class SequenceInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream1 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/test.txt"));//要合并的两个文件
FileInputStream fileInputStream2 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/newtest.txt"));
SequenceInputStream sequenceInputStream = new SequenceInputStream(fileInputStream2, fileInputStream1);//执行合并操作
FileOutputStream mergefile = new FileOutputStream(new File("/Users/wq/Desktop/outInFile/mergefile.txt"));//合并之后的文件
int len;
while ((len = sequenceInputStream.read()) != -1) {
mergefile.write(len);
}
FileInputStream fileInputStream=new FileInputStream(new File("/Users/wq/Desktop/outInFile/mergefile.txt"));
byte[] b = new byte[1024];
if(fileInputStream.read(b) > 0) {
String result = new String(b);
System.out.println(result);
}
sequenceInputStream.close();//关闭合并流
fileInputStream2.close();//关闭输入流
fileInputStream1.close();
mergefile.close();//关闭输出流
fileInputStream.close();
}
}
最后输出的结果是:把test.txt,newtest.txt,中的内容合并到了mergefile.txt中
Qiqi这个时候放学了
但是如果要合并的文件数超过了两个应该怎样进行合并呢?
SequenceInputStream提供了合并多个文件的构造方法:
SequenceInputStream(Enumeration<? extends InputStream> e)
Vector<InputStream> vector = new Vector<>();
vector.add(inputStream1);
vector.add(inputStream2);
vector.add(inputStream3);
//获取迭代器
Enumeration<InputStream> elements = vector.elements();
//构建合并源,把三个文件读到一起,
SequenceInputStream sis = new SequenceInputStream(elements);
package IO;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class MoreSequenceInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream1 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/test.txt"));//要合并的文件
FileInputStream fileInputStream2 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/newtest.txt"));
FileInputStream fileInputStream3 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/test1.txt"));
FileInputStream fileInputStream4 = new FileInputStream(new File("/Users/wq/Desktop/outInFile/test2.txt"));
Vector<FileInputStream> fileInputStreams = new Vector<FileInputStream>();
fileInputStreams.add(fileInputStream3);
fileInputStreams.add(fileInputStream4);
fileInputStreams.add(fileInputStream2);
fileInputStreams.add(fileInputStream1);
Enumeration<FileInputStream> enumeration = fileInputStreams.elements();//SequenceInputStream(Enumeration<? extends InputStream> e)用来执行多个文件合并操作
SequenceInputStream sequenceInputStream = new SequenceInputStream(enumeration);
FileOutputStream mergefile = new FileOutputStream(new File("/Users/wq/Desktop/outInFile/mergefile.txt"));//合并之后的文件
int len;
while ((len = sequenceInputStream.read()) != -1) {
mergefile.write(len);
}
FileInputStream fileInputStream=new FileInputStream(new File("/Users/wq/Desktop/outInFile/mergefile.txt"));
byte[] b = new byte[1024];
if(fileInputStream.read(b) > 0) {
String result = new String(b);
System.out.println(result);
}
sequenceInputStream.close();//关闭合并流
fileInputStream2.close();//关闭输入流
fileInputStream1.close();
mergefile.close();//关闭输出流
fileInputStream.close();
}
}
运行结果
SequenceInputStream —> 两个文件合并在一起,主要操作的是内容 —> 例如:Qiqi这个时候放学了
代码以及概念描述有问题的地方请及时指正