IO流操作

IO流

IO就是输入输出,IO设备在计算机中起着举足轻重的作用,IO流也就是输入输出流,用来交互数据,程序和程序交互,程序也可以和网络等媒介交互。

一、IO流的分类

要分类,肯定得站得不同角度来看这个问题。

  • 根据流向划分,分为输入流和输出流
  • 根据数据的单位来分划,分为字节流和字符流
  • 根据功能划分,分为节点流和包装流
  • 还有很多

二、四大基本流

在java中IO流非常之多,但都继承于这四大基流,分别为,字节输入流(InputStream),字节输出流(OutputStream),字符输入流(Reader)和字符输出流(Writer).

它们都是抽象类,不能创建对象,只能创建它们的子类对象,阅读API发现,它们都有一个共同的方法close方法,用来关闭流,如果不关闭流的话程序会一直引用着文件。

三、模板操作

  • 创建源和目标
  • 创建流对象
  • 具体的IO操作
  • 关闭流

在下面的例子中穿插讲述

四、文件流

文件流操作是是文件。分为字符流和字节流,字节流操作是所有的二进制文件包括视频啊,图片啊什么的。字符流主要解决的还是中文的一些问题,因为中文多个字节(GBK是2个字节,UTF-8是3个字节),所有可能出现读一半或者写一半就不读了或者不写了,这样就出现乱码了。具体用字符流还是字节流得具体选择,字节流是可以操作字符流的,但是反过来就可能出现问题。输入就是从文件中读到程序中来,而输出就是从程序中输出到文件中。在这样章节记住一句话:读进来,写出去。下面操作一下各个流的常用方法,

  • 文件字节输入流(FileInputStream)

    1
    2
    3
    4
    > int read() 	从此输入流中读取一个数据字节。 
    > int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中
    > int read(byte[] b, int off, int len)从此输入流中将最多 len 个字节的数据读入一个 byte 数组中
    >

    读取文件中所有的数据,用到了一个缓冲数组,也就是每次读取5个字节,直到读完为止有个len返回当前读了几个字节,如果后面没有字节了,那么它就返回-1

    1
    2
    3
    4
    5
    6
    7
    //读取文件中所有的数据
    byte[] buffer = new byte[5];//定义一个缓冲数组长度为5,每次只能读取5个字节
    int len = 0;//当前读取的字节数,没有字节就返回 -1
    while((len = in.read(buffer))!= -1 ){
    String str = new String(buffer,0,len);
    System.out.println(str);
    }
  • 文件字节输出流(FileOutputStream)

    1
    2
    3
    4
    5
    6
    7
    > 1.void write(byte[] b) 
    > 将 b.length 个字节从指定 byte 数组写入此文件输出流中。
    > 2.void write(byte[] b, int off, int len)
    > 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
    > 3.void write(int b)
    > 将指定字节写入此文件输出流。
    >

    输出就是想文件中写数据,可以一次写一个字节,也可以写一个数组中的元素,这个比较简单。可以用一下四个步骤操作一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    public class FileOutputStreamDemo {
    public static void main(String[] args) throws Exception {
    //1.创建源
    File f = new File("C:/Users/15626/Desktop/test/123.txt");
    //2.创建输出流对象
    FileOutputStream out = new FileOutputStream(f);
    //3.写操作
    out.write(97);//只能一个一个的写

    //将buffer数组中的数据写在文件中
    byte[] buffer = "ABCDEF".getBytes();
    out.write(buffer);

    //从索引1开始的3个字节写在文件中
    out.write(buffer, 1, 3);

    //4.关闭资源
    out.close();
    }
    }
  • 文件字符输入流(FileReader)

    字符流的方法是从父类中继承过来的,也和上面字节流的方法大同小异

    1
    2
    int read()  读取单个字符。 
    int read(char[] cbuf, int offset, int length) 将字符读入数组中的某一部分。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //文件字符输入流
    public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
    //1.创建源
    File src = new File("C:/Users/15626/Desktop/新建文件夹 (2)/test.txt");
    //2.创建字符输入流对象
    FileReader in = new FileReader(src);
    //3.读操作
    char[] buffer = new char[10];
    int len = -1;
    while((len = in.read(buffer)) != -1){
    String str = new String(buffer,0,len);
    System.out.println(str);
    }
    //4.关闭流
    in.close();
    }
    }
  • 文件字符输出流(FileWriter)

    1
    2
    3
    void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。          
    void write(int c) 写入单个字符。
    void write(String str, int off, int len) 写入字符串的某一部分。

文件的拷贝操作

把指定目录的.java文件拷贝到指定的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

//拷贝文件:把指定目录的.java文件拷贝到指定的目录
public class FileCopyDemo2 {
public static void main(String []args) throws Exception{
//得到指定目录的文件
File srcDir = new File("C:/Users/15626/Desktop/新建文件夹 (2)");
File destDir = new File("C:/Users/15626/Desktop/新建文件夹 (2)/qqq");
//筛选出后缀是.java的文件存储在fs这个文件数组中
File[] fs = srcDir.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir, String name){
return new File(dir,name).isFile() && name.endsWith(".java");
}
});
//遍历这些文件
for (File srcFile : fs) {
//1.创建目标
File destFile = new File(destDir,srcFile.getName());
//2.创建输入流和输出流对象
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
//3.输入输出操作
byte[] buffer = new byte[10];
int len = -1;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
//4.关闭流
in.close();
out.close();
}
}
}

文件夹的拷贝

https://www.cnblogs.com/tfper/p/9855228.html

文件流正确关闭资源

https://www.cnblogs.com/tfper/p/9833722.html

五、缓冲流

缓冲流是一个包装流,目的起缓冲作用.操作流的时候,习惯定义一个byte/char数组. int read():每次都从磁盘文件中读取一个字节. 直接操作磁盘文件性能极低,为了解决这个问题,我们 定义一个数组作为缓冲区. byte[] buffer = new byte[1024]; 该数组其实就是一个缓冲区. 一次性从磁盘文件中读取1024个字节. 如此以来,操作磁盘文件的次数少了,性能得以提升,java提供的默认缓存区大小是8192(1024*8),我们一般不用修改大小.

缓冲流也有四种,分别是 BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter.操作方法都和文件流差不多,我们来看一下缓冲流到底快了多少?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @author 15626
* 操作字符或者字节流都应该用缓冲流包装起来,现在比较一下用缓冲流和不用缓冲流的情况写,复制一个文件所花费时间
* 长度的比较
*/
public class BufferCompare {
public static void main(String[] args) throws Exception {
File srcFile = new File("C:/Users/15626/Desktop/File/test.pdf");
File destFile = new File("C:/Users/15626/Desktop/File/copy.pdf");
//test1(srcFile,destFile);//不用缓冲流包装
test2(srcFile,destFile);//用缓冲流包装
}
//使用缓冲流,从内存每次读取1024个字节:15ms
public static void test2(File srcFile, File destFile) throws Exception {
long begin = System.currentTimeMillis();
BufferedInputStream in = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(destFile));

byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
System.out.print(System.currentTimeMillis() - begin);
}

//使用字节流,从磁盘中每次读取1024个字节:38ms
public static void test1(File srcFile,File destFile) throws Exception {
long begin = System.currentTimeMillis();
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
in.close();
out.close();
System.out.print(System.currentTimeMillis() - begin);
}
}

缓冲流的效率很高,所有以后操作字符字节流都可以用缓冲流包装起来。

六、内存流

内存流就是把程序中的数据暂存在内存中,在这里也就是数组中,或者把内存中的数据输出到其他媒介,内存流也是分为字节流,字符流还有一个字符串流(保存在字符串中)。简单操作一下,熟悉一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.*;
/**
* @author 15626
* 字节内存流,或者说是字节数组流
* 字节内存输入流 : ByteArrayOutputStream
* 字节内存输出流 : ByteArrayInputStream
*/
public class ByteArrayStreamDemo {
public static void main(String[] args) throws Exception {
//字节内存输出流:把程序的内容写到内存中的byte数组中
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("ABSAK".getBytes());
//把程序中的数据存在了缓冲数组中,toByteArray()方法用来获取缓冲区的数据
byte[] buffer = out.toByteArray();

//字节内存输入流: 把内存中的数据读到程序中
ByteArrayInputStream in = new ByteArrayInputStream(buffer);
byte[] by = new byte[1024];
int len = -1;
while((len = in.read(by)) != -1){
System.out.print(new String(by,0,len));
}

//关闭流无效,因为本身就在存储在内存中
in.close();
out.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author 15626
* 字符内存流或者说是字符数组流
* 字符内存输出流:CharArrayWriter
* 字符内存输入流:CharArrayReader
*/
public class CharArrayReaderWriterDemo {
public static void main(String[] args) throws Exception {
//字符内存输出流 : 从程序把数据存储到内存中的一个缓冲数组中
CharArrayWriter cWriter = new CharArrayWriter();
cWriter.write("你是天边最美的云彩");
//获取缓冲数组中的内容,用toCharArray()方法获取
char[] ch= cWriter.toCharArray();

//字符内存输入流:从内存中被数据读取到程序中来
CharArrayReader sReader = new CharArrayReader(ch);
char[] buffer = new char[1024];
int len = -1;
while((len = sReader.read(buffer)) != -1){
System.out.println(new String(buffer,0,len));
}


//关闭流无效,因为本身就在存储在内存中
sReader.close();
cWriter.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @author 15626
* 字符串的内存流,把程序中的数据存储在一个缓冲字符串中
* 字符串的输出流 :StringWriter
* 字符串的输入流:StringReader
*/
public class StringWriterReaderDemo {
public static void main(String[] args) throws Exception {
//字符串的输出流
StringWriter out = new StringWriter();
out.write("你真的想成为一只帅帅气气的一只小人吗");
//获取缓冲区的字符串
//System.out.print(out.toString());

//字符串的输入流
StringReader in = new StringReader("海纳百川,有容乃大");
char[] buffer = new char[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}

//关闭流无效,因为本身就在存储在内存中
in.close();
out.close();
}
}

要注意的关闭流无效,但是为了追求完美的格式,防止忘记了其他流的关闭,我还还是自娱自乐的关一下吧!

七、对象流

对象流包括ObjectInputStream和ObjectOutputStream,名字起得很有范,肯定是字节流

一般用对象流进行序列化和反序列化通过writeObject方法做序列化操作的,通过readObject方法做反序列化操作

1.序列化

指把堆内存中的Java对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络的节点(在网络上传输). 我们把这个过程称之为序列化.

需要做序列化的对象的类,必须实现序列化接口:java.io.Serializable接口(标志接口[没有抽象方法]). 底层会判断,如果当前对象是Serializable的实例,才允许做序列化.大多数类都已经实现了这个接口了

2.反序列化

把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象的过程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
* @author 15626
* 对象流的序列化和反序列化操作
* ObjectInputStream 用WriterObjcet 来做序列化
* ObjectOutputStream 用ReaderObject 来做反序列化
* 需要做序列化的类必须实现Serializable接口
*/
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception {
File f = new File("file/111.txt");
writeObject(f);//序列化
readObject(f);//反序列化
}
public static void readObject(File f) throws Exception{
ObjectInputStream in = new ObjectInputStream(new FileInputStream(f));
Student s = (Student)in.readObject();
System.out.println(s);
in.close();

}
public static void writeObject(File f) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(new Student("小伙子",19,"12345"));
out.close();
}
}
class Student implements Serializable {
/**
* 做反序列化操作必须存在序列化的字节码文件,由于随着版本升级肯定字节码会发生改变,java提供了序列化版本ID,
* 可以固定这一份字节码文件,解决了这个问题
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
//密码一般不会做序列化的,静态的字段和瞬态的字段不能做序列化,所以给密码加上瞬态transient修饰符
//反序列化的结果就为null
transient private String password;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", password=" + password + "]";
}
public Student(String name, int age, String password) {
super();
this.name = name;
this.age = age;
this.password = password;
}
}

可以看到里面有两个重要的问题,一个是反序列化对象需要对象的字节码文件,需要提供序列化ID固定这份字节码文件,还有一个是transient瞬息字段是不能序列化.

八、打印流

1.字节打印流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File;
import java.io.PrintStream;

/**
* @author 15626
* 字节打印流
* 可以自动刷新的,不想API说的那样,必须用换行符或者println才可以刷新缓冲区
*/
public class PrintStreamDemo {
public static void main(String[] args) throws Exception {
File f = new File("file/123.txt");
PrintStream p = new PrintStream(f);
p.print("aa");
p.println(121);
p.append("a");
p.close();

// System.out.println(); //这行代码等价于下面的两行代码
// PrintStream ps = System.out;
// ps.println("a");
}
}

2.字符打印流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author 15626
* 字符打印流
* 当启用自动刷新后,即new PrintWriter(new FileOutputStream(f),true); true
* 调用println方法或者printf或者\n后进行刷新。
*
* 当不启动自动刷新,需要手动刷新,调用flush方法,或者close方法会进行刷新
*/
public class PrintWriterDemo {
public static void main(String[]args) throws Exception{
File f = new File("file/123.txt");
PrintWriter p = new PrintWriter(new FileOutputStream(f),true);
p.print("wqe");
p.println();
p.close();
}
}
文章目录
  1. 1. IO流
    1. 1.1. 一、IO流的分类
    2. 1.2. 二、四大基本流
    3. 1.3. 三、模板操作
  2. 2. 四、文件流
    1. 2.1. 文件字节输入流(FileInputStream)
    2. 2.2. 文件字节输出流(FileOutputStream)
    3. 2.3. 文件字符输入流(FileReader)
    4. 2.4. 文件字符输出流(FileWriter)
      1. 2.4.1. 文件的拷贝操作
  3. 3. 五、缓冲流
  4. 4. 六、内存流
  5. 5. 七、对象流
    1. 5.1. 1.序列化
    2. 5.2. 2.反序列化
  6. 6. 八、打印流
    1. 6.1. 1.字节打印流
    2. 6.2. 2.字符打印流