IO流学习
流的分类
- 根据操作单位不同分为字节流和字符流
- 根据流向不同分为输入流和输出流
- 根据角色不同分为节点流和处理流
以下四个类是IO流中最基础的类,都是抽象类。其他流都是继承他们的。
分类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
字节流:操作的对象是字节数组
字符流:操作的对象是字符数组
节点流:作用在文件上的流,直接对文件进行读写操作
处理流:作用在节点流上的流,可以加速流的传输
输入流和输入流是相对的,主要看你站在那个角度来看待,如果站在程序的角度,读入数据较输入流,写出数据较输出流。
IO流体系结构
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
过滤流 | FilterInputStream | FileOutputStream | FilterReader | FileWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
访问文件的流都是节点流,其他除了抽象基类外都是处理流
File类
想要进行IO操作,肯定离不来文件,所以学习IO流的前提就是要学会File类的操作。
File类的介绍
File是用来操作文件或目录的,但是不能对文件的内容进行操作,主要是用于文件和目录的创建、文件的删除、文件的查找。
File类构造方法
File类表示的是硬盘中实际存在的目录或文件,可以通过以下三种方式创建一个File类的对象。
File(String pathname)//通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。 常用
File(File parent, String child) //从父抽象路径名和子路径名字符串创建新的 File实例。常用
File(String parent, String child) //从父路径名字符串和子路径名字符串创建新的 File实例。 常用
File(URI uri) //通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
代码举例:
@Test
public void test(){
//根据路径创建
String path = "d:\file.txt";
File file = new File(path);
//根据父文件和子类路径创建
File parentFile = new File( "d:\io");
File file1 = new File(parentFile,"file1.txt");
//根据父路径和子路径创建
String parentPath = "d:\io";
String childPath = "file2.txt";
File file2 = new File(parentPath,childPath);
}
注意:在Windows中路径分割使用‘’,在Linux中使用‘/’,为了方便区分在File类中有一个separator 属性,可以用来代替分隔符。
在Java中 是用来转义的,所以这里需要使用两个 字符
路径问题
相对路径:相对于当前的项目路径,不带盘符。如: "hello.txt"
绝对路径:相对于硬盘的物理路径,是一个带有盘符的完整路径。如:"d:hi.txt"
如代码所示,目前文件只是存在于内存中,还没有在硬盘上创建实际的文件,需要使用一些方法来创建,下面介绍File类中的常用方法。
File类常用方法
1、获取的方法
public String getAbsolutePath() //绝对路径名字符串。
public String getPath() //将此抽象路径名转换为路径名字符串。
public String getName() //返回由此抽象路径名表示的文件或目录的名称。
public String getParent() //返回上层文件目录路径,没有则返回null。
public long length() //返回由此抽象路径名表示的文件的长度。 长度指的是文件的字节数,不能获取目录的长度。
public String[] list() //获取指定目录下的所有文件或文件目录的名称数组
public File[] listFiles() //获取指定目录下的所有文件或文件目录的File数组
@Test
public void test3(){
File file = new File("f:\io\hello.txt");
System.out.println("绝对路径:"+file.getAbsolutePath());
System.out.println("路径:"+file.getPath());
System.out.println("名字:"+file.getName());
System.out.println("上层路径:"+file.getParent());
System.out.println("长度"+file.length());
System.out.println("文件目录:"+file.list());
System.out.println("文件目录"+file.listFiles());
}
/*
运行结果:
绝对路径:f:iohello.txt
路径:f:iohello.txt
名字:hello.txt
上层路径:f:io
长度0 -->因为文件中没有内容,所以是0
文件目录:null --> 因为当前是一个文件,所以没有目录,如果是一个目录就会有
文件目录null --> 因为当前是一个文件,所以没有目录,如果是一个目录就会有
*/
进行判断的方法
public boolean exists()//判断文件或目录是否存在
public boolean isDirectory()//判断是否是目录
public boolean isFile()//判断是否是文件
@Test
public void test5(){
File file = new File("hello.txt");//该文件没建过
System.out.println("文件是否存在"+file.exists());//false
System.out.println("是否是目录"+file.isDirectory());//false
System.out.println("是否是文件"+file.isFile());//false,因为连文件都没有,所以是false
File file1 = new File("hello.txt");//该文件我已经创建过了
System.out.println("文件是否存在"+file1.exists());//true
System.out.println("是否是目录"+file1.isDirectory());//false
System.out.println("是否是文件"+file1.isFile());//true
}
进行创建的方法
public boolean createNewFile() //如果文件不存在则创建一个新的空文件返回true,如果存在,不创建,返回false;
public boolean mkdir()//创建文件目录,如果文件目录存在,不创建,如果文件的上层目录不存在,也不创建;
public boolean mkdirs()//创建文件目录,如果上层文件目录不存在,也一起创建
@Test
public void test4() throws IOException {
File file = new File("test.txt");
System.out.println(file.createNewFile());//true
File file1 = new File("f:\test\hello");
file1.mkdir();//test和hello目录都不存在,不会创建
File file2 = new File("f:\test\hello\java");
file2.mkdirs();//test和hello目录都不存在,会全部创建
}
进行删除的方法
public boolean delete()//删除文件或目录
File类练习
打印指定文件目录下的所有文件-递归实现
//递归打印文件
@Test
public void test6(){
File file = new File("f:\io");
printFile(file);
}
public void printFile(File file){
File[] files = file.listFiles();
for (File f : files) {
if(f.isDirectory()) printFile(f);
System.out.println(f.getName());
}
}
字符流
纯文本文件都用字符流来处理
字符输入流
FileReader
1、构造方法
FileReader(File file) //根据file创建一个对象
FileReader(String fileName)//根据文件目录创建一个对象
2、常用方法
public int read()//从文件中读取一个字符,如果文件中没有字符了返回-1
public int read(char[] cbuf)//从文件中读取多个字符,如果文件中没有字符了返回-1,如果有返回读了多少个字符
public void close();//关闭流
使用FileReader读取hello.txt中的内容
hello.txt
@Test
public void test1() {
FileReader reader = null;
try {
File file = new File("hello.txt");
reader = new FileReader(file);
char[] cbuf = new char[5];
int len = 0;
// 方式一,每次读一个字符
while((len = reader.read()) != -1){//每次读一个字符
System.out.print((char)len);
}
/*方式二,每次读多个字符
while ((len = reader.read(cbuf)) != -1){
for (int i = 0; i < len; i++) {//这里不能使用cbuf.length;因为在最后可能字符不够5个,所以使用len
System.out.print(cbuf[i]);
}
}
*/
} catch (IOException e) {
e.printStackTrace();
} finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
运行结果:
hellojava
*/
使用IO流都有可能会出现异常,所以在这里需要使用trycatchfinally来处理异常
字符输出流
FileWriter
1、构造方法
FileWriter(File file) //根据file创建一个对象
FileWriter(File file, boolean append) // 根据file创建一个对象,并根据append判断在多次写入数据是否覆盖,默认false,选择覆盖
FileWriter(String fileName) // 根据字符串创建一个对象
FileWriter(String fileName, boolean append) //同上,true表示不覆盖,false表示覆盖,默认false
2、常用方法
public void wirte(int c)//写入一个字符
public void wirte(char[] cbuf)//写入多个字符
public void wirte(char[] cbuf, int off, int len)//写入字符的长度由len决定,off表示从那开始。
public void wirte(String str)//写入字符串
public void wirte(String str, int off, int len)//写入部分字符串,有off和len决定,off表示开始的下标,len表示长度
public void flush()//刷新缓存区,如果没有关闭流则数据不会写入文件
public void close()//关闭流,关闭流之后会自动刷新缓存
使用FileWriter向hello.txt中写入数据
@Test
public void test8(){
FileWriter fw = null;
try {
File file = new File("hello.txt");
fw = new FileWriter(file);
String str = "i love java";
fw.write(str);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
注意:字符流不能处理非文本的数据
字节流
非纯文本文件使用字节流来处理
字节输入流
FileInputStream
1、构造方法
FileInputStream(File file)//根据file创建一个字节输入流对象
FileInputStream(String name)//根据文件路径创建一个字节输入流对象
2、常用方法
public int read()//从文件中读取一个字节,如果文件中没有字节了返回-1
public int read(b[] b)//从文件中读取多个字节,如果文件中没有字节了返回-1,如果有返回读了多少个字节
public void close();//关闭流
使用FileInputStream读取hello.txt中的内容
@Test
public void test9(){
FileInputStream fis = null;
try {
File file = new File("hello.txt");
fis = new FileInputStream(file);
int len;
while((len = fis.read()) != -1){//每次读一个字节
System.out.print((char)len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节输出流
FileOutputStream
1、构造方法
FileOutputStream(File file)//根据file创建一个字节输出流对象
FileOutputStream(File file, boolean append)//通过append判断是否覆盖,默认为false,覆盖,true为不覆盖
FileOutputStream(String name) //根据文件路径创建一个字节输出流对象
FileOutputStream(String name, boolean append)//同上
2、常用方法
public void wirte(int c)//写入一个字节
public void wirte(byte[] b)//写入多个字节
public void wirte([] b, int off, int len)//写入字节的长度由len决定,off表示从那开始。
public void close()//关闭流,
使用FileOutputStream向hello.txt中写入数据
@Test
public void test10(){
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("hello.txt"));
String str = "hellojava";
fos.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
因为我都选择默认不append,所以每次都是将之前的覆盖掉
缓冲流
缓冲流的主要目的就是提高之前的流的读取或写入的速度。
缓冲流的使用方法和字符流或字节流一样。这里就介绍一下如何声明一个缓冲流。
字符缓冲流
1、构造方法
BufferedReader(Reader in)
BufferedWriter(Writer out)
注意:构造方法的参数是一个抽象类,在传参的时候应该传该抽象类的实现类
使用
BufferedReader br = new BufferedReader(new FileReader(new File("hello.txt")));
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("hello.txt")));
2、特有方法
public String readLine()//读一行
public void newLine()//换行
字节缓冲流
1、构造方法
BufferedInputStream(InputStream in)
BufferedOutputStream(OutputStream out)
注意:构造方法的参数是一个抽象类,在传参的时候应该传该抽象类的实现类
使用
BuffededInputStream bis = new BufferedInputStream(new FileInputStream("hello.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileInputStream("hello.txt"));
转换流
转换流就是将字节流转换为字符流或将字符流转换为字节流。
InputStreamReader(将字节流转换为字符流)
1、构造方法
InputStreamReader(InputStream in) //创建一个默认字符集的转换流
InputStreamReader(InputStream in, String charsetName) //创建一个指定字符集的转换流
2、使用
@Test
public void test11(){
InputStreamReader isr = null;
InputStreamReader isr1 = null;
try {
isr = new InputStreamReader(new FileInputStream("hello.txt"));//默认字符集
isr1 = new InputStreamReader(new FileInputStream("hello.txt"),"GBK");//默认GBK字符集,会出现乱码
int len = 0;
while((len = isr.read()) != -1){
System.out.print((char)len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
运行结果:
hellojava
*/
OutputStreamWriter(将字符流转换为字节流)
1、构造方法
OutputStreamWriter(OutputStream out)//创建一个默认字符集的转换流
OutputStreamWriter(OutputStream out, String charsetName) //创建一个指定字符集的转换流
2、使用
@Test
public void test12(){
OutputStreamWriter osw = null;
try {
osw = new OutputStreamWriter(new FileOutputStream("hello.txt",true));
String str = "hellojava";
osw.write(str);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(osw != null) {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
序列化流
序列化流分为:序列化和反序列化
序列化:就是将自己写的一个对象持久化到硬盘上,
反序列化:将序列化到硬盘上的对象取出来
要求:序列化的类必须实现Serializable接口,而且这个类的属性也必须是可序列化的。8个基本类型是可序列化的。
序列化的类需要提供一个常量,用来表示序列版本号
如果没有这个版本号,会出错!!出什么错呢,比如你先序列化了一个对象到一个文件中,然后你对序列化的类进行了修改,然后从文件中吧对象反序列化出来就会出错。
因为在你实现了Serializable接口的时候,你当前类会有一个默认的序列版本号,你修改类的时候,你的序列版本号就有可能会修改,然后反序列化的时候就会找不到对象。
重点:被static和transient修饰的属性是不会被序列化的。
序列化-ObjectOutputStream
1、构造方法
ObjectOutputStream(OutputStream out) //参数是一个抽象类,需要传一个它的子类
2、使用
@Test
public void test13(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
oos.writeObject(new User("Tom",3));
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
报错了,为什么呢?因为User类没有实现Serializable接口。当实现了接口后就能正常的序列化了。
ObjectInputStream-反序列化
1、构造方法
ObjectInputStream(InputStream in)
2、使用
@Test
public void test14(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("hello.txt"));
User user = (User) ois.readObject();
System.out.println(user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
反序列化后,得到了刚才序列化的内容。
写在最后
花了一天时间,总算是自己把IO的部分总结了一下,怎么说呢,总结的肯定是不够完整和细致的,以后在实践中慢慢的在完善吧!
自己还是太菜了。需要学习的地方还很多!不学习就找不到工作!!!!!