1.什么是IO
IO流实际上是内存和硬盘(中的文件)之间的通道。
流都在java.io.*包下。
无论是输出还是输入,读还是写,都是针对内存而言的。
IO流的分类
按照流的方向进行分类(以内存为参照物):
输入流:往内存中去,叫输入(Input),或者叫读(Read)。
输出流:从内存中出来,叫输出(Output),或者叫写(Write)。
另一种方式是按照读取数据方式的不同进行分类的。
字节流:按照字节的方式读取数据,一次读取一个字节,这种流是万能的,什么文件都可以读。
字符流:按照字符的方式读取数据,一次读取一个字符,专为读取文本文件而存在的,而且只能读取文本文件。
注:windows操作系统中一个字符“a”占一个字节,用字节流读“a”就是一个“a”,因为.txt文件和java没有关系。
IO流的四大家族
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族都是abstract类。
所有类都实现了java.io.Closeable接口,都是可关闭的,其中有close()方法,用完流之后要进行对流的关闭。
所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法,输出流在输出结束后要进行刷新,用于将管道中的数据强行输出完(清空流管道),如果没有flush可能会导致数据的丢失。
java中主要学的16个类
2.FileInputStream
首先看看FileInputStream是如何定义的。
FileInputStream fis = null; // 在try之外进行定义,注意一定要赋值为null
// 有try
fis = new FileInputStream("D:\\CODE\\java\\IO\\test.txt");
// 这里会抛出异常,所以要进行异常的处理
read方法
int data = fis.read(); // 一次读取一个字符,以int形式返回
System.out.println(data); // 读到字符“a”,然后返回97
通过while循环对read方法进行改进
int data = fis.read(); //
while(data != -1){ // 这里不能写fis.read() != -1,因为文件指针会跳过一个字节,所以得用一个变量进行判断
System.out.println(data);
data = fis.read();
}
或者可以这么写
int data = 0;
while((data = fis.read()) != -1){ // 注意里面的括号
System.out.println(data);
}
但一次只读取一个字节,内存和硬盘交换太频繁。
我们可以使用byte数组+while循环去读文件。
fis = new FileInputStream("D:\\CODE\\java\\IO\\test.txt");
byte [] bytes = new byte[4]; // 我们new出来一个4个长度(当然可以更多)的byte数组
int datacount = 0; // datacount作为每次读到的byte数组的长度
while((datacount = fis.read(bytes)) != -1){ // 如果没读完
System.out.print(new String(bytes,0,datacount)); // 使用String类中的构造方法一并输出
}
available方法,判断有流中有多少个字节没读
System.out.println("文件共多少个字节:"+fis.available()); // 可以在一开始就明确文件有多少个字节
byte [] bytes = new byte[fis.available()]; // 字节数量作为byte数组的长度,可以只读一次
System.out.println(new String(bytes));
skip方法,跳过多少个字节不读
3.FileOutputStream
FileOutputStream流把内存中的数据写出硬盘上。
fos = new FileOutputStream("myfile");
String s = "i am Chanese";
byte [] bs = s.getBytes(); // 将字符串转为byte数组的方法
fos.write(bs);
// fos.write(bs,0,2) 表示从byte数组的0位置写入,写两个字节
// 注意写完之后一定要记得刷新一下
fos.flush();
这种方法谨慎使用,因为会清空原文件原先的内容,FileOutputStream的重载构造方法中可以在后面加上true,表示写入的内容是追加而不是覆盖。
fos = new FileOutputStream("myfile" , true);
文件的拷贝,原理就是把硬盘上的东西输入到内存中,再从内存中把数据写到硬盘里。使用字节流的拷贝,可以使得要拷贝的文件类型是任意的,不局限于txt文件。
FileOutputStream fos = null;
FileInputStream fis = null;
fis = new FileInputStream("E:\\迷醉.Euphoria.S01E01.中英字幕.WEB-1080P-人人影视.mp4");
fos = new FileOutputStream("D:\\迷醉1.mp4");
byte [] bytes = new byte[1024*1024];
// 一次最多可以拷贝1M
int count = 0;
while ((count = fis.read(bytes)) != -1){
// 读到多少个字节数,则拷贝多少个字节
fos.write(bytes,0,count);
}
// 最后要记得刷新
fos.flush();
finally {
// 注意这里一定要分开处理异常,否则一个异常处理了会导致另外一个流没有关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.FileReader和FileWriter
文件字符输入输出流,只能读取不同的文本文件,不能读取媒体文件。
读取文本文件时,比较方便,快捷。
// 定义一个FileWriter对象out
// 定义一个FileReader对象in
// 字符的输入输出流不能用byte数组,只能用Sring数组。
char [] chars = {'我','是','中','国','人'};
out.write(chars);// 可以直接在write的参数中添加一个字符串数组形参
System.out.println(chars,0,readcount); // 可以像读字节一样指定读几个字符
out.write("我是中国人"); // 也可以直接读一个字符串(最方便)
5.BufferedReader和BufferedWriter
BufferedReader:带有缓冲区的字符输入流,使用这个流时不需要定义char数组(byte数组),自带缓冲。
BufferedReader br = null;
FileReader reader = null;
reader = new FileReader("test.txt");
// 注意这里要传一个Reader类型的参数,但是Reader是abstra类型的,不能实例化,便传一个Reader的子类进去。
br = new BufferedReader(reader);
// readLine方法可以一次读一行
// 但是注意readLine方法读一行时不带换行符
String s = br.readLine();
System.out.println(s);
finally {
// 注意这里处理异常的时候只需要处理最外层的异常就行
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
通常把里面的流叫节点流,把外面的流叫包装流。
readLine在读不到东西时会返回null,所以可以用while循环实现把文件中的东西全部读出来。
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
在定义BufferedReader时,参数只能写Reader的子类,否则(比如写一个FileInputStream)会报错。
但是我们可以使用转换流把FileInputStream转换成Reader流。
// 先定义一个FileInputStream流
FileInputStream in = new FileInputStream("test.txt");
// 再用转换流把FileInputStream转换成Reader(相当于做了一个对象的转换)
InputStreamReader reader = new InputStreamReader(in);
// 最后在定义BufferedReader流的时候把转换后的对象传进去
BufferedReader br = new BufferedReader(reader);
// 只用关闭最外层的包装流就行了
br.close();
当然我们可以简写
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt");
BufferedWriter:带有缓冲的字符输出流
// 创建一个自带缓冲的字节输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("test2.txt"));
// 像FileWriter一样,可以在write方法里直接写字符串
bw.write("我是中国人\n");
bw.write("我骄傲!");
// 最后注意流的刷新和关闭
bw.flush();
bw.close();
6.数据流
java.io.DataOutputStream:数据专属的流。
这个流可以将数据连通数据的类型一并写入文件。
注:这个文件不是普通的文本文件,使用记事本打不开。
java.io.DataInputStream:数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream读,而且读的时候需要提前知道写入的顺序。
读的顺序和写的顺序一直,才能正常取出数据。
7.标准输出流
PrintStream
// System.out是System类中的一个属性,初始值为空,类型是PrintStream的
PrintStream ps = System.out;
// println方法实际上实在PrintStream类中的。
ps.println("123");
实际上,我们可以改变输出的方向(默认输出在控制台上),需要调用setOut方法
// 我们想将数据输出在log文件中
// PrintStream类实例化时,参数需要一个OutputStream类型的节点流
PrintStream ps = new PrintStream(new FileOutputStream("log"));
// setOut方法是在System类中的
// 调用这个函数,数据便会通过标准输出流输出到log文件中
System.setOut(ps);
System.out.println("123123");
8.File类
File类不属于四大家族的类,所以不能对文件进行读写。
File类的实例化对象是文件和目录路径的抽象表示。
File类常用的方法
import java.io.*;
public class demo1 {
public static void main(String[] args) throws Exception{
File f = new File("log");
// exists方法,用于判断对应路径有没有指定的文件
System.out.println(f.exists());
if(!f.exists()){
// createNewFile方法,创建一个新的文件
f.createNewFile();
// mkdir方法,创建一个新的目录(文件夹)
f.mkdir();
}
File f2 = new File("a/b/c/d/hello.txt");
if(!f2.exists()){
// 创建树结构的目录
f2.mkdirs();
}
// 打印文件的上级目录
System.out.println(f2.getParent());
// 打印文件的绝对地址
System.out.println(f2.getAbsolutePath());
// 打印文件的字节数(大小)
System.out.println(f2.length());
File f3 = new File("D:\\CODE");
// 获取当前目录下的所有文件,包装成一个数组
File [] flist = f3.listFiles();
// 遍历
for(File file : flist){
// 打印文件的名字
System.out.println(file.getName());
}
}
}
9.序列化和反序列化
在关闭程序的时候,程序中实例化的对象会被垃圾回收,有没有一种技术可以在程序结束之前把对象保存起来,我们可以使用序列化和反序列化。