JAVA IO学习笔记(一)IO入门

文章目录

    • File 类
      • File类的作用以及方法
      • 文件过滤器
    • 理解Java的IO流
      • 流的分类
        • 1、输入流和输出流
        • 2、字节流和字符流
        • 3、节点流和处理流
      • 字节流和字符流
        • InputStream & Reader
        • OutputStream & Writer
      • 缓冲流
      • 处理流的用法
      • 转换流
      • 推回输入流
      • 重定向标准输入/输出

大概看了一下《疯狂Java讲义》、《Java编程思想》、《Head First Java》的IO部分内容,三者在讲述I/O前,都先讲了File类的概念以及运用之后才开始讲述实现IO的流的概念等,且《Head First Java》在讲述文件输入输出前提了一页的缓冲流,让读者更易理解输入输出流。这里就大体按照《疯狂JAVA讲义》的内容过一遍,这本书讲得还是比较浅显易懂的。

  • java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入、输出两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。

  • 除此之外,Java的IO流使用了一种装饰器设计模式,它将IO流分成底层节点流和上层处理流。其中节点流用于和底层的物理存储节点直接关联——不同的物理节点获取节点流的方式可能存在一点差异,但程序可以把不同的物理节点流包装成统一的处理流,从而允许程序使用统一的输入、输出代码来读取不同的物理存储节点的资源

File 类

  • File类是java.io包下代表与平台无关的文件和目录,也就是说,如果希望在程序中操作文件和目录,都可以通过File类来完成。值得指出的是,不管是文件还是目录都是使用File来操作的,File能新建、删除、重命名文件和目录,File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入输出流。

File类的作用以及方法

  • File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。在默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性"user.dir"指定,通常也就是运行JVM时所在的路径

  • 一旦创建了File对象后,就可以调用File对象的方法来访问,File类提供了很多方法来操作文件和目录,下面列出一些比较常用的方法。

  • File类访问文件和目录的相关方法:

String getNmae(): 返回相关此File对象所表示的文件名或路径名(如果是路径,则返回最后一级子路径名)

String getPath(): 返回此File对象所对应的路径名

File getAbsoluteFile(): 返回此File对象的绝对路径

String getAbolutePath(): 返回此File对象所对应的绝对路径名

String getParent(): 返回此对象所对应目录的父目录名

boolean renameTo(File newName): 重命名此File对象所对应的文件或目录,如果重命名成功则返回true;否则返回false

  • File类中文件检测相关的方法:

boolean exists(): 判断File对象所对应的文件或目录是否存在

boolean canWrite(): 判断File对象所对应的文件和目录是否可写

boolean isFile(): 判断File对象所对应的是否是文件,而不是目录

boolean isDirectory(): 判断File对象所对应的是否是目录,而不是文件

boolean isAbsolute(): 判断File对象所对应的文件或目录是否是绝对路径。该方法消除了不同平台的差异,可以直接判断File对象是否为绝对路径。在Unix、Linux、BSD等系统上,如果路径名开头是一条斜线(/),则表明该File对象对应一个绝对路径;在Windows等系统上,如果路径开头是盘符,则说明他是一个绝对路径

  • File类中获取常规文件信息:

long lastModified(): 返回文件的最后修改时间

long length(): 返回文件内容的长度

  • File类中文件操作的相关方法:

boolean createNewFile(): 当次File对象所对应的文件不存在时,该方法将新建一个File对象所指定的新文件,如果创建成功则返回true,否则返回false

boolean delete(): 删除File对象所对应的文件或路径

static File createTempFile(String prefix, String suffix): 在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。prefix参数必须是3字节长。建议前缀用一个短的、有意义的字符串如"hjb"、“mail”。suffix参数可以为null,在这种情况下系统将使用默认的后缀".tmp"

static File createTempFile(String prefix, String suffix, File directory): 在directory所指定的目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过File类来调用。

void deleteOnExit(): 注册一个删除钩子,指定当JVM退出时,删除File对象所对应的文件和目录

  • File类中目录操作相关的方法:

boolean mkdir (): 试图创建一个File对象所对应的目录,如果创建成功,则返回true,否则返回false。调用该方法时File对象必须对应一个路径而不是一个文件。

String[] list(): 列出File对象的所有子文件名和路径名,返回String数组。

File[] listFiles(): 列出File对象的所有子文件和路径,返回File数组.

static File[] listRoots(): 列出系统的所有根路径。这是一个静态方法,可以直接通过File类来调用

文件过滤器

  • File类的list()方法可以接受一个FilenameFilter参数,通过该参数可以只列出符合条件的文件。

  • FilenameFilter接口里只包含了一个accept(File dir, String name)方法,该方法依次对指定File的所有子目录或者文件进行迭代,如果该方法返回true,则list()方法会列出该子目录或者文件

  • 因为FilenameFilter接口内只有一个抽象方法,所以它也是一个函数式接口,可使用Lambda表达式创建实现该接口的对象。

理解Java的IO流

  • Java中把不同的输入/输出源抽象表述为"流"(stream),stream是从起源(source)到接收(sink)的有序数据,也正是因为Java提供了这种IO流的抽象,所以我们才能使用一致的IO代码去读写不同IO流节点

流的分类

  • 按照不同的分类方式可以将流分为不同的类型,概念上存在重叠地方

1、输入流和输出流

按照流的流向区分,可以分为输入流和输出流

输入流:只能从中读取数据,而不能向其写入数据。

输出流:只能向其写入数据,而不能从中读取数据

关于输入输出的方向判别,我们要从程序运行所在内存的角度去判断。

Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。它们都是一些抽象基类,无法直接创建实例。

2、字节流和字符流

  • 字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同——字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符

  • 字节流主要由InputStream和OutputStream作为基类,而字符流则由Reader和Writer作为基类。

3、节点流和处理流

  • 按照流的角色区分,分为节点流和处理流

  • 节点流:从/向一个特定的IO设备(如磁盘/网络)读/写数据的流,节点流也被称为低级流。

  • 处理流:用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。处理流也被称为高级流

  • 区别:使用节点流进行输入输出时,程序直接连接到实际的数据源,和实际的输入输出节点连接;而当使用处理流进行输入输出时,程序并不会直接连接到实际的数据源,没有和实际的输入输出节点连接。

  • 这样使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化

实际上,Java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出功能,因此处理流也被称为包装流。

字节流和字符流

InputStream & Reader

  • InputStream 和 Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将称为所有输入流的模板,所以他们的方法是所有输入流都可使用的方法

在InputStream里包含如下三个方法:

int read(): 从输入流中读取单个字节,返回所读取的字节数据(字节数据可以直接转换为类型int)

int read(byte[] b): 从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数

int read(byte[] b, int off, int len):从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数

  • 在Reader里包含同上三个方法,不过是数据类型换成char

  • 下面使用FileInputStram来读取自身的效果

import java.io.FileInputStream;
import java.io.IOException;
public class IOP {
    public static void main(String[] args) throws IOException {
        //创建字节输入流
        FileInputStream fis = new FileInputStream("FileInputStream.java");
        //创建一个长度为1024的byte数组
        byte[] bbuf = new byte[1024];
        //用于保存实际读取的字节数
        int hasRead = 0;
        //使用循环来重复读取过程
        while((hasRead = fis.read(bbuf)) > 0) System.out.println(new String(bbuf, 0, hasRead));
        fis.close();
    }
}
  • 最后使用了close()方法来关闭该文件输入流,与JDBC编程一样,程序里打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该是显式关闭文件IO资源。JAVA 7 改写了所以的IO资源类,它们都实现了AutoCloseable接口,因此都可通过自动关闭资源的try语句来关闭这些IO流。

  • 下面使用FileReader来读取自身的效果,因为下面使用了自动关闭资源的try语句,则无需使用close()方法

import java.io.FileInputStream;
import java.io.IOException;
public class IOP {
    public static void main(String[] args) throws IOException {
        //创建字符输入流
        try(FileReader fr = new FileReader("C:\\Users\\Leant\\Desktop\\Test.txt")){
            //创建一个长度为32的字符数组
            char[] cbuf = new char[32];
            //用于保存实际读取的字符数
            int hasRead = 0;
            //使用循环来重复读取
            while ((hasRead = fr.read(cbuf)) > 0) System.out.println(new String(cbuf, 0, hasRead));
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
  • 与之前的字节流输入并不太大不同;使用try()语句可以保证输入流一定会被关闭

  • 除了以上的方法之外,InputStream和Reader还支持以下几个方法来移动记录指针

void mark(int readAheadLimit): 在记录指针当前位置记录一个标记(mark)

boolean markSupported(): 判断此输入流是否支持mark()操作,即是否支持记录标记

void reset(): 将此流的记录指针重新定位到上一次记录标记(mark)的位置

long skip(long n): 记录指针向前移动n个字节/字符

OutputStream & Writer

  • 将数据输出到指定输出流中,参数的使用类似上面提到的输入流的方法参数

void write(int c)

void write(byte[]/char[] buf)

void write(byte[]/char[] buf, int off, int len)

下面用FileInputStream来执行输入,并使用FileOutputStream来执行输出,用以实现复制Test.txt文件的功能

public static void main(String[] args) throws IOException {
        try(//创建字节输入流
            FileInputStream fis = new FileInputStream("C:\\Users\\Leant\\Desktop\\Test.txt");
            //创建字节输出流
            FileOutputStream fos = new FileOutputStream("C:\\Users\\Leant\\Desktop\\Test2.txt")
            ){
            byte[] buf = new byte[1024];
            int hasRead = 0;
            while((hasRead = fis.read(buf)) > 0){
                fos.write(buf, 0, hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
  • 使用Java的IO流执行输出时,不要忘记关闭输出流,关闭输出流除可以保证流的物理资源被回收之外,可能还可以将输出流缓冲区中的数据flush到物理节点里(因为在执行close()方法之前,自动执行输出流的flush()方法)。

缓冲流

  • 缓冲流实质上是一个处理流,目的是起缓冲作用
  • 优点:累计数据到一个大数据块后再实际进行输出,这样可以减少对资源的读写次数而提升效率
  • 缓冲流的数据存储在缓冲区中
输入缓冲流 输出缓冲流
BufferedInputStream BufferedOutputStream
BufferedReader BufferedWriter
  • 缓冲流都有两个构造器,其中一个构造器只有一个参数,为输入/输出流,另一个构造器有两个参数,一个为输入/输出流,另一个参数指定size大小

  • 其对应方法主要有

void flush(): 缓冲区未满也可以使用该方法强制缓冲区立即写入

void close(): 将数据从缓冲区内写出并且关闭响应的流

输入缓冲流常用: String readLine() : 读取一行的内容(即读取数据直至遇到\r\n),返回的内容是换行符前的数据,不包括换行符

输出缓冲流常用:void nextLine(): 调用本系统的换行符(Windows下为\r\n)

处理流的用法

  • 处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,让程序员只需关系高级流的操作

  • 使用处理流的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的IO设备、文件交互

  • 如何识别处理流?

    看该流的构造器参数,其参数为已存在的流而不是一个物理节点,那么这种流一定是处理流,而所有节点流都是直接以物理IO节点作为构造器参数的

使用处理流的两点优势:①对于开发人员来说,使用处理流进行输入/输出操作更简单;②使用处理流的执行效率更高

  • 下面程序使用PrintStream处理流来包装OutputStream, 使用处理后的输出流在输出时将更加方便
public static void main(String[] args) {
    try(
            FileOutputStream fos = new FileOutputStream("C:/Users/Leant/Desktop/Test.txt");
            PrintStream ps = new PrintStream(fos)
    ){
        //使用PrintStream执行输出
        ps.println("普通字符串");
        //直接使用PrintStream输出对象
        ps.println(new IOP());
    }catch (IOException e){
        e.printStackTrace();
    }
}
  • 上面的程序先定义了一个节点输出流FileOutputStream,然后程序使用PrintStream包装了该节点输出流,最后使用PrintStream输出字符串、输出对象。

在使用处理流包装了底层及节点流之后,关闭输入输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流。

  • Another example:

  • 使用字符串作为物理节点的字符输入输出流

public static void main(String[] args) {
    String src = "Test1\nTest2\nTest3\nTest4\nTest5\n";
    char[] buffer = new char[32];
    int hasRead = 0;
    try(StringReader sr = new StringReader(src)){
        while ((hasRead = sr.read(buffer)) > 0) System.out.println(new String(buffer, 0, hasRead));
    }catch (IOException e){
        e.printStackTrace();
    }
    try(	//创建StringWriter时实际上以一个StringBuffer作为输出节点
        	StringWriter sw = new StringWriter(20)
    	){
        sw.write("Test5\nTest4\nTest3\nTest2\nTest1");
        System.out.println(sw);
        System.out.println(sw.toString());
    }catch (IOException e){
        e.printStackTrace();
    }
  • 上面程序与前面使用FileReader和FileWriter吃程序基本相似,只是在创建StringReader和StringWriter对象时传入的是字符串节点而不是文件节点。由于String是不可变的字符串对象,所以StringWriter使用StringBuffer作为输出节点

转换流

  • 输入输出流体系中还提供了两个转换流,这两个转换流用于实现将字节流转换为字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输入流转换成字符输出流。

  • 下面以获取键盘输入为例来介绍转换流的用法

public static void main(String[] args) {
    try(
            InputStreamReader reader = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(reader)
            ){
        String line = null;
        while((line = br.readLine()) != null){
            if(line.equals("exit")) System.exit(1);
            System.out.println("键盘输入的内容为"+line);
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}
  • Java中System.in为标准输入,也即键盘输入。但这个标准输入流是InputStream类的实例,使用不太方便,而且键盘输入的内容都为文本内容,所以我们使用InputStreamReader将其转换为字符输入流。而普通的Reader读取输入内容时也不够方便,可以将普通的Reader流包装成BufferedReader,利用BudderedReader的readLine()方法一次读取一行的内容.

由于BufferedReader具有一个readLine()方法,可以非常方便地一次读入一行内容,所以经常把读取文本内容的输入流包装成BufferedReader,用来方便地读取输入流的文本内容。

值得注意的是,BufferedReader流具有缓冲功能,一次读取一行文本,以换行符为标志,若它没有读到换行符则程序阻塞。

推回输入流

  • 在输入输出体系中,有两个特殊的流与众不同,分别是PushbackInputStream和PushbackReader, 他们都提供了如下三个方法:

void unread(int b)

void unread(byte[]/char[] b)

void unread(byte[]/char[] b, int off, int len)

  • 这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
public static void main(String[] args) {
    try(
            PushbackReader pr = new PushbackReader(new FileReader("C:/Users/Leant/Desktop/Test2.txt"),64)
            ){
        char[] buf = new char[32];
        //用以保存上次读取的字符串内容
        String lastContent = "";
        int hasRead = 0;
        while ((hasRead = pr.read(buf)) > 0){
            String content = new String(buf, 0, hasRead);
            int targetIndex = 0;
            //将上次读取的字符串和本次读取的字符串拼接
            //查看是否包含目标字符串,如果包含目标字符串
            if ((targetIndex = (lastContent + content).indexOf("ful")) > 0){
                //将本次内容和上次内容一起退回缓冲区
                pr.unread((lastContent+content).toCharArray());
                //重新定义一个长度为targetIndex的char数组
                if(targetIndex > 32) buf = new char[targetIndex];
                //在此读取指定长度的内容
                pr.read(buf, 0, targetIndex);
                //打印读取的内容
                System.out.println(new String(buf, 0, targetIndex));
                System.exit(0);
            }else {
                System.out.println(lastContent);
                lastContent = content;
            }
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

重定向标准输入/输出

  • Java的标准输入/输出分别通过System.in和System.out来代表,在默认情况下它们分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到屏幕

  • 在System类里提供了如下三个重定向标准输入/输出的方法

static void setErr(PrintStream err): 重定向“标准”错误输出流

static void setIn(InputStream in): 重定向“标准”输入流

static void setOut(PrinterStream out): 重定向“标准”输出流

public static void main(String[] args) {
    try(PrintStream ps = new PrintStream(new FileOutputStream("C:/Users/Leant/Desktop/Test.txt"))){
        System.setOut(ps);
        System.out.println("我想做个大佬呀\r\n啥时候我能做个大佬呀\r\n我想去BAT做保洁");
    }catch (IOException e){
        e.printStackTrace();
    }
}
  • 创建了一个PrintStream输出流,并将系统的标准输出重定向到该PrintStream输出流。这意味着标准输出不再输出到屏幕而是输出到Test.txt文件

  • 再来个重定向输出的例子

public static void main(String[] args) {
    try(FileInputStream fis = new FileInputStream("C:/Users/Leant/Desktop/Test.txt")){
        System.setIn(fis);
        Scanner sc = new Scanner(System.in);
        sc.useDelimiter("\n");
        while(sc.hasNext()){
            System.out.println("重定向后输入的东西是:"+sc.next());
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

希望能对还在学习的朋友起到一点帮助,学习IO实在有点累,Java的I/O系统提供的类好多…

你可能感兴趣的:(Java学习笔记)