Java的IO系统

Java的IO操作

最近想用Java写一个爬虫,知乎了一下,很多人推荐如果业务逻辑不太复杂,都推荐使用国内大牛写的的一个框架webmagic,这个是java实现的,思路参照谷歌的Scrapy
。但是实现爬虫需要用到很多关于IO操作和多线程,发现这两项一直都是我java比较模糊的地方,这次就顺便学习一下,我看的是《java编程思想》。

对于IO的存取,不仅存在与各种I/O源端和想与之通信的接收端(接收端包括文件、控制台、网络链接等等),而且可能还需要有多种不同的方式与它们通信(顺序、随机存取、缓冲、二进制、按字符、按字节、按字等)。对于java的类库设计来说,java通过创建了大量的类库来解决这些各种各样复杂的需求。下面根据不同的需求来慢慢分析各种类的使用。

一 . 本应该叫FilePath文件目录的File类

看着这个类名大家不要误会哟,它的作用并不是处理文件的,而是用来处理文件路径(或则叫文件目录)的实用工具类。它代一个特定文件的名称(这里的名称是包含路径的名称),又能代表一个目录下的一组文件的名称

1. 目录列表器

这个实现查看一个文件目录下的文件列表。可以使用两种方法实现:1.调用不带参数的list()方法获取目录下的全部文件的列表。2.通过一个“目录过滤器”,获取规定条件下的File对象,这里的过滤一般使用正则表达式实现。

话不多说,先贴代码:DirList3.java

package io;

import java.util.regex.*;
import java.io.*;
import java.util.*;

public class DirList3 {
  public static void main(final String[] args) {
    File path = new File("./src/io");
    String[] list;
    if(args.length == 0)
      list = path.list();
    else
      list = path.list(new FilenameFilter() {
        private Pattern pattern = Pattern.compile(args[0]);
        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对象调用list()方法,该方法会返回一个字符数组,该字符数组就存储了该目录下的全部文件的文件目录。当我们要显示路劲下受限制的文件目录时(比如显示后缀为.java的java源文件),可以输入参数args,该参数的第一个元素可以输入正则表达式过滤,这时调用的是带参的list()方法,输入的参数是FilenameFilter对象,这是一个文件名称过滤器的类,该类很简单,里面只有一个接口方法accept(),所以我们这里使用一个匿名内部类然后实现该接口。实现该接口的原因在于把accept()方法供list()使用,使list()方法回调accept(),进而决定哪些文件包含在目录列表中,这里的回调用到了一种称为策略模式的设计模式,关于策略模式,具体看设计模式板块。accept()方法必须接受一个代表某个特定文件所在目录的File对象和包含那个文件名的一个string。注意:list()会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内,判断结果由布尔值表示。具体的我们可以看JDK底层源代码:

//这个是JDK底层源代码,对于带参的list()的实现:
public String[] list(FilenameFilter filter) {
	String names[] = list();
	if ((names == null) || (filter == null)) {
	    return names;
	}
	ArrayList v = new ArrayList();
	for (int i = 0 ; i < names.length ; i++) {
	    if (filter.accept(this, names[i])) {
		v.add(names[i]);
	    }
	}
	return (String[])(v.toArray(new String[v.size()]));
    }

2. 目录实用工具:

下面的代码是对一些工具类的一些封装。

package utils;

import java.util.regex.*;
import java.io.*;
import java.util.*;

/**
 * 文件和目录的工具类
 * final类:不可被继承的类
 * @author Administrator
 *
 */
public final class Directory {

	/**
	 * function:根据传入的file目录和正则表达式,获取该目录中的文件构成的File对象数组
	 * @param dir
	 * @param regex
	 * @return 返回满足指定过滤器的抽象路径名表示的目录下的文件和目录的数组
	 */
  public static File[] local(File dir, final String regex) {
    return dir.listFiles(new FilenameFilter() {
      private Pattern pattern = Pattern.compile(regex);//设置正则表达式
      public boolean accept(File dir, String name) {
        return pattern.matcher(new File(name).getName()).matches();
      }
    });
  }

  //Overloaded
  public static File[] local(String path, final String regex) {
    return local(new File(path), regex);
  }

  /**
   * 内部静态类
   * @author Administrator
   *
   */
  public static class TreeInfo implements Iterable<File> {
    public List<File> files = new ArrayList<File>();
    public List<File> dirs = new ArrayList<File>();
    // The default iterable element is the file list:
    public Iterator<File> iterator() {
      return files.iterator();
    }

    void addAll(TreeInfo other) {
      files.addAll(other.files);
      dirs.addAll(other.dirs);
    }

    public String toString() {
      return "dirs: " + PPrint.pformat(dirs) +
        "\n\nfiles: " + PPrint.pformat(files);
    }
  }

  public static TreeInfo walk(String start, String regex) { // Begin recursion
    return recurseDirs(new File(start), regex);
  }

  public static TreeInfo walk(File start, String regex) { // Overloaded
    return recurseDirs(start, regex);
  }

  public static TreeInfo walk(File start) { // Everything
    return recurseDirs(start, ".*");
  }

  public static TreeInfo walk(String start) {
    return recurseDirs(new File(start), ".*");
  }

  /**
   *
   * @param startDir
   * @param regex
   * @return
   */
  static TreeInfo recurseDirs(File startDir, String regex){
    TreeInfo result = new TreeInfo();
    for(File item : startDir.listFiles()) {
      if(item.isDirectory()) {
        result.dirs.add(item);
        result.addAll(recurseDirs(item, regex));
      } else // Regular file
        if(item.getName().matches(regex))
          result.files.add(item);
    }
    return result;
  }

  // Simple validation test:
  public static void main(String[] args) {
    if(args.length == 0)
      System.out.println(walk("."));
    else
      for(String arg : args)
       System.out.println(walk(arg));
  }
}

二 .  I/O的典型使用方式

java的IO操作类库众多,首先看一下类的树结构:下面删除了一些不常用的,只列出了一些常用的,主要就是:File类、 InputStream及其继承子类、 OutputStream及其继承子类、Reader及其继承子类、Writer及其继承子类、RandomAccessFile类。

  • java.lang.Object

    • java.io.File (implements java.lang.Comparable<T>,
      java.io.Serializable)
    • java.io.InputStream (implements java.io.Closeable)
      • java.io.ByteArrayInputStream
      • java.io.FileInputStream
      • java.io.FilterInputStream
        • java.io.BufferedInputStream
        • java.io.DataInputStream (implements java.io.DataInput)
        • java.io.LineNumberInputStream
        • java.io.PushbackInputStream
      • java.io.ObjectInputStream (implements java.io.ObjectInput,
        java.io.ObjectStreamConstants)
      • java.io.PipedInputStream
      • java.io.SequenceInputStream
      • java.io.StringBufferInputStream
    • java.io.OutputStream (implements java.io.Closeable,
      java.io.Flushable)

      • java.io.ByteArrayOutputStream
      • java.io.FileOutputStream
      • java.io.FilterOutputStream
        • java.io.BufferedOutputStream
        • java.io.DataOutputStream (implements java.io.DataOutput)
        • java.io.PrintStream (implements java.lang.Appendable,
          java.io.Closeable)
      • java.io.ObjectOutputStream (implements java.io.ObjectOutput,
        java.io.ObjectStreamConstants)
    • java.io.RandomAccessFile (implements java.io.Closeable,
      java.io.DataInput, java.io.DataOutput)
    • java.io.Reader (implements java.io.Closeable,
      java.lang.Readable)

      • java.io.BufferedReader

        • java.io.LineNumberReader
      • java.io.CharArrayReader
      • java.io.FilterReader
        • java.io.PushbackReader
      • java.io.InputStreamReader
        • java.io.FileReader
      • java.io.PipedReader
      • java.io.StringReader
    • java.io.Writer (implements java.lang.Appendable,
      java.io.Closeable, java.io.Flushable)

      • java.io.BufferedWriter
      • java.io.CharArrayWriter
      • java.io.FilterWriter
      • java.io.OutputStreamWriter
        • java.io.FileWriter
      • java.io.PipedWriter
      • java.io.PrintWriter
      • java.io.StringWriter

上面这些类的使用具体可以去参考JDK的文档。我们可以发现对于文件的读写类大都是成对出现的,InputStream或则Reader实现读取(从流中读取),OutputStream或则Writer实现写入功能(输出至流)。这里的流可以是控制台,可以是文件,也可以是Internet。

Java的IO类库需要多种不同功能的组合,这也正是使用装饰器模式的理由所在。FilterInputStreamFilterOutputStream用来提供装饰器类接口以控制特定的输入流InputStream与输出流OutputStream两个类。

InputStream  和 OutputStream 
主要是面向字节形式的IO中;而ReaderWriter
主要是面向字符的IO操作。

尽管可以通过不同的方式组合使用IO类流,但是常用的就只有那么一些,下面来分别分析。

1. 缓冲方式实现输入文件

对于一个文件用于字符输入,为了提高速度,我们一般希望能够对那个文件进行缓冲,所以我们使用BufferedReader  这个类实现。具体的代码如下:

package io.input;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * 缓冲输入文件
 * 打开一个文件用于字符输入,读取字符
 * @author Administrator
 */
public class BufferedInputFile {

	/**
	 * @param fileName 文件名
	 * @return 文件内的字符串
	 * @throws IOException
	 */
	public static String read(String fileName) throws IOException {

		//inputStream输入流
		//创建一个(使用默认大小输入缓冲区的)缓冲字符输入流
		BufferedReader in = new BufferedReader( new FileReader(fileName));

		String s= null;
		StringBuilder sb = new StringBuilder();

		while( (s = in.readLine()) != null ) {
			sb.append(s + "\n");
		}

		in.close();
		return sb.toString();
	}

	/*
	 * testing
	 */
	public static void main(String[] args) throws IOException {
		System.out.println( read("./src/io/BufferedInputFile.java") );
	}
}

2. 从内存输入

从内存中读取数据,使用的是StringReader  这个类实现,下面的代码首先从一个文件中读取出数据,然后存放在内存中,然后通过StringReader
的read()方法每次读取一个字符来实现。

package io.input;

import java.io.IOException;
import java.io.StringReader;

/**
 * 从内存输入
 * @author Administrator
 */
public class MemoryInput {

	public static void main(String[] args) throws IOException {

		String str = BufferedInputFile.read("./src/io/MemoryInput.java");
		//str这时存放在内存中
		//StringReader :源为字符串的字符流
		StringReader in = new StringReader(str);

		int c = 0;

		while ( (c = in.read()) != -1) {
			System.out.print( (char)c );
		}
	}
}

3. 格式化的内存输入

读取格式化的数据,可以使用DataInputStream  这个类,很明显这个类是面向字节的IO类读取的,所以我们不能使用Reader而必须使用InputStream。代码如下:

package io.input;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;

/**
 * 格式化的内存输入:面向字节读取(8位),而我们一般的字符读取是16位,所以字节读取出来的中文是乱码
 * 			         所以必须使用inputStream。不能使用Reader
 * @author Administrator
 *
 */
public class FormattedMemoryInput {

	public static void main(String[] args) throws IOException {
	/*
		try {
			//先读取java文件的字符串并转换成 字节数组
			byte[] bs = BufferedInputFile.read("./src/io/FormattedMemoryInput.java").getBytes();
			//创建字节数组输入流
			ByteArrayInputStream ins = new ByteArrayInputStream(bs);
			//创建输入流
			DataInputStream in = new DataInputStream(ins);

			while(true){
				System.out.print( (char)in.readByte() );
			}
		} catch (EOFException e) {
			System.err.println("end");
		}
	*/
		//先读取java文件的字符串并转换成 字节数组
		byte[] bs = BufferedInputFile.read("./src/io/FormattedMemoryInput.java").getBytes();
		//创建字节数组输入流
		ByteArrayInputStream ins = new ByteArrayInputStream(bs);
		//创建输入流
		DataInputStream in = new DataInputStream(ins);

		while( in.available() != 0){
			System.out.print( (char)in.readByte() );
		}
	}

}

上面的几个例子都是讲的文件的输入即文件的读取,下面来讲一下关于文件的输出即文件的写入:

4. 基本的文件输出

FileWriter对象可以向文件写入数据,通常我们会用BufferedWriter将其包装起来泳衣缓冲输出。为了提供格式化的输出,它被装饰成PrintWriter,在Java Se5中为PrintWriter 添加了一个辅助构造器,使得我们直接在PrintWriter 
的构造函数中直接输入文件的路径就行了,不必去执行装饰工作。代码如下;

package io.output;

import io.input.BufferedInputFile;
import java.io.*;

public class FileOutputShortcut {
	static String file = "./src/io/output/FileOutputShortcut.java";
	static String fileOut = "./src/io/output/FileOutputShortcut.out";

	public static void main(String[] args) throws IOException {
	    BufferedReader in = new BufferedReader(new FileReader(file));

	    //此处简化了
	    PrintWriter out = new PrintWriter(fileOut);

	    int lineCount = 1;
	    String s;

	    while((s = in.readLine()) != null )
	      out.println(lineCount++ + ": " + s);

	    in.close();
	    out.close();
	    System.out.println(BufferedInputFile.read(fileOut));
  }
}

5. 文件读写的实用工具(把上面关于文件的读写进行封装成一个工具类)

先贴上代码:

package utils;

import java.io.*;
import java.util.*;

/**
 * function:提供一些静态方法,像简单字符串一样读写文件
 * //字符形式
 * @author Administrator
 *
 */
public class TextFile extends ArrayList<String> {

	private static final long serialVersionUID = 2025881288414881601L;

	/**
	 * function:读取一个文件,返回文件内容的字符串形式
	 * @param fileName
	 * @return
	 */
	public static String read(String fileName) {

		StringBuilder sb = new StringBuilder();
	    try {
	    	//获取的绝对路径(磁盘名开始)
	    	BufferedReader in= new BufferedReader(new FileReader( new File(fileName).getAbsoluteFile()));
	    	try {
		        String s;
		        while((s = in.readLine()) != null) {
		        	sb.append(s);
		        	sb.append("\n");
		        }
	    	} finally {
	    		in.close();
	    	}
	    } catch(IOException e) {
	    	throw new RuntimeException(e);
	    }
	    return sb.toString();
	}

    /**
     * function:往文件里面写入一个字符串
     * @param fileName
     * @param text
     */
	public static void write(String fileName, String text) {
	    try {
	        PrintWriter out = new PrintWriter( new File(fileName).getAbsoluteFile());
		    try {
		    	out.print(text);
		    } finally {
		        out.close();
		    }
	    } catch(IOException e) {
	    	throw new RuntimeException(e);
	    }
	}

	/**
	 * function:构造器:
	 * 读一个文件返回字符串,并以一个正则表达式分离,然后保存在一个ArrayList中
	 */
	public TextFile(String fileName, String splitter) {
	    super( Arrays.asList( read(fileName).split(splitter) ) );

	    if(get(0).equals(""))
		    remove(0);
	}

	/**
	 * function:构造器
	 * 默认以换行符为正则
	 * @param fileName
	 */
	public TextFile(String fileName) {
		this(fileName, "\n");
	}

	public void write(String fileName) {
	    try {
	    	PrintWriter out = new PrintWriter(new File(fileName).getAbsoluteFile());
	    	try {
		        for(String item : this)
		        	out.println(item);
		    } finally {
		    	out.close();
		    }
	    } catch(IOException e) {
	    	throw new RuntimeException(e);
	    }
	}

    //main 测试
	public static void main(String[] args) {
	    String file = read("./src/utils/TextFile.java");
	    write("./src/utils/test.txt", file);

	    TextFile text = new TextFile("./src/utils/test.txt");
	    text.write("./src/utils/test2.txt");

	    TreeSet<String> words = new TreeSet<String>(
	    	new TextFile("./src/utils/TextFile.java", "\\W+"));

	    System.out.println(words.headSet("a"));
	}
} 

6. 读取二进制文件

与第5点的工具类很类似:

package utils;

import java.io.*;

/**
 * function:读取二进制格式的文件
 * @author Administrator
 *
 */
public class BinaryFile {

	public static byte[] read(File bFile) throws IOException{
	    BufferedInputStream bf = new BufferedInputStream( new FileInputStream(bFile) );
	    try {
	    	byte[] data = new byte[bf.available()];
	    	bf.read(data);//从此输入流中将 byte.length个字节的数据读入  data数组中。
	    	return data;
	    } finally {
	    	bf.close();
	    }
	}

	public static byte[] read(String bFile) throws IOException {
		return read( new File(bFile).getAbsoluteFile() );
	}
} 

三. 标准IO

按照java的标准IO模型,java提供了System.in、System.out、System.err三种标准IO。其中System.out和System.err已经事先被包装成了printStream对象,然而System.in却是一个未经过加工和包装的InputStream。所以我们可以直接使用System.out和System.err,但是对于System.in使用前必须要先包装。

下面展示一个例子,用于包装使用System.in。回显我们在控制台输入的每一行。

package io;

import java.io.*;

public class Echo {
	public static void main(String[] args) throws IOException {
	    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
	    String s;
	    while((s = stdin.readLine()) != null && s.length()!= 0)
	    	System.out.println(s);
	}
} 

1.标准IO的重定向

我觉得标准IO的一个最大的用处在于标准IO的重定向,比如我们可以把对于控制台的输出全部重定向输出到一个文件中,这样就类似于实现了记录日志的功能。

对于三个标准IO的重定向使用下面的三个函数:

setIn(InputStream)

setOut(PrintStream)

setErr(PrintStream)

示例代码如下:

package io;

import java.io.*;

/**
 * 标准IO重定向,可以吧控制台输出到指定文件中,类似于日志
 * @author Administrator
 */
public class Redirecting {

	public static void main(String[] args) throws IOException {
	    PrintStream console = System.out;
	    BufferedInputStream in = new BufferedInputStream( new FileInputStream("./src/io/Redirecting.java"));
	    PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("./src/io/test.out")));

	    System.setIn(in);
	    System.setOut(out);//标准输出输出重定向到test.out这个文件中。
	    System.setErr(out);//标准输出输出重定向到test.out这个文件中。

	    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	    String s;
	    while((s = br.readLine()) != null && s.length()!= 0 )
	      System.out.println(s);

	    out.close(); // Remember this!
	    System.setOut(console);
	}
}
时间: 2025-01-31 15:41:44

Java的IO系统的相关文章

Java:IO系统与装饰模式

Java的IO流有三种分法: ①输入流.输出流:输入输出流都是以Java程序为参照的. ②字节流.字符流:字节是存储单位,占8位,其他基本数据类型都用字节来衡量大小.字符是数字字母等字符,ASCII和Unicode都是字符编码集,ASCII码是8位一个字节的,Unicode是16位两个字节的,而Java字符编码是采用Unicode的.字节流后缀是Stream,字符流后缀是Reader,Writer. ③节点流.处理流:节点流可以理解为真正处理数据的流,处理流是在节点流的基础上的修饰. 关于各种流

一文看懂java io系统 (转)

出处:  一文看懂java io系统 学习java IO系统,重点是学会IO模型,了解了各种IO模型之后就可以更好的理解java IO Java IO 是一套Java用来读写数据(输入和输出)的API.大部分程序都要处理一些输入,并由输入产生一些输出.Java为此提供了java.io包 java中io系统可以分为Bio,Nio,Aio三种io模型 关于Bio,我们需要知道什么是同步阻塞IO模型,Bio操作的对象:流,以及如何使用Bio进行网络编程,使用Bio进行网络编程的问题 关于Nio,我们需

JAVA的IO学习

IO 有具体的分类: 有具体的分类:1:根据处理的数类型不同:字节流和字符流.2:根据流向不同:输入流和输出流. =============(补充字节跟字符概念区分)============================== 字符:可使用多种不同字符方案或代码页来表示的抽象实体.例如,Unicode UTF-16 编码将字符表示为 16 位整数序列,而Unicode UTF-8 编码则将相同的字符表示为 8 位字节序列.公共语言运行库使用 Unicode UTF-16(Unicode 转换格式

java中的io系统详解

java中的io系统详解 分类: JAVA开发应用 笔记(读书.心得)2009-03-04 11:26 46118人阅读 评论(37) 收藏 举报 javaiostreamconstructorstringbyte 相关读书笔记.心得文章列表 Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他

java I/O系统(输入输出流)

java I/O系统(输入输出流) 流的特性1.含有流质(数据)2.它有方向(读或写) 流的分类: 输入流和输出流 输入流:io包中的输入流继承自抽象类InputStream或Reader 输出流:io包中的输入流继承自抽象类OutputStream或Writer 字节流和字符流 注:1字节代表1个英文单词存储的数据大小,一个汉字占两字节 1.字节流:以byte为最小单位传送,继承自抽象类InputStream或OutputStream,用于处理二进制文件,InputStream为读取字节流的父

java 新IO

传统的IO Java中的InputStream.OutputStream.Reader.Writer这样的面向流的输入输出系统被视为传统的IO.传统的IO是阻塞式的输入输出,并且是通过字节的移动来处理的,即传统的IO一次只能处理一个字节,效率不高. 新IO 新IO和传统的IO有相同的目的,都是用于进行输入输出功能.但是新IO采用内存映射文件的方式来处理输入输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了,这种方式要比传统的IO快得多. Java中新IO相关的包如

Java操作IO各主要类介绍

DataInputStream和DataOutputStream 往二进制文件中读和写入java基本数据类型 public class BinaryReadWrite { private DataInputStream dis = null; private DataOutputStream dos = null; private String s_FilePath = "config\\bin.dat"; private byte[] buff = "{\"nam

JAVA之IO技术-获取指定目录下的文件夹和文件的File对象或是字符串名称。

package ioTest.io3; /* * 获取指定目录下的文件夹和文件的File对象或是字符串名称. * 也可以通过filter获取指定的文件夹或者指定类型的文件 * 这里面需要做一个总结,如何利用jdk的源码去理解不熟悉的方法的应用. */ import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; public class FileDemo2 { public static void m

java中获取系统属性以及环境变量

java中获取系统属性以及环境变量 System.getEnv()和System.getProperties()的差别 从概念上讲,系统属性 和环境变量 都是名称与值之间的映射.两种机制都能用来将用户定义的信息传递给 Java 进程.环境变量产生很多其它的全局效应,由于它们不仅对Java 子进程可见,并且对于定义它们的进程的全部子进程都是可见的.在不同的操作系统上,它们的语义有细微的区别,比方,不区分大写和小写.由于这些原因,环境变量更可能有意料不到的副作用.最好在可能的地方使用系统属性.环境变