一、io流结构分析
要学习io流,我们先来认识几个io流操作有关的类或接口。
以及io流的结构图:
1. 流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
2. 分类:
按照流的单位分的话,可以分为字节流和字符流;
按照流的方向分的话,可以分为输入流和输出流。
-
字节流:多用于读取或书写二进制数据,这些类的基类为InputStream或OutputStream。可以处理所有以bit为单位储存的文件,也就是说可以处理所有的文件,但是在处理字符上的速度不如字符流。
-
字符流:操作的是为了支持Unicode编码,用于字符国际化,一个字符占用两个字节,这些类的基类为Reader或Writer。该流只能处理字符,但处理字符速度很快
流向:都是相对内存来说,内存输入为读,输出为写,I读O写。即从文件中读取数据到内存(程序)里为流入(输入流),从内存(程序)中将数据写入文件中为流出(输出流),
3. 另外:
整个io流所用类就在java的io包之中,整个io包大量应用了 装饰模式(在这不再介绍)。另外读取数据io流分为 节点流 和 处理流 ;
- 节点流:文件(File),管道(Piped)和数组(Array)(他们每个类都分别包括输入输出和字节字符四种流);
- 处理流:其余的都是处理类,他们都是属于节点流的装饰类,下面我整理了一个关于处理流的表格。
二、io流分项解析
1、File类
我们先来学习一下File这个类,File是文件和目录路径名称的抽象表示形式,File只关注文件本身的信息,例如:文件名、路径、可读、可写等,不会关注文件的内容,要关注文件的内容的话,那是IO流的技术。
File中的方法,几个常用的是:mkdir(),创建单个目录;mkdirs(),创建多个目录;getPath(),获取文件的路径;length(),获取文件的长度;getName()获取文件名字;getParentFile(),获取文件的父路径名字,即获取这个文件的上一层目录;exists(),判断文件是否存在;createNewFile(),创建文件,是创建文件,不是目录;list(),返回指定的目录里面包含的文件和目录,返回一个字符串数组;listFiles(),返回一个抽象路径名数组,也就是包含文件目录和文件目录的抽象路径,通过getName()和getAbsolutePath()来获取名字和路径;delete(),删除此文件或目录。【可查看API】
例题:输出指定目录下的所有文件信息(这里输出的只是在当前目录,不会输出子目录下的),要求值包含后缀名为.txt的文件,这个有多个方法,先说一个,使用String类里面的endsWith()方法
public void t(){
File file = new File("H:\\javaio");
String[] list = file.list();
for (String s : list) {
if(s.endsWith(".txt")){
System.out.println(s);
}
}
}
二(1)、字节流
2、FileInputStream和FileOutputStream
FileInputStream继承于InputStream,FileOutputStream继承于OutputStream,是用来对二进制文件进行操作的。
值得注意的地方是,使用完了流,记得要关闭,在文件末尾追加内容,要从基础流来考虑,当输出流文件不存在的时候,会自动创建文件,当输入流文件不存在时,会报错。所以,使用输入流时,要确定文件是否存在。
这两个常用的方法是:
read(),从此输入流中读取一个数据字节;
read(byte[] b),从此输入流中将最多b.length个字节的数据读入一个byte数中;
read(byte[] b,int off,int len),从此输入流中将最多len个字节的数据读入一个byte数组中;
write(byte[] b),将b.length个字节从指定byte数组写入此文件输出流中;
write (byte [] b,int off,int len),将指定byte数组中从偏移量off开始的len个字节写入此文件输出流;
write(int b),将指定字节写入此文件输出流;
close(),关闭流。
关于其的一些构造方法,看看API就懂了
案例:
拷贝文件,一个一个字节的拷贝
public void t1() throws Exception{
FileInputStream fis = new FileInputStream("H:\\javaio\\copy01.txt");
FileOutputStream fos = new FileOutputStream("H:\\javaio\\copy02.txt");
int n;
//这里面是n等于读取到的字节,当读取到末尾时,返回的是-1,所以这里用!=-1来表示没有读到文件末尾
while((n = fis.read()) != -1){
fos.write(n);
}
fos.close();
fis.close();
}
3、BuffereInputStream和BuffereOutputStream 缓冲流
BuffereInputStream(带有缓冲区的字节输入流)继承于FilterInputStream,而FilterInputStream继承于InputStream,BuffereOutputStream(带有缓冲区的字节输出流)继承于FilterOutputStream,而FilterOutputStream继承于OutputStream,关于FilterInputStream和FilterOutputStream,其实它们只是个“装饰器模式”的封装,也就是说它并没有给出具体的功能实现,它具体的功能实现都是通过它的子类来实现的。
可以将数据流从数据源中处理完毕都存入内存缓冲区,然后统一一次性与底层IO进行操作,可以有效降低程序直接操作IO的频率,提高io执行速度。
这两个其实主要就是一个缓冲的作用,我们知道,如果直接让文件或程序跟内存进行交互,效率是十分低下的,而通过缓冲流进行交互,能够大大提高效率,缓冲流的主要作用就是提高了效率。
对于文件的存在与否的反应,和其它一样,不存在时,输入流会报错,输出流会自动创建。
常用的方法是:read(),read(byte [] b,int off,int len),write(int b),write(byte[] b,int off,int len),close();这些具体参照InputStream和OutputStream中这些方法的使用。
案例:
利用缓冲流进行拷贝,多个字节多个字节拷贝
public void t1() throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("H:\\javaio\\测试.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("H:javaio\\测试copy1.avi"));
byte[] b = new byte[2*1024];
int len;
while((len = bis.read(b)) != -1){
bos.write(b, 0, len);
}
bos.close();
bis.close();
}
二(2)、字符流
4、InputStreamReader和OutputStreamWriter(转换流)
InputStreamReader(字符输入转换流)继承于Reader,OutputStreamWriter(字符输出转换流)继承于Writer。它们是字节流和字符流之间的“桥梁”。我们只需要记住,当要操作字节和字符串时,用着两个当纽带来操作。从字节流到字符流的桥,读取字节流转为字符流。可设定编码格式!
InputStreamReader和OutputStreamWriter作用于字节,而FileReader和FileWriter作用于字符,显然两者直接作用有差别的。
案例:
进行文件的拷贝
public void t1() throws Exception{
InputStreamReader isr = new InputStreamReader(new FileInputStream("H:\\javaio\\copy01.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("H:\\javaio\\copy02.txt"));
char[] cbuf = new char[1024];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf, 0, len);
}
osw.close();
isr.close();
}
5、FileReader和FileWriter(字符流)
FileReader继承于InputStreamReader,FileWriter继承于OutputStreamWriter。用于对字符流进行操作。直接对文件进行字符操作。
案例:
进行文件的拷贝
public void t1() throws Exception{
FileReader fr = new FileReader("H:\\javaio\\copy01.txt");
FileWriter fw = new FileWriter("H:\\javaio\\copy02.txt");
char[] cbuf = new char[1024];
int len;
while((len = fr.read(cbuf)) != -1){
fw.write(cbuf, 0, len);
}
fw.close();
fr.close();
}
②将内容追加到文件
public void t2() throws Exception{
FileWriter fw = new FileWriter("H:\\javaio\\test.txt",true);
fw.write("我是测试用例!");
fw.close();
}
6、BufferedReader和BufferedWriter(字符缓冲流)
BuffereReader继承于Reader,BufferedWriter继承于Writer,是字符缓冲流。
这两者常用的方法是:read(),读取单个字符;read(char[] cbuf,int off,int
len),将字符读入数组的某一部分;readLine(),读取一个文本行;write(char[] cbuf,int off,int
len),写入字符数组的某一部分;write(int c)写入单个字符;write(String s,int off,int
len),写入字符串的某一部分;close(),关闭该流;
an案例:
进行文件的拷贝
public void t1() throws Exception{
BufferedReader br = new BufferedReader(new FileReader("H:\\javaio\\copy01.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("H:\\javaio\\copy02.txt"));
// char[] cbuf = new char[1024];
// int len;
// while((len=br.read(cbuf)) != -1){
// bw.write(cbuf, 0, len);
// }
//BufferedReader提供了readLine方法,可以不再使用字节读取方式
String readline;
while((readline = br.readLine()) != null){
bw.write(readline);
bw.newLine();
}
bw.close();
br.close();
}
二(3)、其他流
7、ObjectInputStream和ObjectOutputStream(对象流)
ObjectInputStream(对象输入流)继承于InputStream,ObjectOutputStream(对象输出流)继承于OutputStream。对象流是将对象的基本数据和图形实现持久存储。ObjectOutputStream实际是在对流进行序列化操作,ObjectInputStream实际是在对流进行反序列化操作,要实现序列化,必须实现Serializable接口,否则是无法进行序列化和反序列化的,如果对象中的属性加了transient和static关键字的话,则该属性不会被序列化。
补充:序列化与反序列化
数据传输过程中,都会默认采用二进制文件的方式,因为计算机的底层识别方式就是二进制,不依赖任何运行环境或是程序设计语言,所以这是实现数据传输跨平台跨网络的基础。*序列化可以直接将java对象转化为一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象(反序列化),*这一过程甚至可以通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。
注:实现序列化的对象必须实现Serializable接口
实例:
先创建一个对象
import java.io.Serializable;
/*
* 为测试对象流创建一个对象
*/
public class Student implements Serializable{
/**
*
*/
private static final long serialVersionUID = 6271405124073931152L;
private String name;
private int age;
private transient String info1;
private static String info2;
public Student() {
}
public Student(String name, int age, String info1, String info2) {
super();
this.name = name;
this.age = age;
this.info1 = info1;
this.info2 = info2;
}
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 String getInfo1() {
return info1;
}
public void setInfo1(String info1) {
this.info1 = info1;
}
public static String getInfo2() {
return info2;
}
public static void setInfo2(String info2) {
Student.info2 = info2;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", info1=" + info1 + "]" + ", info2=" + info2 + "]";
}
}
写入一个Student对象
public void t3() throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("H:\\javaio\\objecttest.txt"));
oos.writeObject(new Student("测试",18,"信息1","信息2"));
oos.close();
}
读取对象
public void t4() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("H:\\javaio\\objecttest.txt"));
Student stu = (Student) ois.readObject();
System.out.println(stu);
ois.close();
}
8、PrintStream和PrintWriter(打印流)
PrintStream(可以将字节流封装成打印流)继承于FilterOutputStream,FilterOutputStream是继承于OutputStream的;PrintWriter(可以将字节流、字符流封装成打印流)继承于Writer的。
首先请问java的标准输入流是什么?是InputStream,正确。那么java的标准输出流是什么?是OutputSteam?No!而是PrintStream。
因为标准输入输出流是System类的定义,System中有三个字段,in是InputStream类型,对应的是标准输入流,err和out都是PrintStream对象,out对应的是标准输出流。我们常用的System.out.println(data)方法的返回值就是PrintStream对象,此流默认输出在控制台,也可以重定向输出位置;
其中可以使用PrintStream进行重定向的操作:
系统标准输入流的方向:控制台 -> 程序,重新定义系统标准输入流使用的方向System.setIn(),重新定义后的方向为:文件->程序
System.setIn(new FileInputStream("H:\\javaio\\testofprint.txt"));
//现在获取的next不是控制台输入的 而是文件中的
Scanner input = new Scanner(System.in);
String next = input.next();
System.out.println(next);
input.close();
系统的标准输出流的方向:程序->控制台,重定向系统标准输出流使用的方法System.setOut(),重新定义后的方向为:程序->文件
System.setOut(new PrintStream(new FileOutputStream(file)));
System.out.println("这些内容只能在file对象的文件中才能看到哦!");//并非在控制台打印而是打印到文件中!
PrintWriter就是PrintStream的字符操作的版本。PrintStream都是针对字节流进行操作的,如果要操作字符流,可以使用PrintWriter。用法相似!
①使用PrintStream进行打印到文件的操作
public void t1() throws Exception{
PrintStream ps = new PrintStream(new FileOutputStream("H:\\javaio\\testofprint.txt"));
ps.print("我是打印流测试(PrintStream)");
ps.close();
}
②使用PrintWriter进行打印到文件的操作
public void t2() throws Exception{
PrintWriter pw = new PrintWriter(new FileWriter("H:\\javaio\\testofprint.txt"));
pw.write("我是打印流测试(PrintWriter)");
pw.close();
}
9、RandomAccessFile(随机访问文件)
RandomAccessFile 是任意位置进入文件的意思,适用于由大小已知的记录组成的文件,它有一个seek方法定义了文件的位置,所以要注意在对文件进行RandomAccessFile操作时,要记住文件的内容的位置和大小,否则会发生内容复写更改的后果。
RandomAccessFile对文件进行读和写操作,具体是读还是写,要根据设置的模式(构造时的参数 “r” “rw”)来决定。底层实际可以理解为一个byte数组,是带有指针的,通过指针的指向对文件进行读操作和写操作。
它的常用方法是:getFilePointer(),返回此文件中的当前偏移量,即返回指针位置;
length(),返回此文件的长度;
read(),read方法包含一系列参数和以read开头的方法,具体请看API,无非就是某些数据类型;
readLine(),从此文件读取文本的下一行;
seek(long pos),设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作,即设置指针位置,下次读写从该位置开始;
skipBytes(int n),跳过输入的n个字节,丢弃跳过的字节;
write(),write包含一系列参数和以write开头的方法,具体看API,无非就是某些数据类型。
案例:
直接从某一位置开始读取数据,即跳过某一位置之前,使用seek
public void t3() throws Exception{
RandomAccessFile raf = new RandomAccessFile("H:\\javaio\\randomtest.txt", "r");
//使用seek设置指针位置为3
raf.seek(3);
byte[] b = new byte[1024];
int len;
while((len = raf.read(b)) != -1){
System.out.println(new String(b, 0, len));
}
raf.close();
}
10、ByteArrayInoutStream和ByteArrayOutputStream(内存流)
ByteArrayInputStream(内存输入流)继承于InputStream,ByteArrayOutputStream(内存输出流)继承于OutputStream。内存流是关不掉的,一般用来存放一些临时性的数据,理论值是内存大小。
字节数组处理,把字节数组当作输入输出流
①从内存流读出信息,创建内存流时,就把数据存入到内存中
public void t1() throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream("我是内存流测试".getBytes());//参数为字符数组
byte[] b = new byte[1024];
int len;
while((len = bais.read(b)) != -1){
System.out.println(new String(b, 0, len));
}
}
11、DataOutputStream 和 DataInputStream
这一对类可以直接写入java基本类型数据(没有String),但写入以后是一个二进制文件的形式,不可以直接查看。
DataOutputStream / DataInputStream是常用的过滤流类,如果对象的序列化是整个对象转换为一个字节序列的话,DataOutputStream / DataInputStream就是将字段序列化,转为二进制数据。
三、NIO
io流大体分为BIO,AIO,NIO,常用的基本就是BIO,也就是上文所介绍的那些,
传统io是靠字节或字符来传输,nio是靠块传输,也就是一个一个的buffer速度相对于较快。传统io是阻塞型io,nio是非阻塞型io。多适用于进行流畅的网络读写操作。
具体用法不再介绍