IO流
1. 流是什么?
在计算机中,流是个抽象的概念,是对输入输出设备的抽象。在Java程序中,对于数据的输入/输
出操作,都是以"流"的方式进行数据以二进制的形式在程序与设备之间流动传输,就像水在管道里流动一样,所以就把这种数据传输的
方式称之为输入流、输出流。这里描述的设备,可以是文件、网络、内存等流具有方向性,可以分为输入和输出。
2. 流的分类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 | 字节输出流 |
字符流z | 字符输入流 | 字符输出流 |
注意,字节指的是byte,字符指的的是char
3. 流的结构
几乎所有的流,都是派生自四个抽象的父类型:
- InputStream ,代表字节输入流类型
- OutputStream ,代表字节输出流类型
- Reader ,代表字符输入流类型
- Writer ,代表字符输出流类型
4.一叶知秋:节点流
4.1字节流
三个基本方法
//每次读一个字节,返回值是本次读取的字节值
public abstract int read() throws IOException;
//每次读多个字节,并存放到指定的字节数组中,返回值是本次一共读取了多个字节(字节数)
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//每次读多个字节,并存放到指定的字节数组中,返回值是本次一共读取了多个字节(字节数)
//同时可以指定从数组的什么位置开始存放,以及在数组中最多存放多个字节
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
控制台的读取与输出
在System中已经初始化所以不需要new
InputStream in = System.in;
字节数组的读取与输出
InputStream in = new ByteArrayInputStream
将数据写入到了out对象中的属性里面,该属性是一个字节数组
out.write(buf,0,len);管道的输入与输出
用于操作多线程的
InputStream in =new PipedInputStream
public class Test { public static void main(String[] args) { PipedReader in = null; PipedWriter out = null; in = new PipedReader(); out = new PipedWriter(); try { //管道对接 in.connect(out); Thread t1 = new WriteThread(out); Thread t2 = new ReadThread(in); t1.start(); t2.start(); t1.join(); t2.join(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(); System.out.println("程序运行结束!"); } } class WriteThread extends Thread{ private Writer out; public WriteThread(Writer out){ this.out = out; } @Override public void run() { char[] arr = "hello world briup".toCharArray(); try { for(int i=0;i<arr.length;i++){ out.write(arr[i]); out.flush(); Thread.sleep(1000); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if(out!=null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } class ReadThread extends Thread{ private PipedReader in; public ReadThread(PipedReader in){ this.in = in; } @Override public void run() { int data = -1; try { while((data=in.read())!=-1){ //System.out.print(data); System.out.write(data); System.out.flush(); } System.out.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if(in!=null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
文件的输入与输出
InputStream in =new FileInputStream
4.2 文件的操作
File file = new File(pathname);
public String getAbsolutePath() //返回绝对路径
public String getPath() //返回创建时传入的路径可能是相对路径
public boolean exists() //判断此文件或目录是否真的存在
public boolean isDirectory() //判断File表示的是否是一个目录
public boolean isFile() //判断file表示的是否是一个文件
public String[] list() //返回目录中所有的子文件或子目录,返回值是String类型数组
public File[] listFiles() //返回目录中所有的子文件或子目录,返回值是File类型数组
4.3 类比字节流还有字符流
Writer 和Reader只适用于读写字符数据的场景
5. 辅助流:辅助操作流
5.1数据流
java.io.DataOutputStream ,可以将指定类型的数据 转换为 字节,并写出去
//3.使用流 out.writeLong(1000L); out.flush(); //将写入的数据转为字节数组并返回 byte[] toByteArray = byteArrayOutputStream.toByteArray(); System.out.println(Arrays.toString(toByteArray)); //运行结果 [0, 0, 0, 0, 0, 0, 3, -24]
5.2 缓冲流
提供一个缓冲池即一个数组,做到一次读一次写,节省时间
字符缓冲返回的是String
字节返回的是int
java.io.BufferedInputStream ,负责给字节输入流提供缓冲功能
java.io.BufferedReader ,负责给字符输入流提供缓冲功能
java.io.BufferedWriter ,负责给字符输出流提供缓冲功能 创建新行
java.io.PrintWriter ,一次写一行,写完之后自动加换行,一般和BufferedReader 搭配使用
5.3 转换流
将字节流转换为字符流,注意编码方式
//将字节流转换为字符流
outputStreamWriter = new OutputStreamWriter(newFileOutputStream(file),"UTF-8");
//“包裹”转换流,增强这个字符流的功能,可以一次写出一行字符串,并自动换行
//注意,转换流同时也是一个字符流
out = new PrintWriter(outputStreamWriter);
5.4对象流
将数组 ,创建的对象序列化为字节存储,
前提:该对象实现了java.io.Serializable 接口 ,集合已经实现类
- 一个字节序列来表示一个对象,该字节序列包含了对象的类型、对象中的数据等
- java.io.ObjectOutputStream ,将Java对象转换为字节序列,并输出到内存、文件、网络等地方
注意,具体输出到什么地方,要看 ObjectOutputStream “包裹”的是哪一个节点流
transient :可以修饰类中的属性,它是让对象在进行序列化的时候,忽略掉指定的属性
private transient int age;
序列化版本号:当你没有定义时,每修改一次对象就会更改一次序列化号,那么你存储在文件中的对象反序列化后就会识别序列号报错。所以定义一个固定的序列化号
5.5.随机流
java.io.RandomAccessFile
它并没有继承之前介绍到的那四个抽象父类型
创建该类的对象时,需要指定要操作的文件和操作的模式:
- “r” 模式,以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
- “rw” 模式,以读写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
- “rws” 模式,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新都同步写入到底层设备。
- “rwd” 默认,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入到底层设备
使用
public static void main(String[] args) {
//randomAccessFile负责读取文件内容以及向文件中写内容
RandomAccessFile randomAccessFile = null;
try {
File file = new File("src/main/java/com/briup/demo/a.txt");
//文件中要替换数据的位置
int replacePos = 6;
//文件中要插入的内容
String replaceContent = "briup";
//设置randomAccessFile为读写模式
randomAccessFile = new RandomAccessFile(file,"rw");
byte[] buf = new byte[1024];
int len = -1;
//randomAccessFile定位到要替换数据的位置,准备去写要替换的内容
//注意,原内容和新内容的字节数恰好相等
randomAccessFile.seek(replacePos);
//在指定位置,写入需要替换的内容,覆盖原来此位置上的内容
randomAccessFile.write(replaceContent.getBytes());
//randomAccessFile定位到文件的开始位置,准备去读文件中的所有内容,并输出到控制台
randomAccessFile.seek(0);
while((len=randomAccessFile.read(buf))!=-1){
System.out.write(buf,0,len);
}
System.out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(randomAccessFile!=null){
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//运行结果:
hello briup123