Java IO

打印 上一主题 下一主题

主题 891|帖子 891|积分 2673

IO(输入/输出)是每个程序都必须的部分。使用输入机制,程序可以读取到外部数据(例如来磁盘、光盘、网络等);使用输出机制,程序可以将数据输出到外部, 例如,把数据从内存写入到文件,把数据从内存输出到网络等等。
Java 的 IO 通过 java.io 包下的类和接口来支持,在java.io 包下主要包括输入、输出两种IO流,没中输入输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作
File 类

使用File 类可以操作文件和目录。需要指出的是,File 类能新建、删除、重命名文件和目录,但File 类不能访问文件内容本身。如果需要访问文件内容本身,则需要通过IO来获取
File 类可以使用文件路径字符串来创建 File 实例,该文件路径字符串既可以是绝对路径,也可以是相对路径
  1. // 通过绝对路径创建File 类
  2. new File("C:\\Windows\\notepad.exe");
  3. // 通过相对路径来创建File类
  4. 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() 列出系统所有的根路径
  1. public class FileTest {
  2.     public static void main(String[] args) throws Exception {
  3.         // 以当前路径创建一个File 对象
  4.         File file = new File(".");
  5.         // 打印文件名
  6.         System.out.println(file.getName());
  7.         // 打印相对路径的上一级路径,由于相对路径只是一个点 所以输出null
  8.         System.out.println(file.getParent());
  9.         // 打印绝对路径
  10.         System.out.println(file.getAbsolutePath());
  11.         // 打印绝对路径的上一级路径
  12.         System.out.println(file.getAbsoluteFile().getParent());
  13.         // 创建一个临时文件
  14.         File temp = File.createTempFile("aaa", ".txt", file);
  15.         // 注册一个删除钩子,当JVM 退出时删除该文件
  16.         temp.deleteOnExit();
  17.         // 输出当前File 目录下所有的子文件和文件夹名
  18.         for (String filename : file.list()) {
  19.             System.out.print(filename + "\t");
  20.         }
  21.         // 换行
  22.         System.out.println();
  23.         // 输出系统所有的根目录
  24.         for (File root : File.listRoots()) {
  25.             System.out.println(root);
  26.         }
  27.         // 通过条件过滤出以.txt 结尾的文件
  28.         for (String filename : file.list(((dir, name) -> name.endsWith(".txt")))) {
  29.             System.out.print(filename + "\t");
  30.         }
  31.     }
  32. }
复制代码
输出
  1. .
  2. null
  3. F:\yfd\java\JaveTest\.
  4. F:\yfd\java\JaveTest
  5. .idea        aaa8648461841651352798.txt        Annotation        IODemo        JDBCDemo        out        pom.xml       
  6. C:\
  7. D:\
  8. E:\
  9. F:\
  10. 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 实例
  1. public static void main(String[] args) throws IOException {
  2.     try (
  3.         // 创建字节输入流,IO 流实现了AutoCloseable 接口,因此可通过try语句来关闭IO流
  4.         FileInputStream fis = new FileInputStream("test.txt");
  5.     ) {
  6.         // 创建一个长度128的字节数组
  7.         byte[] data = new byte[128];
  8.         int len = 0;
  9.         // 向数组中装填数据
  10.         while ((len = fis.read(data)) > 0) {
  11.             // 将字节数组转换为字符串输入
  12.             System.out.print((new String(data, 0, len)));
  13.         }
  14.     }
  15. }
复制代码
Reader 实例
  1. public class ReaderTest {
  2.     public static void main(String[] args) throws IOException {
  3.         try (
  4.                     // 创建一个字符输入流
  5.                 FileReader fr = new FileReader("test.txt");
  6.         ) {
  7.             // 表示当前字符
  8.             int data = 0;
  9.             // 通过read() 方法循环读取流,如果到底则返回-1
  10.             while ((data = fr.read()) != -1) {
  11.                 System.out.print((char) data);
  12.             }
  13.         }
  14.     }
  15. }
复制代码
除此之外, 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 示例
  1. public class FileOutputStreamTest {
  2.     public static void main(String[] args) throws IOException {
  3.         File file = new File("test2.txt");
  4.         // 如果文件不存在则创建
  5.         if (!file.exists()) {
  6.             file.createNewFile();
  7.         }
  8.         try (
  9.                 // 创建字节输入流
  10.                 FileInputStream fis = new FileInputStream("test.txt");
  11.                 // 创建字节输出流
  12.                 FileOutputStream fos = new FileOutputStream("test2.txt");
  13.         ) {
  14.             byte[] buf = new byte[2];
  15.             int readLen = 0;
  16.             while ((readLen = fis.read(buf)) > 0) {
  17.                 // 将字节数组写入文件流
  18.                 fos.write(buf, 0, readLen);
  19.             }
  20.         }
  21.     }
  22. }
复制代码
Writer 示例
  1. public class FileWriterTest {
  2.     public static void main(String[] args) throws IOException {
  3.         try (
  4.                 FileWriter fw = new FileWriter("test.txt");
  5.         ) {
  6.             fw.write("锦瑟 \r\n");
  7.             fw.write("锦瑟无端五十弦, \r\n");
  8.             fw.write("一弦一柱思华年。 \r\n");
  9.             fw.write("庄生晓梦迷蝴蝶, \r\n");
  10.             fw.write("望帝春心托杜鹃。 \r\n");
  11.         }
  12.     }
  13. }
复制代码
处理流的用法

处理流隐藏了底层设备上的节点流的差异,并对外提供了更方便的输入/输出方法,让程序员只需关心高级流的操作
下面通过使用 PrintStream 处理流来包装 OutputStream,使用处理流后的输出流在输出时将更加方便
  1. public class PrintStreamTest {
  2.     public static void main(String[] args) throws IOException {
  3.         try (
  4.                 FileOutputStream fos = new FileOutputStream("test.txt");
  5.                 PrintStream ps = new PrintStream(fos);
  6.         ) {
  7.             ps.println("Java");
  8.         }
  9.     }
  10. }
复制代码
可以看到test.txt文件的内容已经变成了 Java
IO 流的体系结构

Java IO流体系中常用的流分类
分类字节输入流字节输出流字符输入流字符输出流抽象基类InputStreamOutputStreamReaderWriter访问文件FileInputStreamFileOutputStreamFileReaderFileWriter访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter访问字符串StringReaderStringWriter缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderPipedWriter转换流InputStreamReaderOutputStreamWriter对象流ObjectInputStreamObjectOutputStream抽象基类FilerInputStreamFilterOutputStreamFilterReaderFilterWriter打印流PrintStreamPrintWriter推回输入流PushBackInputStreamPushbackReader特殊流DataInputStreamDataOutputStream转换流

IO 流体系中还提供了两个用于实现将字节流转换成字符流的转换流,其中 InputStreamReader 将字节输入流转换成字符输入流,OutputStreamWriter 将字节输出流转换成字符输出流
  1. public class StreamTest {
  2.     public static void main(String[] args) throws IOException {
  3.         try (
  4.                 FileInputStream fis = new FileInputStream("test.txt");
  5.                 // 将字节流转换成字符流
  6.                 InputStreamReader isr = new InputStreamReader(fis);
  7.         ) {
  8.             char[] data = new char[32];
  9.             int len = 0;
  10.             
  11.             while ((len = isr.read(data)) > 0) {
  12.                 System.out.println(String.valueOf(data,0,len));
  13.             }
  14.         }
  15.     }
  16. }
复制代码
推回输入流

PushbackInputStream 和 PushbackReader都带有一个推回缓冲区,当程序调用这两个退回输入流的 unread() 方法时,系统将会吧指定数组的内容退回到缓冲区里,而退回输入流每次调用 read() 方法时总是先从退回缓冲区读取
当程序创建一个退回流时,需要指定退回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中退回的长度超出了缓冲区的大小,将会引发异常
  1. public class PushbackTest {
  2.     public static void main(String[] args) throws IOException {
  3.         try (
  4.                 PushbackReader pr = new PushbackReader(new FileReader("test.txt"), 64);
  5.         ) {
  6.             int c = 0;
  7.             while ((c = pr.read()) != -1) {
  8.                 System.out.print((char) c);
  9.             }
  10.             // test.txt 最后一个字符是 a,将此字符推回输入流
  11.             pr.unread('a');
  12.             // 获取缓冲区的字符
  13.             System.out.println((char) pr.read());
  14.         }
  15.     }
  16. }
复制代码
输出
  1. Javaa
复制代码
读写其他进程数据

使用 Runtime 对象的 exec() 方法可以运行平台上的其他程序。Process 类提供了如下三个方法,用于让程序和其子进程进行通讯

  • InputStream getErrorStream() 获取子进程的错误流
  • InputStream getInputStream() 获取子进程的输入流
  • OutputStream getOutputStream() 获取子进程的输出流
  1. public class ProcessStreamTest {
  2.     public static void main(String[] args) throws IOException {
  3.         Process p = Runtime.getRuntime().exec("javac");
  4.         try (
  5.                 // 将字节流转换为字符流,再交给缓冲流(处理流)包装
  6.                 BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream(),"GBK"))
  7.         ){
  8.             String buff = null;
  9.             // 循环输出 p 进程的错误输出
  10.             while ((buff = br.readLine()) != null){
  11.                 System.out.println(buff);
  12.             }
  13.         }
  14.     }
  15. }
复制代码
输出
  1. 用法: javac <options> <source files>
  2. 其中, 可能的选项包括:
  3.   -g                         生成所有调试信息
  4.   -g:none                    不生成任何调试信息
  5.   -g:{lines,vars,source}     只生成某些调试信息
  6.   -nowarn                    不生成任何警告
  7.   -verbose                   输出有关编译器正在执行的操作的消息
  8.   -deprecation               输出使用已过时的 API 的源位置
  9.   -classpath <路径>            指定查找用户类文件和注释处理程序的位置
  10.   -cp <路径>                   指定查找用户类文件和注释处理程序的位置
  11.   -sourcepath <路径>           指定查找输入源文件的位置
  12.   -bootclasspath <路径>        覆盖引导类文件的位置
  13.   -extdirs <目录>              覆盖所安装扩展的位置
  14.   -endorseddirs <目录>         覆盖签名的标准路径的位置
  15.   -proc:{none,only}          控制是否执行注释处理和/或编译。
  16.   -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
  17.   -processorpath <路径>        指定查找注释处理程序的位置
  18.   -parameters                生成元数据以用于方法参数的反射
  19.   -d <目录>                    指定放置生成的类文件的位置
  20.   -s <目录>                    指定放置生成的源文件的位置
  21.   -h <目录>                    指定放置生成的本机标头文件的位置
  22.   -implicit:{none,class}     指定是否为隐式引用文件生成类文件
  23.   -encoding <编码>             指定源文件使用的字符编码
  24.   -source <发行版>              提供与指定发行版的源兼容性
  25.   -target <发行版>              生成特定 VM 版本的类文件
  26.   -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
  27.   -version                   版本信息
  28.   -help                      输出标准选项的提要
  29.   -A关键字[=值]                  传递给注释处理程序的选项
  30.   -X                         输出非标准选项的提要
  31.   -J<标记>                     直接将 <标记> 传递给运行时系统
  32.   -Werror                    出现警告时终止编译
  33.   @<文件名>                     从文件读取选项和文件名
  34. Process finished with exit code 0
复制代码
序列化

序列化机制允许将实现序列化的 Java 对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象
实现序列化

如果需要将某个对象实现序列化,则必须实现 Serializabe 接口,该接口只是一个标记接口,只是表明该类的示例是可序列化的
如果这个可序列化类中包含引用类型的成员变量,那么该成员变量也需要实现 Serializabe 接口,否则该类型的成员变量是不可序列化的,将会引发 NotSerializableException 异常
使用 transient 关键字修饰成员变量,可以指定 Java 序列化时无视该实例变量
  1. // 课程类
  2. public class Course implements Serializable {
  3.     String name;
  4.     public Course(String name) {
  5.         this.name = name;
  6.     }
  7.    ...省略getter,setter,toString 方法
  8. }
复制代码
  1. // 学生类
  2. public class Student implements Serializable {
  3.     String name;
  4.     int age;
  5.     public Student(String name, int age) {
  6.         this.name = name;
  7.         this.age = age;
  8.     }
  9.     ...省略getter,setter,toString 方法
  10. }
复制代码
  1. // 成绩类
  2. public class Grade implements Serializable {
  3.     // 分数
  4.     int grade;
  5.     // 备注,使用 transient 使此成员变量不参与序列化
  6.     transient String memo;
  7.     Student student;
  8.     Course course;
  9.     public Grade(int grade, String memo, Student student, Course course) {
  10.         this.grade = grade;
  11.         this.memo = memo;
  12.         this.student = student;
  13.         this.course = course;
  14.     }
  15.         ...省略getter,setter,toString 方法
  16.    
  17. }
复制代码
进行序列化对象
  1. public class SerializeTest {
  2.     public static void main(String[] args) throws IOException {
  3.         Student stu = new Student("张三", 12);
  4.         Course course = new Course("语文");
  5.         Grade grade = new Grade(66, "备注", stu, course);
  6.         try (
  7.                 // 1. 创建一个 ObjectOutputStream 输出流(处理流)
  8.                 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
  9.         ) {
  10.              // 2. 调用 ObjectOutputStream 的 writeObject()方法将对象输出为可序列化对象
  11.              oos.writeObject(grade);
  12.         }
  13.     }
  14. }
复制代码
反序列化

我们可以通过反序列化从二进制流中恢复 Java 对象
  1. public class DeserializeTest {
  2.     public static void main(String[] args) throws Exception {
  3.         try (
  4.                 // 1. 创建一个 ObjectInputStream 处理流
  5.                 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))
  6.         ) {
  7.             // 2. 调用 readObject() 读取流中的对象
  8.             Grade grade = (Grade) ois.readObject();
  9.             System.out.println(grade);
  10.         }
  11.     }
  12. }
复制代码
输出
  1. 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
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表