**概述
**
Java中输入、输出的处理通过java.io包下的类和接口来支持,在这个包下主要包括输入、输出两种IO流,每种输入、输出流又可以分为字节流和字符流。字节流以字节为单位来处理输入输出,字符流则以字符为单位。除此之外,Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流。节点流用于和底层物理存储节点直接关联,不同物理节点获取节点流的方式可能存在一定差异,但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用统一的输入、输出代码来读取不同物理存储节点的资源。
● 输入流和输出流 (按照数据流的方向)
Java的输入流主要由InputStream和Reader作为基类,输出流主要由OutputStream和Writer作为基类。
● 字节流和字符流 (按照处理数据的单位不同)
字节流和字符流的用法几乎完全一致,区别在于它们所操作的数据单元不同,字节流(8位)、字符流(16位),字节流主要由InputStream和OutputStream作为基类,字符流主
要由Reader和Writer作为基类。
● 节点流和包装流(处理流) (按照功能的不同)
从/向一个特定的I/0设备(磁盘、网络等)读写数据的流称为节点流,也常被称为低级流。
处理流则对于一个已存在的节点流进行连接或封装,常被称为高级流(装饰器设计模式)。
处理流的功能主要体现在:
1、性能的提高:主要以增加缓冲的方式来提高输入/输出的效率
2、操作的便捷:提供了系列便捷的方法来一次输入/输出大批量内容
JDK 1.4以后,Java在java.nio包下提供了系列的全新API,这就是Java新IO(new io)。用以更高效的进行输入、输出操作的处理。
File类
public class File extends Object
implements Serializable, Comparable<File>,文件和目录路径名的抽象表示形式。
File类是java.io包下代表与平台无关的文件和目录,在程序中用来操作文件和目录。使用文件路径字符串来创建File示例,该文件路径字符串既可以是绝对路径,也可以是相对路径。File能新建、删除和重命名文件和目录,但是不能访问文件内容本身。访问文件内容本身的操作需要使用IO流。
package cn.nevo.io; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class DirList2 { public static void main(String[] args) { //必须要保证父目录hello的存在 File path = new File("c:\\hello\\hello.txt"); if(!path.getParentFile().exists()) { path.getParentFile().mkdir(); } if(!path.exists()) { try { path.createNewFile(); System.out.println("文件创建成功!"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } byte[] s = new byte[]{'h', 'e', 'l', 'l', 'o'}; try { OutputStream out = new FileOutputStream(path); out.write(s); } catch (Exception e) { e.printStackTrace(); } } }
实现FilenameFilter接口的类实例用于过滤文件名,在File的list方法中可以接收一个FilenameFilter参数,用于列出符合条件的文件。
package java.io; public interface FilenameFilter { boolean accept(File dir, String name); }
示例:目录列表器
package cn.nevo.io; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; //查看目录列表 public class DirList { public static void main(String[] args) { //"/"代表该资源所处的根目录,此处为F:\ //"."代表该资源所在项目所处目录,此处为F:\workspace\JavaIoTest\. File path = new File("."); //返回此抽象路径名的绝对路径名字符串。 System.out.println(path.getAbsolutePath()); String[] list; //没有指定运行参数,获取File对象包含的全部列表 if(args.length == 0) { //list()方法返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 list = path.list(); } //获取受限制列表 else { //list(FilenameFilter filter)返回满足指定过滤器条件的目录和文件列表 //args:".*\.project" //list方法会为此目录对象下的每个文件名调用accept方法,来判断该文件是否包含再内 list = path.list(new DirFilter(args[0])); } //对查询结果按字母排序 Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); //输出数组列表 for(String dirItem : list) { System.out.println(dirItem); } } } //实现FilenameFilter接口的类实例可用于过滤文件名 class DirFilter implements FilenameFilter { private Pattern pattern; public DirFilter(String regex) { //compile(String regex)将给定的正则表达式编译到模式中。 this.pattern = Pattern.compile(regex); } @Override public boolean accept(File dir, String name) { //创建匹配给定输入与此模式的匹配器,尝试将整个区域与模式匹配。 //当且仅当整个区域序列匹配此匹配器的模式时才返回 true boolean flag = pattern.matcher(name).matches(); System.out.println(flag); return flag; } }
不指定参数运行情况:
F:\workspace\JavaIoTest\.
.classpath
.project
.settings
bin
src
指定参数运行(显示所有后缀名为.project的文件名)
F:\workspace\JavaIoTest\.
false
true
false
false
false
.project
list方法会为此目录对象下的每个资源调用accept方法,来判断该文件是否包含在内。
采用内部类修改上述示例:
package cn.nevo.io; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; public class DirList3 { public static void main(final String[] args) { File path = new File("."); String[] list; if(args.length == 0) { list = path.list(); } else { list = path.list(new FilenameFilter() { //参数args必须是final的,内部类要求 //pattern知道匹配模式 private Pattern pattern = Pattern.compile(args[0]); @Override public boolean accept(File dir, String name) { return pattern.matcher(name).matches(); } }); } Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); for(String dirItem : list) { System.out.println(dirItem); } } }
目录的检查及创建
File类不仅仅只代表存在的文件或目录,还可以用来创建新的目录或尚不存在的整个目录路径。还可以查看文件特性(如大小、读/写),检查某个File对象代表的是一个文件还是一个目录,并删除。注意查看API文档。
package cn.nevo.io; import java.io.File; public class MakeDirectories { //无参数指定 public static void usage() { System.err.println( "Usage:MakeDirectories path1 ... \n" + "Creates each path\n" + "Usage:MakeDirectories -d path1 ... \n" + "Deletes each path\n" + "Usage:MakeDirectories -r path1 path2\n" + "Renames from path1 to path2" ); System.exit(1); } public static void fileData(File f) { System.out.println( "Absolute path:" + f.getAbsolutePath() + "\n Can read:" + f.canRead() + "\n Can write:" + f.canWrite() + "\n getName:" + f.getName() + "\n getParent:" + f.getParent() + "\n getPath:" + f.getPath() + "\n length:" + f.length() + "\n lastModified:" + f.lastModified() ); if(f.isFile()) { System.out.println("It's a file"); } else if(f.isDirectory()) { System.out.println("It's a directory"); } } public static void main(String[] args) { //无参数输入,调用usage,给出提示 if(args.length < 1) { usage(); } //重命名文件,指定三个输入参数 if(args[0].equals("-r")) { if(args.length !=3) { usage(); } File old = new File(args[1]), rname = new File(args[2]); old.renameTo(rname); fileData(old); fileData(rname); return; } //新建和删除 int count = 0; boolean del = false; if(args[0].equals("-d")) { count ++; del = true; } count--; while(++count < args.length) { File f = new File(args[count]); if(f.exists()) { System.out.println(f + " exists"); if(del) { System.out.println("deleting..." + f); f.delete(); } }else { //不存在 if(!del) { f.mkdirs(); System.out.println("created " + f); } } fileData(f); } } }
输入和输出 (对文件内容进行操作)
输入输出使用流这个抽象概念,它屏蔽了实际的I/O设备中处理数据的细节。
字节流和字符流
它们的操作方式几乎完全一样,只是操作的数据单元不同而已
1、InputStream和Reader
InputStream和Reader是所有输入流的基类,它们是两个抽象类,是所有输入流的模版,其中定义的方法在所有输入流中都可以使用。
对比InputStream和Reader所提供的方法,可以看出这两个基类的功能基本相似。 返回结果为-1时表明到了输入流的结束点。
1.1、 FileInputStream和FileReader
InputStream和Reade都是抽象的,不能直接创建它们的实例,可以使用它们的子类,如FileInputStream和FileReader,它们都是节点流,直接和指定文件关联。
示例:
public class FileInputStreamTest { public static void main(String[] args) { InputStream in = null; try { in = new FileInputStream(".\\src\\cn\\nevo\\io\\FileInputStreamTest.java"); byte[] by = new byte[1024]; int hasRead = 0; while((hasRead = in.read(by)) > 0) { System.out.println(new String(by, 0, hasRead)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e2) { e2.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }
程序中打开的文件IO资源不属于内存中的资源,垃圾回收无法回收,需要显示关闭。
2、OutputStream和Writer
OutputStream和Writer也非常相似。
在Writer中包含如下方法:
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象来作为参数。
2.1、FileOutputStream和FileWriter
示例:实现复制功能
public class FileOutputStreamTest { public static void main(String[] args) { FileInputStream input = null; FileOutputStream output = null; try { //字节输入流 input = new FileInputStream(".\\src\\cn\\nevo\\io\\FileOutputStreamTest.java"); output = new FileOutputStream(".\\FileOutputStreamTest.txt"); byte[] by = new byte[1024]; int hasRead = 0; while((hasRead = input.read(by)) > 0) { output.write(by, 0, hasRead); } System.out.println("文件复制成功!"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e2) { e2.printStackTrace(); } finally { try { input.close(); output.close(); }catch (IOException e) { e.printStackTrace(); } } } }
3、处理流
上面介绍了输入输出流的四个基本抽象类,并介绍了其子类访问文件的节点流的用法,下面我们来看一看处理流,它可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,让程序员只需关心高级流的操作。
使用处理流的典型思路是:使用处理流来包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的IO设备、文件交互。
处理流的识别非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流。所有节点流都是直接以物理IO节点作为构造器参数的。
PrintStream示例:
//处理流 public class PrintStreamTest { public static void main(String[] args) { PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream("text.txt"); ps = new PrintStream(fos); ps.println("处理流示例"); ps.append("PrintStream!"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
使用处理流只需要在创建处理流时传入一个节点流构造参数即可。当我们使用处理流包装底层节点流之后,关闭输入输出资源时,只需要关闭最上层的处理流即可。
上表中的缓冲流主要是用于提高输入、输出的效率,但增加缓冲功能后一定要使用flush()才可以将缓冲区中的内容写入实际的物理节点。
对象流主要用于实现对象的序列化,管道流用于实现进程之间通信功能。
通常来说,我们认为字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件。
如果需要进行输入输出操作的是文本内容,考虑使用字符流,如果需要进行输入输出操作的是二进制内容,考虑使用字节流。
4、转换流(InputStreamReader和OutputStreamWriter)
用于实现将字节流转换为字符流。
public class KeyInTest { public static void main(String[] args) { //java使用System.in代表标准输入,是InputStream(字节流)的实例 BufferedReader br = null; try { //将字节流转换为字符流 InputStreamReader isr = new InputStreamReader(System.in); //将普通的字符流包装成缓冲流 br = new BufferedReader(isr); String buffer; while((buffer = br.readLine()) != null) { if(buffer.equals("exit")) { System.exit(1); } //打印读取内容 System.out.println(buffer); } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
BufferedReader具有缓冲功能,可以一次读取一行文本,以换行符为标志,如果没有读到换行符,则程序阻塞,等到读到换行符为止。当我们在控制台进行输入时,只有按下回车时才会打印输入的结果。
由于BufferedReader有一个readLine方法,经常把读取文本内容的输入流包装为BufferedReader,以方便的读取输入流的文本内容。
5、重定向标准输入输出
java的标准输入、输出分别通过 System.in和 System.out来代表,默认情况下它们分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到屏幕。
System类:
java.lang.Object
java.lang.System
public final class System extends Object
示例,重定向标准输出:
public class RedirectOut { public static void main(String[] args) { //问重定向标准输出之前输出到控制台 System.out.println("输出到控制台"); PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream("out.txt"); ps = new PrintStream(fos); //重定向标准输出到PrintStream System.setOut(ps); //此时便不再输出到控制台 System.out.println("不再输出到控制台"); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { ps.close(); } } }
6、RandomAccessFile
与普通的文件输入输出不同的是,RandomAccessFile支持任意访问的方式。程序可以直接跳转的文件的任意地方来读写数据。如果我们希望只访问文件的部分内容,而不是把文件从头读到尾,这是更好的选择。RandomAccessFile既可以读文件也可以写。
注意:RandomAccessFile依然不能向文件的指定位置插入内容,如果这样做,新输出的内容将覆盖文件中原有的内容。如果需要插入,可以先把插入点内容读入缓冲区,等把需要插入的内容写到文件后再将缓冲区内容追加到文件后面。
RandomAccessFile类有两个构造器,一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定其访问模式,具有以下四个值:
示例,使用RandomAccessFile访问中间部分数据:
public class RandomAccessFileTest { public static void main(String[] args) { File file = new File(".\\src\\cn\\nevo\\io\\RandomAccessFileTest.java"); RandomAccessFile raf = null; try { //以只读方式打开一个RandomAccessFile对象 raf = new RandomAccessFile(file, "r"); //文件指针的初始位置0 System.out.println(raf.getFilePointer()); //从文件指针为300的位置开始读 raf.seek(300); int hasRead = 0; byte[] by = new byte[1024]; while((hasRead = raf.read(by)) > 0) { System.out.println(new String(by, 0, hasRead)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e2) { e2.printStackTrace(); } finally { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }
向指定文件后追加内容,应该先将文件记录指针定位到文件最后,模式改为“rw”,然后再开始向文件中输入内容。
多线程、断点的网络下载工具就可以使用RandomAccessFile类来实现。
7、对象序列化
对象序列化的目标是将对象保存在磁盘中,或在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其它程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的java对象。序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize)是指将一个java对象写入IO流中,对象的反序列化(Deserialize)则指从IO流中恢复该java对象。
为了让某个类是可序列化的,该类必须实现如下两个接口之一: Serializable、Externalizable。通常建议,程序创建的每个JavaBean类都实现Serializable接口,该接口是一个标记接口,无须实现任何方法,它是指表明该类的实例是可序列化的。
通过在属性值前面加上 transient关键字(只能用于修饰属性),可以指定java序列化时无须理会该属性值。







