Java的IO流使用了一种装饰器设计模式,它将IO流分为底层节点流和上层处理流。本篇重点在如何访问文件与目录、如何以二进制格式和文本格式来读写数据、对象序列化机制、还有Java7的“NIO.2”。
装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
IO的方式通常分为:BIO(同步阻塞)、NIO(同步非阻塞)、AIO(异步非阻塞),文章下面会有相关介绍。
1.File类
首先我们来看一下File类,java.io.File下代表与平台无关的文件和目录,程序操作文件和目录都可以通过File类来完成,File能新建、删除、重命名文件和目录,但是不能访问文件内容本身。如果需要访问文件内容本身则需要使用输入/输出流。File的常用方法如下:
- File file = new File(".");
-
-
-
- file.getName();
- file.getParent();
- file.getAbsoluteFile();
- file.getAbsoluteFile().getParent();
-
-
-
-
- File tempFile = File.createTempFile("temp", ".txt", file);
- tempFile.deleteOnExit();
-
-
-
-
- File newFile = new File(System.currentTimeMillis() + ".txt");
- System.out.println(newFile.exists());
- newFile.createNewFile();
- System.out.println(newFile.mkdir());
2.理解Java的IO流
下来看一下IO(输入/输出)的思维导图,来全面认识下IO流:
使用处理流的思路:使用处理流包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层IO设备、文件交互。
使用处理流的好处:简单;执行效率更高。
注意:处理流的使用必须建立在其他节点流的基础之上。
Java输入/输出流体系中常用的流分类:
除了这些还有RandomAccessFile,下面将有单独介绍。
3.字符流的用法
1)在硬盘上,创建一个文件并写入一些文字数据。
首先找到一个专门用于操作文件的Writer子类对象。FileWriter。 后缀名是父类名。 前缀名是该流对象的功能。
- class FileWriterTest {
-
- public static void main(String[] args) throws IOException {
-
- FileWriter fw = new FileWriter("test.txt");
-
-
- fw.write("abcdef");
-
-
-
-
-
-
- fw.close();
- }
- }
对已有文件的数据续写
- class FileWriterTest2 {
-
- public static void main(String[] args) throws IOException {
-
-
- FileWriter fw = new FileWriter("test.txt",true);
- fw.write("halou\r\nnihaoma");
- fw.close();
- }
- }
2)文本文件的两种读取方式
- class FileReaderTest {
-
- public static void main(String[] args) throws IOException {
-
-
- FileReader fr = new FileReader("test.txt");
-
-
-
-
-
-
- int ch = 0;
- while((ch=fr.read())!=-1) {
- System.out.println("ch="+(char)ch);
- }
-
-
-
-
-
- char[] buf = new char[1024];
- int num = 0;
- while((num=fr.read(buf))!=-1) {
- System.out.println(new String(buf,0,num));
- }
-
- fr.close();
- }
- }
3)拷贝文本文件
复制的原理:其实就是将C盘下的文件数据存储到D盘的一个文件中
步骤:
在D盘创建一个文件。用于存储C盘文件中的数据;
定义读取流和C盘文件关联;
通过不断的读写完成数据存储;
关闭资源。
为了更加清晰易懂,我把复制过程用图画了出来,是不是就清楚多了呢。
- class CopyTextTest {
-
- public static void main(String[] args) {
- copy();
- }
-
- public static void copy() {
- FileWriter fw = null;
- FileReader fr = null;
- try {
- fw = new FileWriter("test_copy.txt");
- fr = new FileReader("test.java");
-
- char[] buf = new char[1024];
- int len = 0;
- while((len=fr.read(buf))!=-1) {
- fw.write(buf,0,len);
- }
- } catch (IOException e) {
- throw new RuntimeException("读写失败");
- } finally {
- if(fr!=null)
- try {
- fr.close();
- } catch (IOException e) {
- }
- if(fw!=null)
- try {
- fw.close();
- } catch (IOException e) {
- }
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
4)字符读取流缓冲区
缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须要先有流对象。字符流缓冲区中提供了一个跨平台的换行符:newLine();
字符读取流缓冲区:该缓冲区提供了一个一次读一行的方法readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
4-1)创建buffered.txt并写入数据
- class BufferedWriterTest {
- public static void main(String[] args) throws IOException {
-
- FileWriter fw = new FileWriter("buffered.txt");
-
-
-
- BufferedWriter bufw = new BufferedWriter(fw);
-
- for(int x=1; x<5; x++) {
- bufw.write("abcdef"+x);
- bufw.newLine();
- bufw.flush();
- }
-
-
-
-
- bufw.close();
- }
- }
4-2)采用字符读取流缓冲区读取数据
- class BufferedReaderTest {
-
- public static void main(String[] args) throws IOException {
-
- FileReader fr = new FileReader("buffered.txt");
-
-
- BufferedReader bufr = new BufferedReader(fr);
-
- String line = null;
- while((line=bufr.readLine())!=null) {
- System.out.print(line);
- }
-
- bufr.close();
- }
- }
5)通过缓存区复制文本文件
- class CopyTextByBuffered {
-
- public static void main(String[] args) {
- BufferedReader bufr = null;
- BufferedWriter bufw = null;
-
- try{
- bufr = new BufferedReader(new FileReader("BufferedWriterTest.java"));
- bufw = new BufferedWriter(new FileWriter("BufferedWriterTest_copy.txt"));
-
- String line = null;
- while((line=bufr.readLine())!=null) {
- bufw.write(line);
- bufw.newLine();
- bufw.flush();
- }
- } catch (IOException e) {
- throw new RuntimeException("读写失败");
- } finally {
- try {
- if(bufr!=null)
- bufr.close();
- } catch (IOException e) {
- throw new RuntimeException("读取关闭失败");
- } try {
- if(bufw!=null)
- bufw.close();
- } catch (IOException e) {
- throw new RuntimeException("写入关闭失败");
- }
- }
- }
- }
4.字节流的用法
1)字节流File读写操作-复制一张图片
想要操作图片数据,字符流就无法满足需求了,这时就要用到字节流。
复制一张图片的思路:
用字节读取流对象和图片关联;
用字节写入流对象创建一个图片文件。用于存储获取到的图片数据;
通过循环读写,完成数据的存储;
关闭资源;
- class CopyPic {
-
- public static void main(String[] args) {
- FileOutputStream fos = null;
- FileInputStream fis = null;
- try {
- fos = new FileOutputStream("d:\\pic_copy.bmp");
- fis = new FileInputStream("d:\\pic.bmp");
-
- byte[] buf = new byte[1024];
- int len = 0;
- while((len=fis.read(buf))!=-1) {
- fos.write(buf,0,len);
- }
- } catch (IOException e) {
- throw new RuntimeException("复制文件失败");
- } finally {
- try {
- if(fis!=null)
- fis.close();
- } catch (IOException e) {
- throw new RuntimeException("读取关闭失败");
- } try {
- if(fos!=null)
- fos.close();
- } catch (IOException e) {
- throw new RuntimeException("写入关闭失败");
- }
- }
- }
- }
2)通过字节流的缓冲区演示mp4的复制
- class CopyMp4 {
- public static void main(String[] args) throws IOException {
- long start = System.currentTimeMillis();
- copy();
- long end = System.currentTimeMillis();
-
- System.out.println((end-start)+"毫秒");
- }
-
-
- public static void copy()throws IOException {
- BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\video.Mp4"));
- BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\video_copy.Mp4"));
-
- int by = 0;
-
- while((by=bufis.read())!=-1){
- bufos.write(by);
- }
- bufos.close();
- bufis.close();
- }
- }
3)转换流
IO体系只提供了将字节流向字符流转换的转换流,InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字节输出流。
下面以获取键盘输入为例来介绍转换流的用法(注意:readLine是字符流BufferedReader中的方法,而键盘录入的read方法是字节流InputStream的):
- class KeyinTest {
-
- public static void main(String[] args) throws IOException {
-
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
-
-
- String line = null;
-
- while((line=br.readLine())!=null) {
- if(line.equals("exit")) {
- System.exit(1);
- }
- System.out.println("输入内容为:" + line);
- }
- br.close();
- }
- }
5.Properties的用法
Properties是hashtable的子类(它具备map集合的特点),它里面存储的key-value都是字符串,是集合和IO技术相结合的集合容器。
该对象的特点:可以用于key-value形式的配置文件,那么在加载数据时,需要数据有固定格式:key = value。
限制程序运行次数。当运行次数到达5次时,给出,请您注册的提示。并不再让该程序执行。
1)设置和获取元素
- class PropertiesTest1 {
-
- public static void main(String[] args) throws IOException {
- Properties prop = new Properties();
-
- prop.setProperty("zhangsan","80");
- prop.setProperty("lisi","90");
-
- System.out.println(prop);
- String value = prop.getProperty("lisi");
- System.out.println(value);
-
- prop.setProperty("lisi",100+"");
-
- Set<String> names = prop.stringPropertyNames();
- for(String s : names) {
- System.out.println(s+":"+prop.getProperty(s));
- }
-
-
-
-
-
-
-
- }
- }
2)演示如何将流中的数据存储到集合中
test.txt文本文件内容如下:
- #This is a Properties Test
- #11:43:59
- zhangsan=80
- lisi=90
- wangwu=95
我们开始将test.txt中的key-value数据存储到集合中
- class PropertiesDemo {
-
- public static void main(String[] args) throws IOException {
- Properties prop = new Properties();
- FileInputStream fis = new FileInputStream("test.txt");
-
-
- prop.load(fis);
- prop.setProperty("wangwu","60");
-
- System.out.println(prop);
- prop.list(System.out);
-
- fis.close();
-
-
-
-
-
-
-
-
- }
- }
6.对象序列化
序列化机制:允许把内存中的Java对象转换成字节序列(与平台无关的二进制流)
为了让某个类是可序列化的,该类必须实现Serializable和Externalizable两接口之一,Java很多类已经实现Serializable(只是一个标记接口,实现该接口无须实现任何方法)。
通常建议:JavaEE中程序的每个JavaBean类都实现Serializable。
1)使对象流实现序列化
下面程序使用ObjectOutputStream将一个对象写入磁盘文件:
- class Person implements Serializable {
- private String name;
- private int age;
-
- public Person(String name , int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public int getAge() {
- return age;
- }
- }
-
- public class WriteObject {
- public static void main(String[] args) {
- try(
-
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("personObject.txt")))
- {
- Person per = new Person("zhangsan", 500);
- oos.writeObject(per);
- }
- catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
2)反序列化
反序列化读取的仅仅是Java对象的数据,而不是Java类,采用反序列化恢复Java对象必须提供该Java对象所属类的class文件,否则引发ClassNotFoundException异常。
下面简单来实现一下反序列化:
- public class ReadObject {
- public static void main(String[] args) {
- try(
-
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("personObject.txt")))
- {
-
- Person p = (Person)ois.readObject();
- System.out.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge());
- }
- catch (Exception ex) {
- ex.printStackTrace();
- }
-
-
-
-
-
- }
- }
7.管道流
Java IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力,所以管道也可以作为数据源以及目标媒介。在Java中管道流实现了线程间的数据传送。
注意:当使用两个相关联的管道流时,务必将它们分配给不同的线程,read()方法和write()方法调用时会导致流阻塞,这意味着如果你尝试在一个线程中同时进行读和写,可能会导致线程死锁。
- class Read implements Runnable {
- private PipedInputStream in;
- Read(PipedInputStream in) {
- this.in = in;
- }
- public void run() {
- try {
- byte[] buf = new byte[1024];
- System.out.println("读取前..没有数据阻塞");
-
- int len = in.read(buf);
- System.out.println("读到数据..阻塞结束");
-
- String s= new String(buf,0,len);
- System.out.println(s);
- in.close();
- } catch (IOException e) {
- throw new RuntimeException("管道读取流失败");
- }
- }
- }
-
- class Write implements Runnable {
- private PipedOutputStream out;
- Write(PipedOutputStream out) {
- this.out = out;
- }
- public void run() {
- try {
- System.out.println("开始写入数据,等待5秒后..");
- Thread.sleep(5000);
- out.write("piped is here".getBytes());
- out.close();
- } catch (Exception e) {
- throw new RuntimeException("管道输出流失败");
- }
- }
- }
-
- class PipedStreamTest {
- public static void main(String[] args) throws IOException {
-
- PipedInputStream in = new PipedInputStream();
- PipedOutputStream out = new PipedOutputStream();
- in.connect(out);
-
- Read r = new Read(in);
- Write w = new Write(out);
-
- new Thread(r).start();
- new Thread(w).start();
- }
-
-
-
-
-
-
-
- }
8.RandomAccessFile
RandomAccessFile(该类不是算是IO体系中子类,而是直接继承自Object,但是它是IO包中成员)是Java IO体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,既可以读取文件内容,也可以向文件输出数据。与普通IO流不同的是RandomAccessFile支持“随机访问的方式”(内部封装了一个数组,而且通过指针对数组的元素进行操作,可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置),程序可以直接跳转到文件的任意地方来读写数据。所以,如果只需要访问文件部分内容,使用RandomAccessFile是更好的选择。
RandomAccessFile完成读写的原理就是内部封装了字节输入流和输出流。
- class RandomAccessFileTest {
-
- public static void main(String[] args) throws IOException {
- writeFile();
- readFile();
- System.out.println(Integer.toBinaryString(258));
- }
-
- public static void readFile()throws IOException {
- RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
-
-
-
- raf.skipBytes(8);
- byte[] buf = new byte[4];
- raf.read(buf);
-
- String name = new String(buf);
- int age = raf.readInt();
-
- System.out.println("name="+name);
- System.out.println("age="+age);
- raf.close();
- }
-
- public static void writeFile()throws IOException {
- RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
- raf.seek(8*1);
- System.out.println("当前指针的位置是:"+raf.getFilePointer());
- raf.write("张三".getBytes());
- raf.writeInt(97);
- raf.close();
- }
-
-
-
-
-
-
-
- }
9.NIO(同步非阻塞)
前面介绍的BufferedReader时提到一个特征,当BufferedReader读取输入流中的数据时,如果没有读到有效数据,程序将会在此处阻塞该进程的执行(使用InputStream的read()方法从流中读取数据时,如果数据源没有数据,它也会阻塞该线程),也就是说前面介绍的输入流、输出流都是阻塞式的输入、输出。
从JDK1.4开始,Java提供了很多改进IO的新功能,称为新IO(New IO,简称NIO),新增了许多用于处理输入/输出的类(这些类在java.nio包及其子包下)。
从Java7开始,对NIO进行了重大改进,改进主要包括:提供了全面的文件IO和文件系统访问支持;基于异步Channel的IO。
Java7把这种改进称为NIO.2。
10.AIO(异步非阻塞)
从Java7开始,Java增加了AIO新特性,基本上所有的Java服务器都重写了自己的网络框架以通过NIO来提高服务器的性能。目前很多的网络框架(如Mina),大型软件(如Oracle DB)都宣布自己已经在新版本中支持了AIO的特性以提高性能。
下面就来看一下AIO的基本原理:
AIO主要是针对进程在调用IO获取外部数据时,是否阻塞调用进程而言的,一个进程的IO调用步骤大致如下:
1)进程向操作系统请求数据;2)操作系统把外部数据加载到内核的缓冲区中;3)操作系统把内核的缓冲区拷贝到进程的缓冲区,进程获得数据完成自己的功能 。
当操作系统在把外部数据放到进程缓冲区的这段时间(即第2、3步),如果应用进程是挂起等待状态,那么就是同步IO,反之,就是异步IO,也就是AIO。