1、概述
Java 的IO通过java.io 包下的类和接口来支持,java.io包下主要包括输入、输出两种流。每种输入输出流又可分为字节流和字符流两大类。
字节流以字节为单位处理输入、输出操作;
字符流以字符来处理输入、输出操作。
2、File类
File类代表与平台无关的文件和目录,他可以操作文件或目录,比如 File能新建、删除、重命名文件和目录,File类不能访问文件本身。如果要访问文件本身,则使用输入/输出流。
下面测试一下File类的功能。
/**
* Created by 杨Sir on 2017/10/30.
* 该类测试了 File类的常用方法
*/
public class FileTest {
public static void main(String[] args) throws IOException {
//以当前路径来创建一个File对象
File file2 = new File(".");
//直接获取文件名
System.out.println(file2.getName());
//获取文件相对路径的父路径,可能出错,下面返回 null
System.out.println(file2.getParent());
//获取绝对路径
System.out.println(file2.getAbsoluteFile());
//获取上一级路径
System.out.println(file2.getAbsoluteFile().getParent());
System.out.println("-------------------------------------------");
//以指定路径来创建一个File对象,例如项目中的 file/file2 文件夹为指定路径
File file0 = new File("file/file2");
/**
* 在指定的路径下创建一个临时文件,,文件存放位置 file0对象对应的路径下
* 使用前缀,系统随机生成的随机数和给定的后缀 作为文件名。 这是一个静态方法
*/
File tmpFile = File.createTempFile("aaa",".txt",file0);
//指定JVM退出时删除该文件,钩子方法
tmpFile.deleteOnExit();
System.out.println("tmpFile 被删除...");
System.out.println("-------------------------------------------");
//以系统当前时间为新文件名来创建新文件
File newFile = new File(System.currentTimeMillis() + "");
System.out.println("newFile对象是否存在: "+ newFile.exists()); //返回false
//以指定的newFile对象来创建一个文件 ,文件位置在项目名下
newFile.createNewFile();
//以newFile对象创建一个目录,因为newFile已存在,因此下面方法返回false,无法创建
System.out.println(newFile.mkdir());
/**
* 使用list方法列出当前路径下所有文件和路径
* 项目名下的所有文件及路径(不包括路径的子路径)
*/
String[] fileList = file2.list();
for(String fileName : fileList){
System.out.println(fileName);
}
System.out.println("-----------------------");
//listRoots()静态方法列出所有的磁盘根路径: C:\ D:\ E:\ F:\
File[] roots = File.listRoots();
for(File file : roots){
System.out.println(file);
}
}
}
需要注意的是:windows的路径分割符为反斜线(\), 而Java中反斜线是转义字符,因此windows下应该写两条反斜线(\),例如 F:\abc\test.txt。或者使用斜线(/),java支持将斜线当做平台无关的路径分割符。
File类的list方法可以接收一个FilenameFilter参数,用于文件过滤。这里的FilenameFilter接口是一个函数式接口,里面包含一个accept(File dir, String name)方法。举例如下:
/**
* Created by 杨Sir on 2017/10/30.
* 文件过滤器的使用
*/
public class FilenameFilterTest {
public static void main(String[] args){
File file = new File(".");
/**
* 使用Lambda表达式实现文件过滤器
* accept方法将对指定File的所有 子目录或文件进行迭代,如果方法返回true,则list()会列出该子目录或文件
* 如果文件以 .java结尾或文件对应一个路径,则返回true
*/
String[] nameList = file.list(((dir, name) -> name.endsWith(".java") || new File(name).isDirectory()));
//当前路径(创建nameList对象的路径)下所有的.java文件和文件夹将被输出
for (String name : nameList){
System.out.println(name);
}
}
}
3、理解 Java 的 IO 流
3.1 Java的 io流是实现输入输出的基础,Java把不同的输入输出源抽象为流。正因为流的抽象,才可以通过一致的IO代码去读写不同的IO流节点。
流的分类:
1.从内存的角度来看
– 输入流:只能从中读取数据,而不能向其写入数据
– 输出流:只能向其写入数据,而不能从中读取数据
java的输入流主要由 InputStream 和 Reader作为基类,而输出流主要以 OutputStream 和 Writer作为基类,这些基类无法创建实例。
2. 字节流和字符流
字节流和字符流的区别在于所操作的数据单元不同,字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位的字符。
字节流主要由 InputStream 和 OutputStream作为基类,字符流主要有 Reader 和 Writer作为基类。如下图:
3. 节点流和处理流(按流的角色分):
从/向 一个特定的IO设备(磁盘、网络)读/写数据的流,称为节点流,当使用节点流时,程序直接连到实际的数据源,和实际的输入/输出节点连接。
处理流用于对一个已存在的流进行连接或封装,通过封装后的流实现读/写功能。
使用处理流的好处是:只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码访问不同的数据源,随着处理流包装节点流的变化,程序实际访问的数据源也在变化。Java用处理流包装节点流是一个典型的装饰器模式。
3.2 字节流和字符流
我们先来看看InputStream 和 Reader
InputStream 和 Reader是所有输入流的抽象基类,他们是所有输入流的模板,提供通用方法。
①. InputStream包含三个方法:
int read(): 从输入流读取单个字节,返回读取的字节数据,字节数据可直接转换为 int类型。
int read(byte[] b): 从输入流中最多读取 b.length 个字节的数据,并将其存入数组b中,返回实际读取的字节数。
int read(byte[] b, int off, int len): 从输入流中最多读取 len 个字节的数据,并将其存入数组b中,从off位置开始读,返回实际读取的字节数。
②. Reader包含的三个方法:
int read(): 从输入流读取单个字符,返回读取的字符数据,字符数据可直接转换为 int类型。
int read(char[] b): 从输入流中最多读取 b.length 个字符的数据,并将其存入数组b中,返回实际读取的字符数。
int read(char[] b, int off, int len): 从输入流中最多读取 len 个字符的数据,并将其存入数组b中,从off位置开始读,返回实际读取的字符数。
要注意:当read(char[] c) 或 read(byte[] b)方法返回 -1,表明到了输入流的结束点。
前面说过,InputStream 和 Reader都不能创建实例,那怎么办呢?没关系,他们各自提供了一个用于读取文件的输入流:FileInputStream 和 FileReader,他们都是节点流,会直接和指定文件关联。 例如使用FileInputStream 来读取文件本身数据。
/**
* Created by 杨Sir on 2017/10/30.
* 通过FileInputStream 来读取文件数据
* 采用字节读取,注释可能会乱码,因为文本保存时采用GBK编码,一个中文字符占两个字节,如果read只读到半个字节,就会乱码。
*/
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
//创建字节输入流,相当于从管道中取数据,,绝对路径
FileInputStream fis = new FileInputStream("D:\\绝对路径\\FileInputStreamTest.java");
//相对路径
// FileInputStream fis = new FileInputStream(".\\src\\相对路径\\FileInputStreamTest.java");
//创建一个长为1024个字节的管道
byte[] buf = new byte[1024];
//用于保存实际读取的字节数
int hasRead = 0;
//读取1024字节之后会进行输出,然后再次读取时,读取剩余的字节,在输出,直到没有一个能被读取的字节,会返回-1。
while ((hasRead = fis.read(buf)) > 0){
//取出字节,将字节数组转换成字符串数输入
System.out.println(new String(buf,0,hasRead));
}
//关闭文件输入流,放在 finally中更安全
fis.close();
}
}
使用FileReader 来读取文件本身数据:
/**
* Created by 杨Sir on 2017/10/30.
*/
public class FileReaderTest {
public static void main(String[] args){
try(
//创建字符输入流, JDK 1.7以后,这样写,系统自动关闭流
FileReader fr = new FileReader("D:\\绝对路径\\FileReaderTest.java");
){
char[] cbuf = new char[32]; //这时需要多次调用read()读取
//保存实际读取的字符数
int hasRead = 0;
while((hasRead = fr.read(cbuf)) > 0){
System.out.println(new String(cbuf, 0, hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
除此之外,InputStream 和 Reader还支持如下几个方法移动指针:
void mark(int readAheadLimit): 在记录指针当前位置记录一个标记。
boolean markSupported() : 判断此流是否支持 mark()操作,即是否支持记录标记。
void reset(): 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
long skip(long n): 记录指针向前移动 n个字符/字节。
我们在来看 OutputStream 和 Writer
他们各自提供了一个用于写入文件的输入流:FileOutputStream 和 FileWriter,他们都是节点流,会直接和指定文件关联。
其实OutputStream 和 Writer 也非常相似, 他们共同提供了如下三个方法:
void write(int c): 将指定的字节或字符输出到输出流中。
void write(byte[]/char[] buf): 将字节数组或字符数组中的数据输出到指定输出流中。
void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组中从off位置开始,长度为len的字节或字符输出到输出流中。
因为字符流可以直接额以字符做操作单位,所以Writer可以用字符串代替字符数组。因此Writer 里还有两个方法:
void write(String str): 将字符串中的字符输出到输出流中。
void wirte(String str , int off ,int len): 将字符串中off开始,长度为len的字符输出到输出流中。
下面举例 利用FileInputStream输入,并使用 FileOutputStream执行输出,实现复制FileOutputStreamTest的功能。
/**
* Created by 杨Sir on 2017/10/30.
* 利用FileInputStream输入,并使用 FileOutputStream执行输出,实现复制FileOutputStreamTest的功能
*/
public class FileOutputStreamTest {
public static void main(String[] args){
try(
//创建字节输入流
FileInputStream fis =
new FileInputStream("D:\\绝对路径\\FileOutputStreamTest.java");
//创建字节输出流
FileOutputStream fos = new FileOutputStream("FileOutputStreamTest.txt")
) {
byte[] buf = new byte[1024];
int hashead = 0;
//循环取数据
while ((hashead = fis.read(buf)) > 0) {
//每读取一次,写入一次,读多少,写多少,,可以看到当前路径下多了一个 FileOutputStreamTest.txt文件
fos.write(buf, 0, hashead);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
如果希望直接输出字符串内容,使用Writer会更好:
/**
* Created by 杨Sir on 2017/10/30.
* 当前路径下会输出一个 FileWriterTest.txt 文件。
*/
public class FileWriterTest {
public static void main(String[] args){
try (
//创建字符输出流
FileWriter fw = new FileWriter("FileWriterTest.txt"))
{
// "\r\n"是windows的换行符,UNIX/Linux/BSD等平台,使用"\n"换行
fw.write("锦瑟 - 李商隐\r\n");
fw.write("锦瑟无端五十弦,一悬疑蛛丝年华。\r\n");
fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上介绍了4个访问文件的节点流的用法,4个基类使用优点繁琐。如果希望简化编程,就需要借助处理流了。处理流后面在更新。。。