IO流
作用: 从磁盘或者网络中读取/写入数据
流的分类
-
根据数据的流向分为:输入流和输出流
输入流: 把数据从其他设备上读取到程序中的流
输出流: 把数据从程序中写出到其他设备上的流
-
根据数据的类型分为: 字节流和字符流
字节流: 以字节为单位(byte),读写数据的流
字符流: 以字符为单位(插入),读写数据的流
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 | 字节输出流 |
字符流 | 字符输入流 | 字符输出流 |
流的结构
在java中,和IO流相关的类,主要是在java.io包下的定义的
- InputStream,代表字节输入流
- OutputStream,代表字节输出流
- Reader,代表字符输入流的类型
- Writer,代表字符输出流的类型
流的继承结构图
使用流的步骤
- 分析使用输入流还是输出流
- 声明流
- 创建流
- 使用流
- 关闭流
文件
java.io.File 类,是java中对文件和目录的抽象表示,主要用于文件和目录的创建\查找和删除等操作.
File中常见的构造器有
public class File{
//通过指定文件路径,创建File对象。
public File(String pathname){...}
//通过父路径名和子路径名,创建File实例。
public File(String parent, String child){...}
//通过父路径名和子路径名,创建File实例。
public File(File parent, String child){...}
}
File中的常用方法
public String getAbsolutePath(); //返回file的绝对路径
public String getPath(); //返回创建file对象时传入的路径参数(有可能是相对路径)
public String getName(); // 返回file的名字
public long length(); // file 如果表示文件,则返回文件内容的长度
相对路径:
程序中,也经常使用相对路径来表示一个文件或者目录
public boolean exists() ,判断此文件或目录是否真的存在
public boolean isDirectory() ,判断File表示的是否是一个目录
public boolean isFile() ,判断file表示的是否是一个文件
public boolean createNewFile() ,创建一个文新文件
public boolean delete() 删除文件或目录
public boolean mkdir() 创建一个目录
public boolean mkdirs() 创建多级目录
public String[] list() ,返回目录中所有的子文件或子目录,返回值是String类型数组
public File[] listFiles() ,返回目录中所有的子文件或子目录,返回值是File类型数组
字节流
java.io.InputStream是所有字节输入流的抽象父类型
InputStream中最核心的三个read方法
//每次读一个字节,返回值是本次读取的字节值
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;
java.io.OutputStream是所有字节输出流的抽象父类型
OutputStream中最核心的三个write方法
//写出去一个字节值
public abstract void write(int b) throws IOException;
//把一个字节数组中的值全部写出去
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//写出字节数组,指定开始位置,以及写出的字节数量
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {write(b[off + i]);
}
}
字节数组
使用字节流,从字节数组中读取数据,以及向自己数组中写数据
java.io.ByteArrayInputStream 负责从字节数组中读取数据
java.io.ByteArrayOutputStream 福贼把数据写入到字节数组中
public class Test{
public static void main(String[] args){
// 1.声明流
ByteArrayInputStream in = null;
ByteArrayOutputStream out = null;
// 2.创建流
byte[] arr = "hello".getBytes();
in = new ByteArrayInputStream(arr);
out = new ByteArrayOutputStram();
int len = -1;
byte[] buff = new byte[1024];
try{
len = in.read(buff);
out.write(buff,0,len);
out.flush();
byte[] toByteArray = out.toByteArray();
}catch(Exception e){
e.printStackTrace();
}finally {
in.close();
out.close();
}
}
}
管道流
使用字节流,可以从管道中读取数据,以及向管道中写数据.
java.io.PipedInputStream 负责从管道中读取数据
java.io.PipedOutputStream 负责将数据写入到管道中
public class Test {
public static void main(String[] args) {
PipedInputStream in = null;
PipedOutputStream out = null;
in = new PipedInputStream();
out = new PipedOutputStream();
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 OutputStream out;
public WriteThread(OutputStream out){
this.out = out;}
@Override
public void run() {
byte[] arr = "hello world briup".getBytes();
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 InputStream in;
public ReadThread(InputStream 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();
}}
}
}
}
注意一定先把管道进行对接,然后再操作
文件字节流(FileInputStream/FileOutputStream)
使用字节流,可以从文件中读取数据,以及向文件中写数据
java.io.FileInputStream, 负责从文件中读取数据
java.io.FileOutputStream, 负责把数据写入到文件中
public class Test {
public static void main(String[] args) {
//1.声明流
InputStream in = null;
OutputStream out = null;
try {
//2.创建流
File file1 = new File("src/main/java/com/briup/demo/a.txt");
File file2 = new File("src/main/java/com/briup/demo/b.txt");
in = new FileInputStream(file1);
out = new FileOutputStream(file2);
//3.使用流
int len = -1;
byte[] buf = new byte[1024];
while((len=in.read(buf))!=-1){
out.write(buf,0,len);
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字符流
java.io.Reader 是所有字符输入流的抽象父类型
Reader中最核心的三个read方法
//每次读取一个字符,返回这个字符的编码值
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
//每次读取多个字符,并存放到指定字符数组中,返回值是本次读取到的字符个数
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
//每次读取多个字符,并存放到指定字符数组中,返回值是本次读取到的字符个数
//同时可以指定从数组的什么位置开始存放,以及在数组中最多存放多个字符
abstract public int read(char cbuf[], int off, int len) throws IOException;
java.io.Writer 是所有字符输出流的抽象父类型
writer中最核心的三个read方法
//写出去一个字符,注意字符可以使用int值来表示
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
//写出去数组中的多个字符
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
//写出去数组中的多个字符
//可以指定从数组的什么位置开始写,以及多少个字符
abstract public void write(char cbuf[], int off, int len) throws IOExceptio//写出去一个字符串
public void write(String str) throws IOException {
write(str, 0, str.length());
}
//写出去一个字符串
//可以指定从字符串的什么位置开始写,以及多少个字符
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else {
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
字符数组
使用字符流,从字符数组中读取数据,以及向字符数组中写数据
java.io.CharArrayReader 负责从字符数组中读取数据
java.io.CharArrayWriter 负责把数据写入到字符数组中
public class Test {
public static void main(String[] args) {
//1.声明流
Reader in = null;
Writer out = null;
//2.创建流
char[] arr = "hello 中国".toCharArray();
in = new CharArrayReader(arr);
out = new CharArrayWriter();
//3.使用流
int len = -1;
char[] buf = new char[1024];
try {
//返回本次一共读取了多少个字符
len = in.read(buf);
//将数据写入到了out对象中的属性里面,该属性是一个字符数组
out.write(buf,0,len);
out.flush();
//CharArrayWriter中的toCharArray方法,可以将写入到out对象中的数据返回
char[] toCharArray = ((CharArrayWriter) out).toCharArray();
System.out.println(Arrays.toString(toCharArray));
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}}
}
}
}
管道流
java.io.PipedReader 负责从管道中读取数据
java.io.PipedWriter 负责将数据写入到管道中
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();
}
}
}
}
}
字符文件流
使用字符流,可以从文件中读取数据,以及向文件中写数据
java.io.FileReader,负责从文件中读取数据
java.io.FileWriter, 负责把数据写入到文件中
public class Test {
public static void main(String[] args) {
//1.声明流
Reader in = null;
Writer out = null;
try {
//2.创建流File file1 = new File("src/main/java/com/briup/demo/a.txt");
File file2 = new File("src/main/java/com/briup/demo/b.txt");
in = new FileReader(file1);
out = new FileWriter(file2);
//3.使用流
int len = -1;
char[] buf = new char[1024];
while((len=in.read(buf))!=-1){
out.write(buf,0,len);
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.关闭流
if(in!=null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out!=null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
节点流
以上介绍并且使用的字节流和字符流,都属于节点流
它们的特定是可以直接读取某一个地方的数据,或者直接把数据写入到某一个地方
功能流
以下介绍的亏都不是节点流,不能直接读写数据但是有一些特殊的功能
数据流
java.io.DataOutputStream,可以将指定类型的数据转换为字节,并写出去
public class DataOutputStream{
private byte writeBuffer[] = new byte[8];
public DataOutputStream(OutputStream out) {
super(out);
}
public final void writeBoolean(boolean v) throws IOException {}
public final void writeByte(int v) throws IOException {}public final void writeShort(int v) throws IOException {}
public final void writeInt(int v) throws IOException {}
public final void writeChar(int v) throws IOException {}
public final void writeLong(long v) throws IOException {
writeBuffer[0] = (byte)(v >>> 56);
writeBuffer[1] = (byte)(v >>> 48);
writeBuffer[2] = (byte)(v >>> 40);
writeBuffer[3] = (byte)(v >>> 32);
writeBuffer[4] = (byte)(v >>> 24);
writeBuffer[5] = (byte)(v >>> 16);
writeBuffer[6] = (byte)(v >>> 8);
writeBuffer[7] = (byte)(v >>> 0);
out.write(writeBuffer, 0, 8);
}
public final void writeFloat(float v) throws IOException {}
public final void writeDouble(double v) throws IOException {}
public final void writeUTF(String str) throws IOException {}
}
注意 :
1. DataOutputStream 必须要包裹一个字节流,增强这个字节流的功能,一次可以写出去一个具体类型的数据
2. 通过writeLong 方法可以看出,内部是通过移位操作,拿到每个字节的值,放到数组中
与DataOutputStream 功能类似,java.io.DataInputStream 可以将读取到的字节自动转化为指定类型的数据,但是一般都需要和DataOutputStream配合使用
缓冲流
缓冲流,可以在创建流对象时,设置一个默认大小的缓冲区数组,通过缓冲区进行读写,减少系统磁盘的IO次数,从而提高读写的效率
java.io.BufferedInputStream,负责给字节输入流提高缓冲功能
java.io.BufferedOutputStream,负责给字节输出流提供缓冲功能
java.io.BufferedReader 负责给字符输入流提供缓冲功能
java.io.BufferedWriter 负责给字符输出流提供缓冲功能
转换流
字节流是比较通用的IO流,因为所有的数据在计算机中都是以字节的形式存储的,所以在很多场景下,都提供了字节流的操作实现
转换流,可以把一个字节流转换为字符的同事,并指定转换的字符编码
java.io.OutputStreamWriter ,可以将字节输出流转换为字符输出流,并指定编码
java.io.InputStreamReader,可以将字节输入流转换为字符输入流,并指定编码
对象流
java 提供了一种对象序列化的机制,可以将对象和字节序列之间进行转换
-
序列化
程序中,可以用一个字节序列来表示一个对象,该字节序列包含了对象的类型、对象中的数据等如果这个字节序列写出到文件中,就相当于在文件中持久保存了这个对象的信息
-
反序列化
相反的过程,从文件中将这个字节序列读取回来,在内存中重新生成这个对象,对象的类型、对象 中的数据等,都和之前的那个对象保持一致。(注意,这时候的对象和之前的对象,内存地址可能 是不同的)
要求:
在java中,并非所有的对象都可以进行序列化和反序列化,而是只有实现了指定接口的对象才可以进行
java.io.Serializable 接口,该接口中没有定义抽象
java.io.ObjectOutputStream ,将Java对象转换为字节序列,并输出到内存、文件、网络等地方 注意,具体输出到什么地方,要看 ObjectOutputStream “包裹”的是哪一个节点流
java.io.ObjectInputStream ,从某一个地方读取出对象的字节序列,并生成对应的对象。 注意,具体是从输什么地方读取字节,要看 ObjectInputStream “包裹”的是哪一个节点流
随机访问流
java.io.RandomAccessFile 是JavaAPI中提供的对文件进行随机访问的流.
它并没有继承之前介绍到的那四个抽象父类型
之前使用的每一个流,要么是读数据的,要么是写数据的,而这个随机访问流,它的对象即可以用作文件内容的读,也可以同坐文件内容的写,同时它还可以任意定位到文件的某一个文件进行读写操作
- 对象即可读也可以写
- 随机定位文件中的任意字节位置进行读或写,并且可以前后反复定位
创建该类的对象时,需要指定要操作的文件和操作的模式:
- “r” 模式,以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出 IOException异常。
- “rw” 模式,以读写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
- “rws” 模式,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新 都同步写入到底层设备。
- “rwd” 默认,以读写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入 到底层设备。
public static void main(String[] args) {
//randomAccessFile负责读取文件内容以及向文件中写内容RandomAccessFile randomAccessFile = null;
//out用于临时保存一些内容
ByteArrayOutputStream out = null;
try {
File file = new File("src/main/java/com/briup/demo/a.txt");
//文件中要插入数据的位置
int insertPos = 6;
//文件中要插入的内容
String insertContent = "briup ";
//设置randomAccessFile为读写模式
randomAccessFile = new RandomAccessFile(file,"rw");
//out只能写数据
out = new ByteArrayOutputStream();
//读操作需要用的常规参数
byte[] buf = new byte[1024];
int len = -1;
//randomAccessFile定位要插入数据的位置,准备去读此位置及其以后的所有数据,并写入到数组存
randomAccessFile.seek(insertPos);
while((len=randomAccessFile.read(buf))!=-1){
out.write(buf,0,len);
}
System.out.flush();
//randomAccessFile再次定位到要插入数据的位置,准备去写要插入的内容,以及临时保存的内容
//因为默认写入时会将对应位置上的数据给覆盖掉
randomAccessFile.seek(insertPos);
//在指定位置,写入需要插入的内容
randomAccessFile.write(insertContent.getBytes());
//在插入内容后面,写入刚刚临时保存起来的内容
randomAccessFile.write(out.toByteArray());
//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();
}
}
}
}