Java IO

Wesley13
• 阅读 638

**概述
**
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 体系大体图
Java 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;
    }
}

显示结果:
Java IO

不指定参数运行情况:
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里包含如下三个方法:
Java IO

在Reader中包含如下三个方法:
Java IO

对比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也非常相似。

在OutputStream里包含如下三个方法:
Java IO

在Writer中包含如下方法:
Java IO
因为字符流直接以字符作为操作单位,所以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();
        }
    }

}

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

输入输出流体系大体结构图(java.io):
Java IO

上表中的缓冲流主要是用于提高输入、输出的效率,但增加缓冲功能后一定要使用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 IO java.lang.System

public final class System extends Object

Java IO

在这个类中提供了三个重定向标准输入输出的方法:
Java IO

示例,重定向标准输出:

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参数,该参数指定其访问模式,具有以下四个值:
Java IO

示例,使用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序列化时无须理会该属性值。

点赞
收藏
评论区
推荐文章
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
面试避坑手册之 Java字节流和字符流总结IO流!
从接收输入值说起在日常的开发应用中,有时候需要直接接收外部设备如键盘等的输入值,而对于这种数据的接收方式,我们一般有三种方法:字节流读取,字符流读取,Scanner工具类读取。字节流读取直接看一个例子:cpublicclassDemo01SystemInpublicstaticvoidmain(Stringargs)throw
Wesley13 Wesley13
3年前
java多种文件复制方式以及效率比较
1.背景java复制文件的方式其实有很多种,可以分为传统的字节流读写复制FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream传统的字符流读写复制FileReader,FileWriter,BufferWriter,Buffered
Wesley13 Wesley13
3年前
NIO入门
1、I/O输入输出,所有的IO都被视作是单个字节的移动,通过stream对象一次移动一个字节。流IO负责把对象转换为字节,然后再转换为对象。NIO提供了二套NIO,一套是针对标准输入输出NIO,另一套是网络编程NIO2、流与块的比较NIO和IO最大的区别是数据打包和传输方式,IO是以流的方式来处理数据,而NIO是以块的方式处理数据。面向块的IO
Wesley13 Wesley13
3年前
IO(输入输出)
IO流有很多种,按照操作数据的不同,可以分为字节流和字符流,按照数组传输方向的不同又可分为输入流和输出流。字节流的输入输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入输出流分别用java.io.Reader和java.io.Writer表示。!(https://oscimg
Wesley13 Wesley13
3年前
Java IO输入输出
学前知道Java的IO使用“流”的概念来表示。IO流涉及到数据源和目的地。流,是从源“流向”目的的数据流。Java将各种数据源和目标之间数据的传输统一抽象为流,通过对流对象的操作来完成I/O功能。输入输出实际都是对内存而言的。数据源可以是键盘、文件、应用程序、鼠标、网络连接。
Wesley13 Wesley13
3年前
Java输入输出流
1.什么是IO   Java.io是大多数面向数据流的输入/输出类的主要软件包。此外,Java也对块传输提供支持,在核心库java.nio中采用的便是块IO。  流IO的好处是简单易用,缺点是效率较低。块IO效率很高,但编程比较复杂。   JavaIO模型 :   Java的IO
Wesley13 Wesley13
3年前
Java IO流
IO流框架!IO框架(https://oscimg.oschina.net/oscnet/d6baa9ffd0adc3ecffd9fef90e48936437f.png)IO流概述IO即输入与输出,Java把不同来源和目标的数据抽象为流,在此基础上对流进行数据操作IO流分类按流向分:
Wesley13 Wesley13
3年前
Java工作流引擎
1.关键字工作流开发框架权限设计、用户组、岗位、集团模式应用.java工作流程引擎,.net工作流引擎,工作流开发框架1.相关的表结构\相关组织\表结构。SELECTNo,Name,ParentNoFROMport\_dept;  部门。SELECTNo,Name,Adminer,AdminerNam
Wesley13 Wesley13
3年前
Java NIO之缓冲区
JavaNIO之Buffer  Java传统的I/O模型是面向单个字节的,它将输入输出抽象为字节流或字符流。这种单个字节的读取或写入模型的效率比较低,而且不符合操作系统的I/O特点。操作系统的IO是面向字节块的,通常是直接从磁盘中读取一块数据到内存或写入一块数据到磁盘。JavaNIO提供了缓冲区来实现字节块的读写。