IO问题是任何编程语言都无法回避的,可以说,IO问题是整个人机交互的核心问题。在Java中,提供了包名为java.io
的专门操作类库。
在这个包下提供了将近有80个类,这些类大致分为四组:
- 基于字节操作的
InputStream
和OutputStream
。 - 基于字符操作的IO接口:
Writer
和Reader
。 - 基于磁盘操作的IO接口:
File
- 基于网络操作的IO接口:Socket
要学好Java的文件IO操作,重点需要掌握:
- 2个代码模型
- 5个类
- File
- OutputStream
- InputStream
- Reader
- Writer
- 1个接口(Serializable)
1. File类的基本使用
这个类的使用直接产生实例化对象即可。
1.1 File类实例化对象
如果要实例化对象,需要用到两个构造方法。
- public File(String pathName)
- public File(File parent,String child),设置父路径和子文件
1.2 File类操作文件
如果要进行文件的操作,可以使用下列方法:
- createNewfile: 根据制订的路径创建新文件
- exists(); 判断文件是否存在
- file.delete(); 删除文件
实例: 判断文件是否存在,存在的话就删除,不存在的话就创建
public class FileDemo {
public static void main(String[] args) throws IOException {
File file = new File("D:\\hello.txt"); // 定义要操作的文件路径
if (file.exists()) { // 判断文件是否存在
System.out.println("文件已经存在,已经将其删除");
file.delete(); //删除文件
} else {
System.out.println("不存在文件,已经创建了一个新的!");
file.createNewFile(); //创建一个空文件
}
}
}
说明,如果此处操作的都是根路径,如果是含有子路径的话,如果路径不存在,是不会自动创建路径的,会报错
注意:路径分隔符
由于不同的操作系统对路径分隔符是不同的,因此,使用路径的时候,需要将上述指定操作文件的位置修改为如下:
File file = new File("D:"+File.separator+"hello.txt"); // 定义要操作的文件路径
1.3 File类操作目录
有以下常用的方法操作目录
- 获得父目录的方法1: getParentFile(),该方法返回的是File类型的值(推荐使用)
- 获取父目录的方法2: getParent(),该方法返回的是String类型的值
- 创建父目录,就对获取的父目录的返回对象执行: file.getParentFile().mkdirs(),该方法如果有多层路径,都可以自动创建。
public class FileDemo {
public static void main(String[] args) throws IOException {
/**
* 对路径的操作
* */
File file = new File("D:"+File.separator+"hello"+File.separator+"hello.txt"); // 定义要操作的文件路径
if(!file.getParentFile().exists()){ // 判断父目录是否存在
System.out.println(file.getParentFile().toString());
file.getParentFile().mkdirs(); // 默认创建多级父目录
}
file.createNewFile();
}
}
1.4 File类取得文件信息
File类提供很多取得文件信息的方法。方法有很多,使用方式也都类似,直接通过一个例子记录一下吧。
范例
class MyMath {
// 保留几位小数
public static double round(double num, int scale) {
return Math.round(num * Math.pow(10, scale)) / Math.pow(10, scale);
}
}
public class FileInfoDemo {
public static void main(String[] args) {
File file = new File("D:\\4. 暂存待整理\\截图\\Screenshot_2016-07-17-11-21-27.png"); // 定义要操作的文件路径
if (file.exists() && file.isFile()) { // 文件存在且是文件
System.out.println("文件大小:" + MyMath.round((file.length()) / 1024, 2)); // 文件大小为字节数,太大了,仅保留2位小数
System.out.println("上次修改时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date(file.lastModified())));
}
}
}
2. 字节流与字符流
File类本质上不操作文件的内容,要进行文件内容的处理,就需要用到字节流和字符流的概念。
字节流和字符流的区别是什么?
- 字节流是指文件作为数据的二进制传输形式,而字符流则是在内存中进行操作的对象的感觉。这么理解不太严密,但是可以简单认为,字符流更靠近文件。
- 字节流更多的是文件在磁盘上的保存,网络的数据传输形式等。
- 字符更适合处理中文
不管是字节流还是字符流,其处理文件内容的基本步骤都是如下的思路:
- 要根据文件的路径创建File类的对象
- 根据字节流或者字符流的子类实例化父类对象
- 进行数据的读取或者写入操作
- 关闭流:IO操作属于资源操作,所有的资源操作都必须执行关闭操作。
2.1 字节输出流OutputStream
如果要通过程序进行内容的输出,则可以使用字节输出流OutputStream。
OutputStream是一个抽象类,实现了Closeable接口和Flushable接口,通过这两个接口,继承了他们各自的关闭方法和刷新方法。同样的,由于他是一个抽象类,必须使用它的子类进行实例化,那么就会需要针对不同的操作,实现不同的子类实例化。对于文件的操作,使用FileOutputStream进行实例化。 FileOutputStream拥有不同的构造函数,根据构造函数的不同,可以进行文件的覆盖写入和文件的追加写入设置等。
FileOutputStream的不同构造方法
- 覆盖写入: FileOutputStream(File file)
- 追加写入:+ 覆盖写入: FileOutputStream(File file,boolean append)
FileOutputStream的写入方法
- 全部写入: write(byte[] content)
- 局部写入: write(byte[] content,int start, int len)
- 写入单个字节: write(int content)
范例:使用FileOutputStream进行文件内容的写入/追加等。
/** 文件内容的写入操作范例 */
public class OutputStreamDemo {
public static void main(String[] args) throws IOException {
// 1. 确定要操作的文件
File file = new File("D:\\filewrite.txt");
// 2. 实例化一个文件类型的OutputStream
OutputStream os = new FileOutputStream(file); // 覆写
// OutputStream os = new FileOutputStream(file, true); // 追加
// 3. 写入文件
// 3.1 全部写入
String content = "this is a test String!\r\n";
os.write(content.getBytes());
// 3.2 部分写入
os.write(content.getBytes(), 0, 9);
// 3.3 写入单个字节
os.write(65);
// 4. 关闭流
os.close();
}
}
说明:
- 使用FileOutputStream进行文件操作的时候,就不在需要进行文件的创建,调用写入的方法时,会进行自动的创建。(只创建文件,如果有多级,那么目录一定要确保是存在的)
- 一定要记得关闭文件字节流。
2.2 字节输入流InputStream
InputStream是字节输入流,其使用方法和OutputStream类几乎一样。
原理是通过read方法将流对象中的数据读入的byte数组中,之后通过操作数组实现读取到的内容的操作。
import java.io.*;
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
// 1. 定位到要处理的文件
File file = new File("D:\\ProgramFiles\\kibana-5.6.2-windows-x86\\LICENSE.txt");
// 2. 使用FileInputStream类实例化InputStream对象
InputStream input = new FileInputStream(file);
// 3. 读取流的内容到字节数组中
byte[] content = new byte[1024];
int len = input.read(content);
System.out.println("读取文件内容为:【" + new String(content, 0, len) + "】");
// 4. 关闭流
input.close();
}
}
2.3 字符输出流Writer
Writer是一种字符输出流的处理类,使用方法也和OutputStream使用类似,还是使用FileWriter进行父类对象的实例化。
比较有特色的就是,可以直接向文件输出字符数组或者字符串。
范例:使用Writer类进行字符输出。
package org.liyubo.java8demos.fileops;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo {
public static void main(String[] args) throws IOException {
// 1. 定位到要操作的文件
File file = new File("D:\\test\\fileops\\testwriter.txt");
if(!file.getParentFile().exists()){ // 如果父目录不存在的话,创建父目录
file.getParentFile().mkdirs();
}
// 2. 创建Writer对象,并使用FileWriter进行实例化
Writer output = new FileWriter(file);
// 3. 执行输出,调用输出字符串或者
output.write("你好,中国!");
output.append('H');
char[] content = new char[]{'s','v'};
output.write(content);
// 4. 关闭
output.close();
}
}
2.4 字符输入流Reader
Reader类是一个字符输入类,使用方法和Writer类似,不同的是,其只有将字符输入流读入字符数组的功能,没有读入字符串的功能。
不写例子了,没啥意思。
字节输入/出流和字符输入/出流区别
其实,字符流的核心还是因为对中文操作更好,但是现在使用一些方法也能将中文使用字节流很好的处理了。 在正常的开发中,还是字节流使用更多一些。
那么,所谓的一些操作又是怎么实现的呢?
原因就在于,其实,从流到文件中间,还有一层就是内存的缓冲。在流关闭之前,内容都是缓冲在内容中的,只有通过flush()操作强制刷新,内容才会写入到文件。因此,所谓的一些操作,就是如何在内存中对流的缓冲进行操作的问题。在内存的缓冲区中,就可以进行字节流和字符流之间的相互转换。
3. 转换流
如何在缓冲区中进行字符流和字节流的互相转换呢,此处就主要涉及两个类:
- OutputStreamWriter: 将字节输出流转换成字符输出流。
- InputStreamReader:将字节输入流转换成字符输入流。
范例: 使用转换流的操作
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 1. 定位到要操作的文件
File file = new File("D:\\test\\fileops\\testwriter.txt");
if(!file.getParentFile().exists()){ // 如果父目录不存在的话,创建父目录
file.getParentFile().mkdirs();
}
OutputStream os = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(os);
// 3. 执行输出,调用输出字符串或者
writer.write("你好,中国!");
writer.append('H');
char[] content = new char[]{'s','v'};
writer.write(content);
// 4. 关闭
writer.close();
os.close();
}
}