Java IO

**概述
**
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)。用以更高效的进行输入、输出操作的处理。

                       Java I/O 体系大体图
wps_clip_image-11015

File类

public class File extends Object
implements Serializable, Comparable<File>,文件和目录路径名的抽象表示形式。

File类是java.io包下代表与平台无关的文件和目录,在程序中用来操作文件和目录。使用文件路径字符串来创建File示例,该文件路径字符串既可以是绝对路径,也可以是相对路径。File能新建、删除和重命名文件和目录,但是不能访问文件内容本身。访问文件内容本身的操作需要使用IO流。

1package cn.nevo.io; 2 3import java.io.File; 4import java.io.FileOutputStream; 5import java.io.IOException; 6import java.io.OutputStream; 7 8public class DirList2 { 9 10 public static void main(String[] args) { 11 12 //必须要保证父目录hello的存在 13 File path = new File("c:\\hello\\hello.txt"); 14 if(!path.getParentFile().exists()) { 15 path.getParentFile().mkdir(); 16 } 17 if(!path.exists()) { 18 try { 19 path.createNewFile(); 20 System.out.println("文件创建成功!"); 21 } catch (IOException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 } 26 27 byte[] s = new byte[]{'h', 'e', 'l', 'l', 'o'}; 28 try { 29 OutputStream out = new FileOutputStream(path); 30 out.write(s); 31 } catch (Exception e) { 32 e.printStackTrace(); 33 } 34 } 35}

实现FilenameFilter接口的类实例用于过滤文件名,在File的list方法中可以接收一个FilenameFilter参数,用于列出符合条件的文件。

1package java.io; 2public interface FilenameFilter { 3 boolean accept(File dir, String name); 4}

示例:目录列表器

1package cn.nevo.io; 2 3import java.io.File; 4import java.io.FilenameFilter; 5import java.util.Arrays; 6import java.util.regex.Pattern; 7 8//查看目录列表 9public class DirList { 10 11 public static void main(String[] args) { 12 //"/"代表该资源所处的根目录,此处为F:\ 13 //"."代表该资源所在项目所处目录,此处为F:\workspace\JavaIoTest\. 14 File path = new File("."); 15 //返回此抽象路径名的绝对路径名字符串。 16 System.out.println(path.getAbsolutePath()); 17 18 String[] list; 19 //没有指定运行参数,获取File对象包含的全部列表 20 if(args.length == 0) { 21 //list()方法返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 22 list = path.list(); 23 } 24 //获取受限制列表 25 else { 26 //list(FilenameFilter filter)返回满足指定过滤器条件的目录和文件列表 27 //args:".*\.project" 28 //list方法会为此目录对象下的每个文件名调用accept方法,来判断该文件是否包含再内 29 list = path.list(new DirFilter(args[0])); 30 } 31 32 //对查询结果按字母排序 33 Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); 34 35 //输出数组列表 36 for(String dirItem : list) { 37 System.out.println(dirItem); 38 } 39 } 40} 41 42//实现FilenameFilter接口的类实例可用于过滤文件名 43class DirFilter implements FilenameFilter { 44 45 private Pattern pattern; 46 public DirFilter(String regex) { 47 //compile(String regex)将给定的正则表达式编译到模式中。 48 this.pattern = Pattern.compile(regex); 49 } 50 51 @Override 52 public boolean accept(File dir, String name) { 53 //创建匹配给定输入与此模式的匹配器,尝试将整个区域与模式匹配。 54 //当且仅当整个区域序列匹配此匹配器的模式时才返回 true 55 boolean flag = pattern.matcher(name).matches(); 56 System.out.println(flag); 57 return flag; 58 } 59}

显示结果:
QQ截图20120922110714

不指定参数运行情况:
F:\workspace\JavaIoTest\.
.classpath
.project
.settings
bin
src

指定参数运行(显示所有后缀名为.project的文件名)
F:\workspace\JavaIoTest\.
false
true
false
false
false
.project
list方法会为此目录对象下的每个资源调用accept方法,来判断该文件是否包含在内。

采用内部类修改上述示例:

1package cn.nevo.io; 2 3import java.io.File; 4import java.io.FilenameFilter; 5import java.util.Arrays; 6import java.util.regex.Pattern; 7 8public class DirList3 { 9 10 public static void main(final String[] args) { 11 File path = new File("."); 12 String[] list; 13 if(args.length == 0) { 14 list = path.list(); 15 } else { 16 list = path.list(new FilenameFilter() { 17 //参数args必须是final的,内部类要求 18 //pattern知道匹配模式 19 private Pattern pattern = Pattern.compile(args[0]); 20 21 @Override 22 public boolean accept(File dir, String name) { 23 return pattern.matcher(name).matches(); 24 } 25 }); 26 } 27 28 Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); 29 for(String dirItem : list) { 30 System.out.println(dirItem); 31 } 32 } 33}

目录的检查及创建

File类不仅仅只代表存在的文件或目录,还可以用来创建新的目录或尚不存在的整个目录路径。还可以查看文件特性(如大小、读/写),检查某个File对象代表的是一个文件还是一个目录,并删除。注意查看API文档。

1package cn.nevo.io; 2 3import java.io.File; 4 5public class MakeDirectories { 6 7 //无参数指定 8 public static void usage() { 9 System.err.println( 10 "Usage:MakeDirectories path1 ... \n" + 11 "Creates each path\n" + 12 "Usage:MakeDirectories -d path1 ... \n" + 13 "Deletes each path\n" + 14 "Usage:MakeDirectories -r path1 path2\n" + 15 "Renames from path1 to path2" 16 ); 17 System.exit(1); 18 } 19 20 public static void fileData(File f) { 21 System.out.println( 22 "Absolute path:" + f.getAbsolutePath() + 23 "\n Can read:" + f.canRead() + 24 "\n Can write:" + f.canWrite() + 25 "\n getName:" + f.getName() + 26 "\n getParent:" + f.getParent() + 27 "\n getPath:" + f.getPath() + 28 "\n length:" + f.length() + 29 "\n lastModified:" + f.lastModified() 30 ); 31 32 if(f.isFile()) { 33 System.out.println("It's a file"); 34 } else if(f.isDirectory()) { 35 System.out.println("It's a directory"); 36 } 37 } 38 39 public static void main(String[] args) { 40 //无参数输入,调用usage,给出提示 41 if(args.length < 1) { 42 usage(); 43 } 44 45 //重命名文件,指定三个输入参数 46 if(args[0].equals("-r")) { 47 if(args.length !=3) { 48 usage(); 49 } 50 File old = new File(args[1]), 51 rname = new File(args[2]); 52 old.renameTo(rname); 53 fileData(old); 54 fileData(rname); 55 return; 56 } 57 58 //新建和删除 59 int count = 0; 60 boolean del = false; 61 if(args[0].equals("-d")) { 62 count ++; 63 del = true; 64 } 65 count--; 66 while(++count < args.length) { 67 File f = new File(args[count]); 68 if(f.exists()) { 69 System.out.println(f + " exists"); 70 if(del) { 71 System.out.println("deleting..." + f); 72 f.delete(); 73 } 74 }else { //不存在 75 if(!del) { 76 f.mkdirs(); 77 System.out.println("created " + f); 78 } 79 } 80 fileData(f); 81 } 82 } 83}

输入和输出 (对文件内容进行操作)

输入输出使用流这个抽象概念,它屏蔽了实际的I/O设备中处理数据的细节。

字节流和字符流
它们的操作方式几乎完全一样,只是操作的数据单元不同而已

1、InputStream和Reader
InputStream和Reader是所有输入流的基类,它们是两个抽象类,是所有输入流的模版,其中定义的方法在所有输入流中都可以使用。

在InputStream里包含如下三个方法:
QQ截图20120925123007

在Reader中包含如下三个方法:
QQ截图20120925123225

对比InputStream和Reader所提供的方法,可以看出这两个基类的功能基本相似。 返回结果为-1时表明到了输入流的结束点。

1.1、 FileInputStream和FileReader

InputStream和Reade都是抽象的,不能直接创建它们的实例,可以使用它们的子类,如FileInputStream和FileReader,它们都是节点流,直接和指定文件关联。
示例:

1public class FileInputStreamTest { 2 3 public static void main(String[] args) { 4 5 InputStream in = null; 6 try { 7 in = new FileInputStream(".\\src\\cn\\nevo\\io\\FileInputStreamTest.java"); 8 byte[] by = new byte[1024]; 9 int hasRead = 0; 10 while((hasRead = in.read(by)) > 0) { 11 System.out.println(new String(by, 0, hasRead)); 12 } 13 14 } catch (FileNotFoundException e) { 15 e.printStackTrace(); 16 } catch (IOException e2) { 17 e2.printStackTrace(); 18 } finally { 19 try { 20 in.close(); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } 24 } 25 } 26}

程序中打开的文件IO资源不属于内存中的资源,垃圾回收无法回收,需要显示关闭。

2、OutputStream和Writer
OutputStream和Writer也非常相似。

在OutputStream里包含如下三个方法:
QQ截图20120925131602

在Writer中包含如下方法:
QQ截图20120925131719
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象来作为参数。

2.1、FileOutputStream和FileWriter
示例:实现复制功能

1public class FileOutputStreamTest { 2 3 public static void main(String[] args) { 4 FileInputStream input = null; 5 FileOutputStream output = null; 6 7 try { 8 //字节输入流 9 input = new FileInputStream(".\\src\\cn\\nevo\\io\\FileOutputStreamTest.java"); 10 output = new FileOutputStream(".\\FileOutputStreamTest.txt"); 11 12 byte[] by = new byte[1024]; 13 int hasRead = 0; 14 while((hasRead = input.read(by)) > 0) { 15 16 output.write(by, 0, hasRead); 17 } 18 System.out.println("文件复制成功!"); 19 20 } catch (FileNotFoundException e) { 21 e.printStackTrace(); 22 } catch (IOException e2) { 23 e2.printStackTrace(); 24 } finally { 25 try { 26 input.close(); 27 output.close(); 28 }catch (IOException e) { 29 e.printStackTrace(); 30 } 31 } 32 } 33}

3、处理流
上面介绍了输入输出流的四个基本抽象类,并介绍了其子类访问文件的节点流的用法,下面我们来看一看处理流,它可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,让程序员只需关心高级流的操作。

使用处理流的典型思路是:使用处理流来包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的IO设备、文件交互。
处理流的识别非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流。所有节点流都是直接以物理IO节点作为构造器参数的。

PrintStream示例:

1//处理流 2public class PrintStreamTest { 3 4 public static void main(String[] args) { 5 PrintStream ps = null; 6 try { 7 FileOutputStream fos = new FileOutputStream("text.txt"); 8 ps = new PrintStream(fos); 9 ps.println("处理流示例"); 10 ps.append("PrintStream!"); 11 12 } catch (FileNotFoundException e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 } 17 18}

使用处理流只需要在创建处理流时传入一个节点流构造参数即可。当我们使用处理流包装底层节点流之后,关闭输入输出资源时,只需要关闭最上层的处理流即可。

输入输出流体系大体结构图(java.io):
QQ截图20120925161143

上表中的缓冲流主要是用于提高输入、输出的效率,但增加缓冲功能后一定要使用flush()才可以将缓冲区中的内容写入实际的物理节点。
对象流主要用于实现对象的序列化,管道流用于实现进程之间通信功能。

通常来说,我们认为字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件。
如果需要进行输入输出操作的是文本内容,考虑使用字符流,如果需要进行输入输出操作的是二进制内容,考虑使用字节流。

4、转换流(InputStreamReader和OutputStreamWriter)

用于实现将字节流转换为字符流。

1public class KeyInTest { 2 3 public static void main(String[] args) { 4 //java使用System.in代表标准输入,是InputStream(字节流)的实例 5 BufferedReader br = null; 6 try { 7 8 //将字节流转换为字符流 9 InputStreamReader isr = new InputStreamReader(System.in); 10 11 //将普通的字符流包装成缓冲流 12 br = new BufferedReader(isr); 13 String buffer; 14 while((buffer = br.readLine()) != null) { 15 if(buffer.equals("exit")) { 16 System.exit(1); 17 } 18 //打印读取内容 19 System.out.println(buffer); 20 } 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } finally { 24 try { 25 br.close(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31}

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

QQ截图20120925165214

在这个类中提供了三个重定向标准输入输出的方法:
QQ截图20120925165408

示例,重定向标准输出:

1public class RedirectOut { 2 3 public static void main(String[] args) { 4 //问重定向标准输出之前输出到控制台 5 System.out.println("输出到控制台"); 6 7 PrintStream ps = null; 8 try { 9 FileOutputStream fos = new FileOutputStream("out.txt"); 10 ps = new PrintStream(fos); 11 //重定向标准输出到PrintStream 12 System.setOut(ps); 13 //此时便不再输出到控制台 14 System.out.println("不再输出到控制台"); 15 } catch (FileNotFoundException e) { 16 e.printStackTrace(); 17 } finally { 18 ps.close(); 19 } 20 } 21 22}

6、RandomAccessFile

与普通的文件输入输出不同的是,RandomAccessFile支持任意访问的方式。程序可以直接跳转的文件的任意地方来读写数据。如果我们希望只访问文件的部分内容,而不是把文件从头读到尾,这是更好的选择。RandomAccessFile既可以读文件也可以写。

注意:RandomAccessFile依然不能向文件的指定位置插入内容,如果这样做,新输出的内容将覆盖文件中原有的内容。如果需要插入,可以先把插入点内容读入缓冲区,等把需要插入的内容写到文件后再将缓冲区内容追加到文件后面。

RandomAccessFile类有两个构造器,一个使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定其访问模式,具有以下四个值:
QQ截图20120925172410

示例,使用RandomAccessFile访问中间部分数据:

1public class RandomAccessFileTest { 2 3 public static void main(String[] args) { 4 File file = new File(".\\src\\cn\\nevo\\io\\RandomAccessFileTest.java"); 5 RandomAccessFile raf = null; 6 try { 7 //以只读方式打开一个RandomAccessFile对象 8 raf = new RandomAccessFile(file, "r"); 9 //文件指针的初始位置0 10 System.out.println(raf.getFilePointer()); 11 //从文件指针为300的位置开始读 12 raf.seek(300); 13 14 int hasRead = 0; 15 byte[] by = new byte[1024]; 16 while((hasRead = raf.read(by)) > 0) { 17 System.out.println(new String(by, 0, hasRead)); 18 } 19 } catch (FileNotFoundException e) { 20 e.printStackTrace(); 21 } catch (IOException e2) { 22 e2.printStackTrace(); 23 } finally { 24 try { 25 raf.close(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31}

向指定文件后追加内容,应该先将文件记录指针定位到文件最后,模式改为“rw”,然后再开始向文件中输入内容。

多线程、断点的网络下载工具就可以使用RandomAccessFile类来实现。

7、对象序列化

对象序列化的目标是将对象保存在磁盘中,或在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其它程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的java对象。序列化机制使得对象可以脱离程序的运行而独立存在。

对象的序列化(Serialize)是指将一个java对象写入IO流中,对象的反序列化(Deserialize)则指从IO流中恢复该java对象。

为了让某个类是可序列化的,该类必须实现如下两个接口之一: Serializable、Externalizable。通常建议,程序创建的每个JavaBean类都实现Serializable接口,该接口是一个标记接口,无须实现任何方法,它是指表明该类的实例是可序列化的。

通过在属性值前面加上 transient关键字(只能用于修饰属性),可以指定java序列化时无须理会该属性值。

点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java多种文件复制方式以及效率比较

1.背景java复制文件的方式其实有很多种,可以分为传统的字节流读写复制FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream传统的字符流读写复制FileReader,FileWriter,BufferWriter,Buffered

面试避坑手册之 Java字节流和字符流总结IO流!

从接收输入值说起在日常的开发应用中,有时候需要直接接收外部设备如键盘等的输入值,而对于这种数据的接收方式,我们一般有三种方法:字节流读取,字符流读取,Scanner工具类读取。字节流读取直接看一个例子:cpublicclassDemo01SystemInpublicstaticvoidmain(Stringargs)throw

NIO入门

1、I/O输入输出,所有的IO都被视作是单个字节的移动,通过stream对象一次移动一个字节。流IO负责把对象转换为字节,然后再转换为对象。NIO提供了二套NIO,一套是针对标准输入输出NIO,另一套是网络编程NIO2、流与块的比较NIO和IO最大的区别是数据打包和传输方式,IO是以流的方式来处理数据,而NIO是以块的方式处理数据。面向块的IO

IO(输入输出)

IO流有很多种,按照操作数据的不同,可以分为字节流和字符流,按照数组传输方向的不同又可分为输入流和输出流。字节流的输入输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入输出流分别用java.io.Reader和java.io.Writer表示。!(https://oscimg

Java IO输入输出

学前知道Java的IO使用“流”的概念来表示。IO流涉及到数据源和目的地。流,是从源“流向”目的的数据流。Java将各种数据源和目标之间数据的传输统一抽象为流,通过对流对象的操作来完成I/O功能。输入输出实际都是对内存而言的。数据源可以是键盘、文件、应用程序、鼠标、网络连接。