JAVA IO

Java I/O 层次结构

根据I/O结构绘制思维导图如下:
I/O层次图

输入流与输出流

InputStream和OutputStream是两个抽象类,分别含义read()和write()抽象方法。

InputStream:

InputStream的作用是用来表示那些从不同数据源产生输入的类,这些数据源包括字节数组、String对象、文件、管道、一个由其它种类的流组成的序列、其它数据源。每一种数据源都有对应的InputStream子类。此外,还有装饰者类FilterInputStream,关于装饰者模式可以看看下面的链接:

装饰者模式

OutputStream:

OutputStream决定了输出要去往的目标:字节数组、文件或管道,每种都有对应的类,另外提供了一个装饰者基类FilterOutputStream。

输入输出流的一个示例(利用InputStream和OutputStream存储和恢复数据,二者可保证数据正确显示):

public static void main(String[] args) throws IOException{
    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt")));
    out.writeDouble(3.14159);
    out.writeUTF("That is Pi");
    out.writeDouble(1.41413);
    out.writeUTF("Square root of 2");
    out.close();

    DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt")));
    System.out.println(in.readDouble()+"\n"+in.readUTF()+"\n"+in.readDouble()+"\n"+in.readUTF());
}

Reader和Writer

Java1.1版本在流库中添加了Reader和Writer类,与InputStream和OutputStream类似,区别是前者兼容面向字符和Unicode的I/O,它们可以通过适配器InputStreamReader和OutPutStreamWriter转化为InputStream和OutputStream。

一个BufferReader装饰者的使用示例,可以借用缓冲来提高速度,读取一个文件:

public class BufferedInputFile {
    public static void main(String[] args) throws IOException{
        System.out.print(read("./out/production/iotest/BufferedInputFile.class"));
    }

    public static String read(String filename) throws IOException{
        BufferedReader in = new BufferedReader(new FileReader(filename));
        String s;
        StringBuilder sb = new StringBuilder();
        while ((s = in.readLine()) != null)
            sb.append(s+"\n");
        in.close();
        return sb.toString();
    }
}
  • Java1.1还在InputStream和OutputStream添加了一些新类,所以不会被取代;
  • 应尽量使用Reader和Writer。

文件相关

File类:

File的构造函数可以通过相对路径、绝对路径或文件的URL来创建文件对象。

  • 相对路径:当指定一个相对文件名时,该文件位于Java虚拟机启动路径的相对位置,启动路径是命令解释器的当前路径,若使用IDE,该路径由IDE控制。”.”表示当前路径;
  • 绝对路径:直接给出文件路径。

File既可以表示一个文件,也可以表示一个目录下所有的文件。
显示文件列表示例:

public class DirList {

    public static void main(String[] args) {
        File path = new File(".");       //表示相对路径下的所有文件
        String[] list;
        if (args.length == 0) {
            list = path.list();
        } else
            list = path.list(new DirFilter(args[0]));
        Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
        for (String s : list) {
            System.out.println(s);
        }
    }
}

class DirFilter implements FilenameFilter{
    private Pattern pattern;
    public DirFilter(String regex){
        pattern = Pattern.compile(regex);
    }

    public boolean accept(File dir, String name){
        return pattern.matcher(name).matches();
    }

}

关于文件的一个示例,实现了文件的创建、删除、重命名:

public class MakeDir {

    public static void main(String[] args) {
        if(args.length < 1)
            usage();
        if(args[0].equals("-r")) {
            if (args.length != 3)
                usage();
            File old = new File(args[1]);
            File 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("delete..."+f);
                    f.delete();
                }
            }
            else {
                if(!del){
                    f.mkdirs();
                    System.out.println("created "+f);
                }
            }
            fileData(f);
        }
    }

    private static void usage() {
        System.err.println("Usage:MakeDir path1 ...\n"+
                "Creates each path\n"+
                "Usage:MakeDir -d path1 ...\n"+
                "Deletes each path\n"+
                "Usage:MakeDir -r path1 path2\n"+
                "Renames from path1 to path2");
        System.exit(1);
    }

    private 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");
    }
}

文件写入数据示例(使用PrintWriter):

public class File {
    public static void main(String[] args) throws IOException{
        Scanner in = new Scanner(Paths.get("F:\\myfile.txt"),"UTF-8");
        PrintWriter out = new PrintWriter("F:\\myfile.txt");
        out.write("dajsdkasdjk");
        out.println("dkwadm");
        out.print("mdk");
        out.close();
        System.out.print(in.nextLine());
    }
}

RandomAccessFile:

RandomAccessFile的每个构造器构造器都需要一个参数model指定工作模式(r-随机读、rw-既读又写)适用于由大小已知的记录组成的文件。

  • seek()方法将记录从一处转移到另一处,然后读取或者修改记录。
  • getFilePointer()方法查找当前位置;
  • length()判断文件大小;
  • 继承自Object,不支持装饰。

读取随机访问文件示例:

public class RandomFile {
    static String file = "rtest.dat";

    public static void main(String[] args) throws IOException{
        RandomAccessFile rf = new RandomAccessFile(file, "rw");
        for(int i = 0; i < 7; i++){
            rf.writeDouble(i*1.414);
        }
        rf.writeUTF("The end of the file");
        rf.close();
        display();
        rf = new RandomAccessFile(file, "rw");
        rf.seek(5*8);
        rf.writeDouble(47.0001);
        rf.close();
        display();
    }
    static void  display() throws IOException{
        RandomAccessFile rf = new RandomAccessFile(file,"r");
        for(int i = 0; i < 7; i++){
            System.out.println("Value"+i+":"+rf.readDouble());
        }
        System.out.println(rf.readUTF());
        rf.close();
    }
}

标准I/O

包括 System.in,System.out,System.err,其中,后两者被包装成了PrintWriter对象,可直接使用,使用System.in时需要进行包装。

标准I/O重定向

可使用set方法重定向输入输出,指向所需的数据。操纵的是字节流,所以使用InputStream和OutputStream进行重定向。

新I/O(nio)

java nio 包中包含一个抽象类Buffer,其余类继承自Buffer,nio的主要目的是通过通道(Channel)和缓冲器(Buffer)提高速度。我们通过和缓冲器交互,把缓冲器派送到通道,通道要么向缓冲器派送数据,要么获得数据。唯一和通道直接交互的缓冲器是ByteBuffer类。

FileInputStream、OutputStream、RandomAccessFile可以通过getChannel方法产生FileChannel,下面是一个示例:

public class GetChannel {
    private static final int BSIZE = 1024;

    public static void main(String[] args) throws IOException{
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("Some text".getBytes()));
        fc.close();
        fc = new RandomAccessFile("data.txt","rw").getChannel();
        fc.position(fc.size());
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();
        fc = new FileInputStream("data.txt").getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
        fc.read(buffer);
        buffer.flip();    //获取从0到当前位置的数据
        while (buffer.hasRemaining()){
            System.out.print((char) buffer.get());
        }
    }
}

数据转换

缓冲器容纳的是普通的字节,为了把它们转换成字符,要在输入他们的时候进行编码,或者在输出时进行解码。下面是一个示例,将数据编码为不同格式:

public class AvailableCharSets {
    public static void main(String[] args) {
        SortedMap charSets = Charset.availableCharsets();
        Iterator iterator = charSets.keySet().iterator();
        while (iterator.hasNext()){
            String csName = iterator.next();
            System.out.print(csName);
            Iterator aliases = charSets.get(csName).aliases().iterator();
            if(aliases.hasNext()){
                System.out.print(": ");
            }
            while (aliases.hasNext()){
                System.out.print(aliases.next());
                if(aliases.hasNext())
                    System.out.print(",");
            }

            System.out.println();
        }
    }
}

输出结果:

Big5: csBig5
Big5-HKSCS: big5-hkscs,big5hk,Big5_HKSCS,big5hkscs
CESU-8: CESU8,csCESU-8
EUC-JP: csEUCPkdFmtjapanese,x-euc-jp,eucjis,Extended_UNIX_Code_Packed_Format_for_Japanese,euc_jp,eucjp,x-eucjp
EUC-KR: ksc5601-1987,csEUCKR,ksc5601_1987,ksc5601,5601,euc_kr,ksc_5601,ks_c_5601-1987,euckr
GB18030: gb18030-2000
......

如上所示,可对缓冲器数据进行编码,从而读取到正确的数据,而不是乱码。

获取基本类型

ByteBuffer可以从其容纳的字节中产生各种不同基本类型值的方法,下面是一个示例(rewind()方法返回数据开始部分):

public class GetData {
    private static final int BSIZE = 1024;

    public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        int i = 0;
        while (i++ < bb.limit())
            if(bb.get() != 0)
                System.out.print("nonzero");

        System.out.println(" i = " +i);
        bb.rewind();
        bb.asCharBuffer().put("Hoedy!");
        char c;
        while ((c = bb.getChar()) != 0)
            System.out.print(c+" ");
        bb.rewind();
        System.out.println();

        bb.asShortBuffer().put((short) 471142);
        System.out.println(bb.getShort());
        bb.rewind();

        bb.asIntBuffer().put(99471142);
        System.out.print(bb.getInt());
        bb.rewind();

    }
}

缓冲器的细节

Buffer由数据和可以高效地访问和操作这些数据的四个索引组成,分别是mark,position,limit和capacity。在缓冲器中插入和提取数据会更新这些索引。

内存映射文件

内存映射文件(MappedByteBuffer)允许我们创建和修改那些因为太大而不能放入内存的文件,有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。

一个例子:

public class LargeMappedFiles {
    static int length = 0x8FFFFFF;        //文件大小128MB
    public static void main(String[] args) throws Exception{
        MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel().map(FileChannel.MapMode.READ_WRITE,0,length);
        for(int i = 0; i < length; i++)
            out.put((byte)'x');
        System.out.println("Finished writing");
        for(int i = length/2; i < length/2 +6; i++){
            System.out.print((char) out.get(i));
        }
    }
}

通过内存映射文件,访问时只有一部分文件进入内存,其他部分交换出去,用这种方式,很大的文件也可以很容易修改,可以优化性能。

压缩

I/O类库中支持读写压缩格式的数据流,这些类继承自InputStream和OutputStream,因为压缩是按字节处理的。

具体压缩类如下:

压缩类 功能
CheckedInputStream GetCheckSum()为任何InputStream产生校验和(不仅是解压缩)
CheckedOutputStream GetCheckSum()为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream 压缩类的基类
ZipOutputStream 一个DeflaterOutputStream,用于将数据压缩成zip格式
GZIPOutputStream 一个DeflaterOutputStream,用于将数据压缩成Gzip格式
InflaterInputStream 解压缩类的基类
ZipInputStream 一个InflaterInputStream,用于解压缩zip文件
GZIPInputStream 一个InflaterInputStream,用于解压缩GZIP文件

压缩示例:

public class GZIPcompress {
    public static void main(String[] args) throws IOException{
        if(args.length == 0) {
            System.out.println("Usage: \nGZIPcompress " +
                    "\tUses GZIP compression to compress " +
                    "the file to test.gz");
            System.exit(1);
        }

        BufferedReader in = new BufferedReader(new FileReader(args[0]));
        BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("test.gz")));

        System.out.println("Writing file");
        int c;
        while ((c = in.read()) != -1){
            out.write(c);
        }
        in.close();
        out.close();

        System.out.println("Reading file");
        BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream("test.gz"))));
        String s;
        while ((s = in2.readLine()) != null)
            System.out.println(s);
    }
}

运行后可看到生成了压缩文件test.gz。

练习用的一些代码链接:

代码链接

原文链接:加载失败,请重新获取