IO(输入/输出)是比较乏味的事情,因为看不到明显的运行效果,但输入/输出是所有程序都必需的部分----使用输入机制,允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据,使用输出机制,允许程序记录允许状态,将程序数据输出到磁盘、光盘等存储设备。
使用File类访问本地文件系统 File类File类是Java.io包下代表与平台无关的文件和目录,也就是说,如果希望在程序中操作文件和目录,都可以通过File类来完成。值得指出的是,不管是文件还是目录都是使用File来操作的,File能新建、删除、重命名文件和目录,File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
访问文件和目录File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。一旦创建了File对象后,就可以调用File对象的方法来访问,File提供了很多方法来操作文件和目录。
| 方法名 | 说明 |
|---|---|
| String getName() | 返回此FIle对象所表示的文件名或路径名(如果是路径,则返回最后以及子路径名) |
| String getPath() | 返回此File对象所对应的路径名 |
| File getAbsoluteFile() | 返回此File对象的绝对路径 |
| String getAbsolutePath() | 返回此File对象所对应的绝对路径名 |
| String getParent() | 返回此File对象所对应目录(最后一级子目录)的父目录名 |
| Boolean renameTo(File newName) | 重命名此File对象所对应的文件或目录,如果重命名成功,则返回true;否则返回false |
| 方法名 | 说明 |
|---|---|
| boolean exists() | 判断File对象所对应的文件和目录是否存在 |
| boolean canWrite() | 判断File对象所对应的文件和目录是否可写 |
| boolean canRead() | 判断File对象所对应的文件和目录是否可读 |
| boolean isFile() | 判断File对象所对应的是否是文件,而不是目录 |
| boolean isDirectory | 判断File对象所对应的是否是目录,而不是文件 |
| boolean isAbsolute() | 判断File对象所对应的文件和目录是否是绝对路径。该方法消除了不同平台的差异,可以直接判断File对象是否为绝对路径。在UNIX/Linux/BSD等系统上,如果路径名开头是一条斜线(/),则表明该File对象对应一个绝对路径;在Window系统上,如果路径开头是盘符,说明它是一个绝对路径。 |
| 方法名 | 说明 |
|---|---|
| long lastModified() | 返回文件的最后修改时间 |
| long length() | 返回文件内容的长度 |
| 方法名 | 说明 |
|---|---|
| boolean createNewFile() | 当此File对象所对应的文件不存在时,该方法将新建一个该File对象所指定的新文件,如果创建成功则返回true;否则返回false |
| boolean delete() | 删除File对象所对应的文件或路径 |
| static File createTempFile(String prefix,String suffix) | 在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。prefix参数必须至少是3个字节长。建议前缀使用一个短的、有意义的字符串,比如"hjb"或"mail"。suffix参数可以为null,在这种情况下,将使用默认的后缀".tmp" |
| static File createTempFile(String prefix,String suffix,FIle directory) | 在directory所指定的目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。 |
| void deleteonExit() | 注册一个删除钩子,指定当Java虚拟机退出时,删除File对象所对应的文件和目录 |
| 方法名 | 说明 |
|---|---|
| boolean mkdir() | 试图创建一个File对象所对应的目录,如果创建成功,则返回true;否则返回false。调用该方法时File对象必须对应一个路径,而不是一个文件。 |
| String[ ] list() | 列出File对象的所有子文件名和路径名,返回String数组 |
| File[ ] listFiles() | 列出File对象的所有子文件和路径,返回File数组 |
| static File[ ] listRoots() | 列出系统所有的根路径。这是一个静态方法,可以直接通过File类来调用 |
使用文件过滤器Windows的路径分隔符使用反斜线(),而Java程序中的反斜线表示转义字符,所以如果需要在Windows的路径下包括反斜线,则应该使用两条反斜线,如F: anc test.txt,或者直接使用斜线(/)也可以,Java程序支持将斜线当成平台无关的路径分隔符
FilenameFilter接口里包含了一个accept(File dir,String name)方法,该方法将依次对指定File的所有子目录或者文件进行迭代,如果该方法返回true,则list()方法会列出该子目录或者文件。
public static void main(String[] args) {
File file = new File("D:/TestDownload");
// listFiles(FileFilter filter)
// FileFilter是过滤规则
File[] childList = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
// return true; 代表要筛选出来的
// return false; 代表不需要的
return pathname.getName().endsWith(".pdf");
}
});
for (File file2 : childList) {
System.out.println(file2);
}
}
通过实现accept()方法,实现accept()方法就是指定自己的规则,指定哪些文件应该由list()方法列出。
理解Java的IO流Java的IO流就是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述为"流"(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入/输出源。stream是从起源(source)到接受(sink)的有序数据。
流的分类 1. 输入流和输出流输入流:只能从中读取数据,而不能向其写入数据
输出流:只能向其写入数据,而不能从中读取数据
Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OuputStream和Writer作为基类。
2. 字节流和字符流字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同—字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符
字节流主要由InputStream和OutputStream作为基类,而字符流主要由Reader和Writer作为基类。
3. 节点流和处理流可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流。
----当使用节点流进行输入/输出时,程序直接连接到实际的数据源,和实际的输入/输出节点连接。
处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。处理流也被称为高级流。
----当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。
流的概念模型使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际访问的数据源也相应地发生变化。
对于InputStream和Reader而言,它们把输入设备抽象成一个"水管",这个水管里的每个"水滴"依次排列
对于OutputStream和Writer而言,它们同样把输出设备抽象成一个"水管",只是这个水管里没有任何水滴
字节流和字符流 InputStream和Reader在InputStream里包含如下三个方法
| 方法名 | 说明 |
|---|---|
| int read() | 从输入流中读取单个字节(相当于从水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型) |
| int read(byte[ ] b) | 从输入流最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数 |
| int read(byte[ ] b, int off,int Len) | 从输入流中最多读取len个字节的数据,并将其存储在数组b中,加入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数 |
在Reader里包含如下三个方法
| 方法名 | 说明 |
|---|---|
| int read() | 从输入流中读取单个字符(相当于从水管中取出一滴水),返回所读取的字符数据(字符数据可直接转换为int类型) |
| int read(byte[ ] cbuf) | 从输入流最多读取cbuf.length个字符的数据,并将其存储在字符数组b中,返回实际读取的字符数 |
| int read(byte[ ] cbuf, int off,int Len) | 从输入流中最多读取len个字符的数据,并将其存储在数组cbuf中,加入数组cbuf中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数 |
当使用数组作为read()方法的参数时,可以理解为使用一个"竹筒"到水管中去取水。read(char[ ] cbuf)方法中的数组可理解为一个"竹筒",程序每次调用输入流的read(char[ ] cbuf)或read(char[ ] b)方法,就相当于用"竹筒"从输入流中取出一筒"水滴",程序得到"竹筒"里的"水滴"后,转换为相应的数据即可。程序如何判断取水取到了最后呢?直到read(char[ ] cbuf)或read(char[ ] b)方法返回-1,即表明到了输入流的结束点。
public static void main(String[] args) {
try {
File file = new File("D:/xxxx.m4a");
Reader reader = new FileReader(file);
// length() 获取文件的字节数大小
// long size = file.length();
// char[] charArray = new char[(int) size];
// reader.read(charArray);
String str = "";
while (true) {
int i = reader.read();
if (i == -1) {
break;
}
str += (char) i;
}
System.out.println(str);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
注意!!最后一定要关闭输入流或者输出流的资源!!!
使用Java的IO流不要忘记关闭输出流,关闭输入流除了可以保证保证流的物理资源被回收外,可能还可以将输出缓冲区中的数据flush到物理节点里(因为在执行close()方法之前,自动执行输出流的flush()方法)。Java的很多输出流默认都提供了缓冲功能,其实没有必要刻意去记忆哪些流有缓冲功能、哪些流没有,只要正常关闭所有的输出流即可保证程序正常。
InputStream和Reader还支持以下几个方法来移动记录指针
| 方法名 | 说明 |
|---|---|
| void mark(int readAheadLimit) | 在记录指针当前位置记录一个标记(mark) |
| boolean markSupported() | 判断此输入流是否支持mark()操作,即是否支持记录标记 |
| void reset() | 将此流的记录指针重新定位到上一次记录标记(mark)的位置 |
| long skip(long n) | 记录指针向前移动n个字符/字节 |
OutputStream提供了三个方法
| 方法名 | 说明 |
|---|---|
| void write(int c) | 将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字符 |
| void write(byte[ ]/char[ ] buf) | 将字节数组/字符数组中的数据输出到指定输出流中 |
| void write(byte[ ]/char[ ] buf,int off,int len) | 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中 |
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。
| 方法名 | 说明 |
|---|---|
| void write(String str) | 将str字符串里包括的字符输出到指定输出流中 |
| void write(String str,int off,int len) | 将str字符串里从off位置开始,长度为len的字符输出到指定输出流中 |
| 分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
| 抽象基类 | InputStream | OutputStream | Reader | Writer |
| 访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
| 访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
| 访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
| 访问字符串 | StringReader | StringWriter | ||
| 缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
| 转换流 | InputStreamReader | OutputStreamWriter | ||
| 对象流 | ObjectInputStream | ObjectOutputStream | ||
| 抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
| 打印流 | PrintStream | PrintWriters | ||
| 推回输入流 | PushbackInputStream | PushbackReader | ||
| 特殊流 | DataInputStream | DataOutputStream |
之前有了输出/输入流的4个抽象基类,并介绍了4个访问文件的节点流的用法。会发现4个基类使用起来有些烦琐。如果希望简化编程,这就需要借助于处理流了。
处理流的用法处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。
使用处理流时的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互
实际识别处理流也很简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流一定是处理流;而所有节点流都是直接以物理IO节点作为构造器参数的
关于使用处理流的优势:1.对开发人员来说,使用处理流进行输入/输出操作更简单;2.使用处理流的执行效率更高
public class 处理流的使用 {
public static void main(String[] args) {
PrintStream p = null;
try {
p = new PrintStream(new FileOutputStream(new File("/Users/wushiwei/文件/C语言.md")));
String str = "hello world";
int i = 5;
double d = 7.9;
p.println(str);
p.println(i);
p.println(d);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
p.close();
}
}
}
在使用处理流包装了底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流
输入/输出流体系通常来说,字节流的功能比字符流的功能更强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件–但是问题是,如果使用字节流来处理文本文件,则需要使用合适的方式把这些字节转换成字符,这就增加了编程的复杂度。所以通常有个规则:
如果进行输入/输出的内容是文本内容,则应该考虑使用字符流;如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。
转换流输入/输出流体系中还提供了两个转换流,这两个转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流;OutputStreamWriter将字节输出流转换成字符输出流。
怎么没有字符流转字节流的转换流呢???想一想字节流和字符流的差别:字节流比字符流的使用范围更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说,是一个用起来更方便的流了,为什么要转换成字节流呢??反之,如果现在有一个字节流,但可以确定这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一点,所以Java只提供了将字节流转换成字符流的转换流。
这边以获取键盘输入为例来介绍转换流的用法
public class KeyinTest {
public static void main(String[] args) {
try (
// 将System.in对象转换成Reader对象
InputStreamReader reader = new InputStreamReader(System.in);
// 将普通的Reader包装成BufferedReader
BufferedReader br = new BufferedReader(reader))
{
String line = null;
// 采用循环方式来逐行读取
while ((line = br.readLine()) != null) {
if (line.equals("exit")) {
System.exit(1);
}
System.out.println("输入内容为:" + line);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上面程序将System.in包装成BufferedReader,BufferedReader流具有缓冲功能,它可以一次读取一行文本—以换行符为标志,如果它没有读到换行符,则程序阻塞,等到读到换行符为止。运行这个程序会发现,在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容
推回输入流由于BufferedReader具有一个readLine()方法,可以非常方便地一次读入一行内容,所以经常把读取文本内容的输入流包装成BufferedReader,用来方便地读取输入流的文本内容。
在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader。
| 方法名 | 说明 |
|---|---|
| void unread(byte[ ]/char[ ] buf ) | 将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容 |
| void unread(byte[ ]/char[ ] b,int off,int len) | 将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容 |
| void unread(int b ) | 将一个字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容 |
PushbackInputStream和PushbackReader的奥秘所在就是这三个方法与InputStream和Reader中的三个read()方法一一对应
这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
已知,当程序创建一个PushbackInputStream和PushbackReader时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中推回到推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow 的IOEXception异常。
虽然图中的推回缓冲区的长度看似比read()方法的数组参数的长度小,实际上,推回缓冲区的长度与read()方法的数组参数的长度没有任何关系,完全可以更大。
还未更新完



