IO(输入/输出)是每个程序都必须的部分。使用输入机制,程序可以读取到外部数据(例如来磁盘、光盘、网络等);使用输出机制,程序可以将数据输出到外部, 例如,把数据从内存写入到文件,把数据从内存输出到网络等等。
Java 的 IO 通过 java.io 包下的类和接口来支持,在java.io 包下主要包括输入、输出两种IO流,没中输入输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作
File 类
使用File 类可以操作文件和目录。需要指出的是,File 类能新建、删除、重命名文件和目录,但File 类不能访问文件内容本身。如果需要访问文件内容本身,则需要通过IO来获取
File 类可以使用文件路径字符串来创建 File 实例,该文件路径字符串既可以是绝对路径,也可以是相对路径- // 通过绝对路径创建File 类
- new File("C:\\Windows\\notepad.exe");
- // 通过相对路径来创建File类
- File f3 = new File(".\\sub\\javac");
复制代码注意Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符:
创建 File 对象后,就可以通过File对象来操作文件和目录,下面列入一些比较常用的方法
1. 访问文件名相关方法
- String getName() 返回此 File 对象所表示的文件名或路径名
- String getPath() 返回此 File 对象所对应的路径名
- File getAbsoluteFile() 返回此对象所对应的绝对路径
- String getAbsoluteFile() 返回此File 对象所对应的绝对路径名
- String getParent() 返回此 File 对象所对应目录的父目录名
- boolean renameTo(File newName) 重命名此File 对象所对应的文件或目录,如果重命名成功,则返回true,否则返回false
2. 文件检测方法
- boolean exists() 判断 File 对象所对应的文件或目录是否存在
- boolean canWrite() 判断 File 对象所对应的文件和目录是否可写
- boolean canRead() 判断 File 对象所对应的文件和目录是否刻度
- boolean isFile() 判断 File 对象所对应的是否是文件
- boolean isDirectory() 判断 File 对象所对应的是否是目录,而不是文件
- boolean isAbsolute() 判断 File 对象所对应的文件或目录是否是绝对路径
3. 获取常规文件信息
- long lastModified() 返回文件的最后修改时间
- long length() 返回文件内容的长度
4. 文件操作相关方法
- boolean createNewFile() 新建一个该 File 对象所指定的新文件,创建成功返回 true,否则返回false
- boolean delete() 删除 File 对象所对应的文件或路径
- static File createTempFile(String prefix,String suffix) 在默认的临时文件目录中创建一个临时的空文件,使用给定前缀和给定后缀作为文件名
- static File createTempFile(String prefix,String suffix,File directory) 在 directory 所指定的目录中创建一个临时的空文件,使用给定前缀和给定后缀作为文件名
- void deleteOnExit() 注册一个删除钩子,指定当 Java 虚拟机退出时,删除File对象所对应的文件和目录
5. 目录操作相关方法
- boolean mkdir() 试图创建一个 File 对象所对应的目录,如果创建成功则返回true
- String[] list() 列出 File 对象的所有子文件名和路径名
- String[] list(FilenameFilter filter) FilenameFilter 是一个函数式接口,该接口包含了一个accept(File,String name) 方法,可以通过该参数列出符合条件的文件
- File[] listFiles() 列出 File 对象所有的子文件和路径
- static File[] listRoots() 列出系统所有的根路径
- public class FileTest {
- public static void main(String[] args) throws Exception {
- // 以当前路径创建一个File 对象
- File file = new File(".");
- // 打印文件名
- System.out.println(file.getName());
- // 打印相对路径的上一级路径,由于相对路径只是一个点 所以输出null
- System.out.println(file.getParent());
- // 打印绝对路径
- System.out.println(file.getAbsolutePath());
- // 打印绝对路径的上一级路径
- System.out.println(file.getAbsoluteFile().getParent());
- // 创建一个临时文件
- File temp = File.createTempFile("aaa", ".txt", file);
- // 注册一个删除钩子,当JVM 退出时删除该文件
- temp.deleteOnExit();
- // 输出当前File 目录下所有的子文件和文件夹名
- for (String filename : file.list()) {
- System.out.print(filename + "\t");
- }
- // 换行
- System.out.println();
- // 输出系统所有的根目录
- for (File root : File.listRoots()) {
- System.out.println(root);
- }
- // 通过条件过滤出以.txt 结尾的文件
- for (String filename : file.list(((dir, name) -> name.endsWith(".txt")))) {
- System.out.print(filename + "\t");
- }
- }
- }
复制代码 输出- .
- null
- F:\yfd\java\JaveTest\.
- F:\yfd\java\JaveTest
- .idea aaa8648461841651352798.txt Annotation IODemo JDBCDemo out pom.xml
- C:\
- D:\
- E:\
- F:\
- aaa8648461841651352798.txt
复制代码 Java 的 IO 流
流的分类
按照不同的分类方式,可以将流分为不同类型
1. 输入流和输出流
- 输入流:只能从中读取数据,而不能向其写入数据,主要由 InputStream 和 Reader 作为基类
- 输出流:只能向其写入数据,而不能从中读取数据,主要由 OutputStream 和 Writer 作为基类
2. 字节流和字符流
字节流和字符流的用法几乎一样,区别在于操作的数据单元不同——字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位的字符
3. 节点流和处理流
按照流的角色来分,可以分为节点流和处理流。
可以从/向一个特定的 IO 设备 读/写 数据的流,称为节点流。处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据读/写功能
字节流和字符流
字节流和字符流的操作方式几乎一样,区别只是操作的数据单元不同
InputStream 和 Reader
InputStream 和 Reader 是所有输入流的抽象基类
InputStream 里包含如下三个方法
- int read() 从输入流中读取单个字节
- int read(byte[] b) 从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数
- int read(byte[] b,int off,int len) 从输入流中最多读取 len 个字节的数据,并将其存储在 数组b 中,放入数组b中时,是从 off 位置开始,返回实际读取的字节数
Reader 包含如下三个方法
- int read() 从输入流中读取单个字符
- int read(char[] cbuf) 从输入流中最多读取cbuf.length 个字符的数据,并将其存储在字符数组 cbuf中,返回实际读取的字符数
- int read(char[] cbuf,int,int len) 从输入流中最多读取len 个字符的数据,并将其存储在字符数组cbuf 中,防暑数组cbuf中时,是从off 位置开始,返回实际读取的字符数
InputStream 实例- public static void main(String[] args) throws IOException {
- try (
- // 创建字节输入流,IO 流实现了AutoCloseable 接口,因此可通过try语句来关闭IO流
- FileInputStream fis = new FileInputStream("test.txt");
- ) {
- // 创建一个长度128的字节数组
- byte[] data = new byte[128];
- int len = 0;
- // 向数组中装填数据
- while ((len = fis.read(data)) > 0) {
- // 将字节数组转换为字符串输入
- System.out.print((new String(data, 0, len)));
- }
- }
- }
复制代码 Reader 实例- public class ReaderTest {
- public static void main(String[] args) throws IOException {
- try (
- // 创建一个字符输入流
- FileReader fr = new FileReader("test.txt");
- ) {
- // 表示当前字符
- int data = 0;
- // 通过read() 方法循环读取流,如果到底则返回-1
- while ((data = fr.read()) != -1) {
- System.out.print((char) data);
- }
- }
- }
- }
复制代码 除此之外, InputStream 和 Reader 还支持如下几个方法来移动记录指针
- void mark(int readAheadLimit) 在记录指针当前位置记录一个标记(mark)
- boolean markSupported() 判断此输入流是否支持 mark() 操作
- void reset() 将此流的记录指针重新定位到上一次标记(mark)的位置
- long skip(long n) 记录指针向后移动 n 个字节/字符
OutputStream 和 Writer
OutputStream 和 Writer 是输出流的基类,两个流都提供了如下三个方法
- void write(int c) 将指定的 字节/字符 输出到输出流中,其中c既可以代表字节,也可以代表字符
- void write(byte[]/char[] buf) 将 字节/字符 数组中的数据输出到指定输出流中
- void write(byte[]/char[] buf,int off,int len) 将 字节/字符 数组从off 位置开始,长度为len 的 字节/字符 输出到输出流中
除此之外,Writer 还包含如下两个方法
- void write(String str) 将 str 字符串里包含的字符输出到指定输出流中
- void write(String str,int off,int len) 将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中
OutputStream 示例- public class FileOutputStreamTest {
- public static void main(String[] args) throws IOException {
- File file = new File("test2.txt");
- // 如果文件不存在则创建
- if (!file.exists()) {
- file.createNewFile();
- }
- try (
- // 创建字节输入流
- FileInputStream fis = new FileInputStream("test.txt");
- // 创建字节输出流
- FileOutputStream fos = new FileOutputStream("test2.txt");
- ) {
- byte[] buf = new byte[2];
- int readLen = 0;
- while ((readLen = fis.read(buf)) > 0) {
- // 将字节数组写入文件流
- fos.write(buf, 0, readLen);
- }
- }
- }
- }
复制代码 Writer 示例- public class FileWriterTest {
- public static void main(String[] args) throws IOException {
- try (
- FileWriter fw = new FileWriter("test.txt");
- ) {
- fw.write("锦瑟 \r\n");
- fw.write("锦瑟无端五十弦, \r\n");
- fw.write("一弦一柱思华年。 \r\n");
- fw.write("庄生晓梦迷蝴蝶, \r\n");
- fw.write("望帝春心托杜鹃。 \r\n");
- }
- }
- }
复制代码 处理流的用法
处理流隐藏了底层设备上的节点流的差异,并对外提供了更方便的输入/输出方法,让程序员只需关心高级流的操作
下面通过使用 PrintStream 处理流来包装 OutputStream,使用处理流后的输出流在输出时将更加方便- public class PrintStreamTest {
- public static void main(String[] args) throws IOException {
- try (
- FileOutputStream fos = new FileOutputStream("test.txt");
- PrintStream ps = new PrintStream(fos);
- ) {
- ps.println("Java");
- }
- }
- }
复制代码 可以看到test.txt文件的内容已经变成了 Java
IO 流的体系结构
Java IO流体系中常用的流分类
分类字节输入流字节输出流字符输入流字符输出流抽象基类InputStreamOutputStreamReaderWriter访问文件FileInputStreamFileOutputStreamFileReaderFileWriter访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter访问字符串StringReaderStringWriter缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderPipedWriter转换流InputStreamReaderOutputStreamWriter对象流ObjectInputStreamObjectOutputStream抽象基类FilerInputStreamFilterOutputStreamFilterReaderFilterWriter打印流PrintStreamPrintWriter推回输入流PushBackInputStreamPushbackReader特殊流DataInputStreamDataOutputStream转换流
IO 流体系中还提供了两个用于实现将字节流转换成字符流的转换流,其中 InputStreamReader 将字节输入流转换成字符输入流,OutputStreamWriter 将字节输出流转换成字符输出流- public class StreamTest {
- public static void main(String[] args) throws IOException {
- try (
- FileInputStream fis = new FileInputStream("test.txt");
- // 将字节流转换成字符流
- InputStreamReader isr = new InputStreamReader(fis);
- ) {
- char[] data = new char[32];
- int len = 0;
-
- while ((len = isr.read(data)) > 0) {
- System.out.println(String.valueOf(data,0,len));
- }
- }
- }
- }
复制代码 推回输入流
PushbackInputStream 和 PushbackReader都带有一个推回缓冲区,当程序调用这两个退回输入流的 unread() 方法时,系统将会吧指定数组的内容退回到缓冲区里,而退回输入流每次调用 read() 方法时总是先从退回缓冲区读取
当程序创建一个退回流时,需要指定退回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中退回的长度超出了缓冲区的大小,将会引发异常- public class PushbackTest {
- public static void main(String[] args) throws IOException {
- try (
- PushbackReader pr = new PushbackReader(new FileReader("test.txt"), 64);
- ) {
- int c = 0;
- while ((c = pr.read()) != -1) {
- System.out.print((char) c);
- }
- // test.txt 最后一个字符是 a,将此字符推回输入流
- pr.unread('a');
- // 获取缓冲区的字符
- System.out.println((char) pr.read());
- }
- }
- }
复制代码 输出读写其他进程数据
使用 Runtime 对象的 exec() 方法可以运行平台上的其他程序。Process 类提供了如下三个方法,用于让程序和其子进程进行通讯
- InputStream getErrorStream() 获取子进程的错误流
- InputStream getInputStream() 获取子进程的输入流
- OutputStream getOutputStream() 获取子进程的输出流
- public class ProcessStreamTest {
- public static void main(String[] args) throws IOException {
- Process p = Runtime.getRuntime().exec("javac");
- try (
- // 将字节流转换为字符流,再交给缓冲流(处理流)包装
- BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream(),"GBK"))
- ){
- String buff = null;
- // 循环输出 p 进程的错误输出
- while ((buff = br.readLine()) != null){
- System.out.println(buff);
- }
- }
- }
- }
复制代码 输出- 用法: javac <options> <source files>
- 其中, 可能的选项包括:
- -g 生成所有调试信息
- -g:none 不生成任何调试信息
- -g:{lines,vars,source} 只生成某些调试信息
- -nowarn 不生成任何警告
- -verbose 输出有关编译器正在执行的操作的消息
- -deprecation 输出使用已过时的 API 的源位置
- -classpath <路径> 指定查找用户类文件和注释处理程序的位置
- -cp <路径> 指定查找用户类文件和注释处理程序的位置
- -sourcepath <路径> 指定查找输入源文件的位置
- -bootclasspath <路径> 覆盖引导类文件的位置
- -extdirs <目录> 覆盖所安装扩展的位置
- -endorseddirs <目录> 覆盖签名的标准路径的位置
- -proc:{none,only} 控制是否执行注释处理和/或编译。
- -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
- -processorpath <路径> 指定查找注释处理程序的位置
- -parameters 生成元数据以用于方法参数的反射
- -d <目录> 指定放置生成的类文件的位置
- -s <目录> 指定放置生成的源文件的位置
- -h <目录> 指定放置生成的本机标头文件的位置
- -implicit:{none,class} 指定是否为隐式引用文件生成类文件
- -encoding <编码> 指定源文件使用的字符编码
- -source <发行版> 提供与指定发行版的源兼容性
- -target <发行版> 生成特定 VM 版本的类文件
- -profile <配置文件> 请确保使用的 API 在指定的配置文件中可用
- -version 版本信息
- -help 输出标准选项的提要
- -A关键字[=值] 传递给注释处理程序的选项
- -X 输出非标准选项的提要
- -J<标记> 直接将 <标记> 传递给运行时系统
- -Werror 出现警告时终止编译
- @<文件名> 从文件读取选项和文件名
- Process finished with exit code 0
复制代码 序列化
序列化机制允许将实现序列化的 Java 对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象
实现序列化
如果需要将某个对象实现序列化,则必须实现 Serializabe 接口,该接口只是一个标记接口,只是表明该类的示例是可序列化的
如果这个可序列化类中包含引用类型的成员变量,那么该成员变量也需要实现 Serializabe 接口,否则该类型的成员变量是不可序列化的,将会引发 NotSerializableException 异常
使用 transient 关键字修饰成员变量,可以指定 Java 序列化时无视该实例变量- // 课程类
- public class Course implements Serializable {
- String name;
- public Course(String name) {
- this.name = name;
- }
- ...省略getter,setter,toString 方法
- }
复制代码- // 学生类
- public class Student implements Serializable {
- String name;
- int age;
- public Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
- ...省略getter,setter,toString 方法
- }
复制代码- // 成绩类
- public class Grade implements Serializable {
- // 分数
- int grade;
- // 备注,使用 transient 使此成员变量不参与序列化
- transient String memo;
- Student student;
- Course course;
- public Grade(int grade, String memo, Student student, Course course) {
- this.grade = grade;
- this.memo = memo;
- this.student = student;
- this.course = course;
- }
- ...省略getter,setter,toString 方法
-
- }
复制代码 进行序列化对象- public class SerializeTest {
- public static void main(String[] args) throws IOException {
- Student stu = new Student("张三", 12);
- Course course = new Course("语文");
- Grade grade = new Grade(66, "备注", stu, course);
- try (
- // 1. 创建一个 ObjectOutputStream 输出流(处理流)
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
- ) {
- // 2. 调用 ObjectOutputStream 的 writeObject()方法将对象输出为可序列化对象
- oos.writeObject(grade);
- }
- }
- }
复制代码 反序列化
我们可以通过反序列化从二进制流中恢复 Java 对象- public class DeserializeTest {
- public static void main(String[] args) throws Exception {
- try (
- // 1. 创建一个 ObjectInputStream 处理流
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))
- ) {
- // 2. 调用 readObject() 读取流中的对象
- Grade grade = (Grade) ois.readObject();
- System.out.println(grade);
- }
- }
- }
复制代码 输出- Grade{grade=66, memo='null', student=Student{name='张三', age=12}, course=Course{name='语文'}}
复制代码 NIO
从 JDK 1.4 开始 ,Java 提供了一系列改进的 输入/输出处理的新功能,这些功能被统称为新 IO(New IO,简称NIO),新增了许多用于处理IO的类,这些类都被放在java.nio 包以及子包下,并且对原 java.io 包中的很多类都以 NIO 为基础进行了改写
Channel(通道)和 Buffer(缓冲)是NIO 中的两个核心对象,Channel 是对传统的IO系统的模拟,在NIO 系统中所有的数据都需要通过通道传输
Buffer 可以被理解成一个容器,它的本质是一个数组,发送到 Channel 中的所有对象都必须首先放到 Buffer 中,而从 Channel 中读取的数据也必须先放到 Buffer 中
Buffer(缓冲区)
Buffer 的主要作用就是装入数据,然后输出数据,在Buffer 中有三个重要的概念:容量(capacity)、界限(limit)和位置(position)
- 容量(capacity) 缓冲区的容量(capacity)表示该 Buffer 的最大数据容量。缓冲区的容量不可能为负值,且创建后不能改变
- 界限(limit) 第一个不应该被读出或者写入的缓冲区位置索引。就是说,位于 limit 后的数据既不可被读,也不可被写
- 位置(position) 用于指明下一个可以被读出的或者写入的缓冲区索引
除此之外,Buffer 还支持一个可选的标记(mark),Buffer 允许直接将 position 定位到该 mark 处。
这些值满足如下关系
[code]0 |