![请添加图片描述](https://img-blog.csdnimg.cn/18c2b3f3bc6e45dea2ca4923349d5849.jpeg)
输入流:磁盘等数据给内存。
输出流:内存里的数据存入磁盘。
字节流:内存和磁盘之间的管道是一个一个字节相互来去
字符流:内存和磁盘之间的管道是一个一个字符相互来去
字节流
文件字节输入流
每次读取1个字节
public class Main {
public static void main(String[] args) throws Exception {
//创建文件字节输入流管道,与源文件接通
//FileInputStream inputStream = new FileInputStream(new File("src/test.txt"));
FileInputStream inputStream = new FileInputStream("src/test.txt");
//读取1个字符,返回int,没有字符返回-1
int b = inputStream.read();
System.out.println((char) b);
//循环读取文件地所有字符,多次访问硬盘性功能很差且遇到汉字会有乱码
//运行时注释上面读取一个字符的代码,否则从第二个字符开始读取
int x;
while ((x = inputStream.read()) != -1) {
System.out.print((char) x);
}
//流使用完毕后必须关闭,释放系统资源
inputStream.close();
}
}
中文字符乱码原因:美国最早推出ASC码(之后的编码方式基本上都在此基础上),包含数字、字母能字符,每个字符1个字节;中国推出的GBK国标码每个汉字2个字节;国际组织推出UTF-8编码汉字占3个字节。每次只读取一个字节,所以汉字会出现乱码。
每次读取多个字节
public class Main {
public static void main(String[] args) throws Exception {
FileInputStream inputStream = new FileInputStream("src/test.txt");
//test.txt的内容是abcde
int numOfWater = 3;
byte[] buffer = new byte[numOfWater];//就像是一个水桶
int lenOfRead = inputStream.read(buffer);
String s = new String(buffer);
System.out.println(s);
System.out.println("读取字符个数:" + lenOfRead);
//再运行一次
int lenOfRead2 = inputStream.read(buffer);
String s2 = new String(buffer);
System.out.println(s2);
System.out.println("读取字符个数:" + lenOfRead2);
inputStream.close();
}
}
第二次读取的输出有误,对后续的编程可能会有影响,String类重载了方法修改入下。
String s = new String(buffer, 0, lenOfRead);//读多少,倒多少
while循环改造,还是会有汉字乱码的问题,截断汉字的字节。
byte[] buffer = new byte[3];
int lenOfRead;
while ((lenOfRead = inputStream.read(buffer)) != -1) {
String s = new String(buffer, 0, lenOfRead);
System.out.print(s);
}
一次性读取全部字节
- 方法1:定义一个和文件字节数大小一样的数组,重复以上操作
- 方法2:调用public byte[] readAllBytes()
byte[] bytes = inputStream.readAllBytes();
System.out.println(new String(bytes));
两个方法本质上是一样的,JDK中封装好了方法而已。同时如果文件特别大,readAllBytes()方法会抛出内存溢出异常OutOfMemoryError
文件字节输出流
public class Main {
public static void main(String[] args) throws Exception {
//创建文件字节输入流管道,与源文件接通
//覆盖数据管道
FileOutputStream outputStream = new FileOutputStream("src/test.txt");
//写一个字节
outputStream.write(97);//a
outputStream.write('b');//b
outputStream.write('国');//默认写进去第一个字节,所以写进去乱码
//写多个字节
byte[] bytes = "我爱你中国".getBytes();
outputStream.write(bytes);
//使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
outputStream.write(bytes, 0, 9);
//换行,\n支持windows操作系统,\r\n支持更过平台
outputStream.write("\r\n".getBytes());
outputStream.close();
}
}
上面的方法每次运行会先将文件里的数据删干净,再写入。如果想追加数据,看下面。
//追加数据管道
FileOutputStream outputStream = new FileOutputStream("src/test.txt", true);
专业释放资源方式
以上我们讲到,使用完io流后要及时关闭流以达到释放资源的目的,但是在复杂的情形中,关闭流之前出现异常,那么流就一直没有被关闭,一直占用着资源,这一点不好。主要以下两种方式解决。
- try-catch-finally
finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
public class Main {
public static void main(String[] args) {
FileOutputStream outputStream = null;
try {
//创建文件字节输入流管道,与源文件接通
outputStream = new FileOutputStream("src/test.txt", true);
//写多个字节
byte[] bytes = "我爱你中国".getBytes();
outputStream.write(bytes);
//使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
outputStream.write(bytes, 0, 9);
//换行,\n支持windows操作系统,\r\n支持更过平台
outputStream.write("\r\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 防止创建流对象之前有异常,或者流在try中已经关闭
try {
if (outputStream != null) outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- try-with-resource
以上关闭资源方式的finally中的代码略显臃肿,JDK7之后提供了更简便的释放资源方式。
注意:try之后的括号里只能放资源对象,像定义一个整形变量是不可以的。
资源:实现了AutoCloseable接口,每个资源实现了close()方法,资源对象被使用完成后会自动去调用它的close()方法实现资源释放。
public class Main {
public static void main(String[] args) {
try (//创建文件字节输入流管道,与源文件接通
FileOutputStream outputStream = new FileOutputStream("src/test.txt", true)) {
//写多个字节
byte[] bytes = "我爱你中国".getBytes();
outputStream.write(bytes);
//使用 UTF-8 编码,每个汉字三个字节,只想输入”我爱你“要输入9个字节
outputStream.write(bytes, 0, 9);
//换行,\n支持windows操作系统,\r\n支持更过平台
outputStream.write("\r\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流
public class Main {
public static void main(String[] args) {
try(
Reader reader = new FileReader("src/test.txt");
) {
char[] buffer = new char[3];
int len;
while ((len = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
跟字节流差不多,不做演示了。
注意:字符流写出数据后,必须刷新流(.flush())或者关闭流,不然没写。因为字符流先把数据写到了缓冲区,刷新和关闭操作通知缓冲区写道文件里。
缓冲流
对原始流经行包装,提升原始流的性能。
先来讲讲缓冲流是如何提高原始流的读取性能的,缓冲流的原理。
举一个例子从D盘复制文件到C盘,用字节输入输出流实现逻辑如下。在内存里创建一个1KB的数组,16次从D盘输入,16次向C盘输出,一共32次访问磁盘。
字节缓冲输入输出流先是包装了字节输入输出流,它就会在内存中开辟一个8KB的缓冲池。D盘数据到输入缓冲池只需要2次,也就是2次输入;同理,2次输出。也就是4次访问磁盘。
输入缓冲池的数据通过数组放到输出缓冲池中,这一操作在内存中实现,是相当快的。
字节缓冲流
方法也就是功能和字节流一样,只是性能上得到了提升。
public class Main {
public static void main(String[] args) {
try(
InputStream inputStream = new FileInputStream("src/test.txt");
//创建字节输入缓冲流,可传入int类型作为第二参数定义缓冲池大小
InputStream bis = new BufferedInputStream(inputStream);
OutputStream outputStream = new FileOutputStream("src/test_copy.txt");
//创建字节输出缓冲流,可传入int类型作为第二参数定义缓冲池大小
OutputStream bos = new BufferedOutputStream(outputStream);
) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符缓冲流
字符缓冲输入流还新增了一个功能
字符缓冲输出流新增功能
转换流
解决代码编码和文本文件编码不一致的情况,避免乱码。
- 字符输入转换流
public class Main {
public static void main(String[] args) {
try(
//创建原始字节流(GBK编码)
InputStream inputStream = new FileInputStream("src/test.txt");
//把原始字节流变成字符转换流
Reader reader = new InputStreamReader(inputStream, "GBK");
//字符转换流包装成字符缓冲流
BufferedReader br = new BufferedReader(reader);
) {
char[] buffer = new char[20];
int len = br.read(buffer);
System.out.println(new String(buffer,0,len));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 字符输出转换流
public class Main {
public static void main(String[] args) {
try(
//创建原始字节流(GBK编码)
OutputStream outputStream = new FileOutputStream("src/test.txt");
//把原始字节流变成字符转换流
Writer writer = new OutputStreamWriter(outputStream, "GBK");
//字符转换流包装成字符缓冲流
BufferedWriter bw = new BufferedWriter(writer);
) {
bw.write("我爱中国");
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印流
PrintStream/PrintWriter (打印流)
作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
- PrintStream
public class Main {
public static void main(String[] args) {
try(
PrintStream ps = new PrintStream("src/test.txt")
) {
ps.println("你好");
ps.println(true);
ps.println(123);
ps.println(3.14);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- PrintWriter
try(
PrintWriter pw = new PrintWriter("src/test.txt")
) {
pw.println("你好");
pw.println(true);
pw.println(123);
pw.println(3.14);
} catch (Exception e) {
e.printStackTrace();
}
以上两个流属于高级流,创建时在后面加true不能实现追加字符。那如果要实现追加流,需要包装一个实现追加流的低级流。
PrintWriter pw = new PrintWriter(new FileWriter("src/test.txt", true))
- 打印流的应用:输出重定向
平时开发中,我们通常把数据输出到控制台,而程序在运行的时候通常用户是在文件里看数据或者异常。
public class Main {
public static void main(String[] args) {
try(
PrintStream ps = new PrintStream(new FileOutputStream("src/test.txt", true))
) {
//改变系统默认的打印对象
System.setOut(ps);
System.out.println(123);
System.out.println("你好");
System.out.println(1132.543);
} catch (Exception e) {
e.printStackTrace();
}
}
}
数据流
应用场景,输出数据的同时,把数据的类型也输出记录下来。
数据输出流
数据输入流
可以看出来数据输入流和数据输出流的方法是相互对应的,使用时也应该相互对应使用。
public class Main {
public static void main(String[] args) {
try(
DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/test.txt"));
) {
dos.writeInt(123);
dos.writeDouble(12.3);
dos.writeUTF("我爱中国");
} catch (Exception e) {
e.printStackTrace();
}
try(
DataInputStream dis = new DataInputStream(new FileInputStream("src/test.txt"));
) {
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
System.out.println(dis.readUTF());
} catch (Exception e) {
e.printStackTrace();
}
}
}
而text中的数据
序列化流
序列化:把Java对象写道文件中去
反序列化:把文件里的Java对象读出来
序列化流就是解决把把Java对象写道文件中去的。
对象字节输出流
对象字节输入流
public class Main {
public static void main(String[] args) {
try(
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/test.txt"));
) {
//需要序列化的对象,必须实现可序列化接口Serializable
oos.writeObject(new Student("张三", 20));
} catch (Exception e) {
e.printStackTrace();
}
try(
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/test.txt"));
) {
Student student = (Student) ois.readObject();
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:需要序列化的对象,必须实现可序列化接口Serializable
如果想要对象的某个成员变量(一般密码不参与)不参与序列化,在变量前加transient修饰符即可。
private transient String password;
总结
读取文本更适合使用字符流,而字节流跟适用于数据转移,如文件的复制。
使用缓冲流可以提高性能。
转换流解决编码问题。
使用资源后要及时关闭资源,用try-with-resource更方便。