Java IO流

IO

File对象

构造一个File对象需要传入文件路径:

public static void main(String[] args) {
    File file = new File("C:\\Users\\xiong\\Documents\\xiong\\pom.xml");
    System.out.println(file);
}

构造File对象时既可以传入绝对路径,也可以传入相对路径。File对象有三种方式表示的路径,一种时getPath(),返回构造方法传入的路径,一种是getAbsolutePath(),返回绝对路径,一种是getCannonicalPath(),它和绝对路径类似,但是返回的是规范路径:

public static void main(String[] args) throws IOException {
    File f = new File("..");
    System.out.println(f.getPath());
    System.out.println(f.getAbsolutePath());
    System.out.println(f.getCanonicalPath());
    
    // ..
	// C:\Users\xiong\Documents\xiong\..
	// C:\Users\xiong\Documents
}

文件和目录

File对象即可以表示文件,也可以表示目录,如果传入的文件或者目录不存在,代码也不会报错,因为构造一个对象不会真的进行磁盘的操作,只有调用File的某些方法的时候,才真正的进行磁盘操作。

File对象取到一个文件时,可以判断文件的大小和权限:

  • boolean canRead():是否可读
  • boolean canWrite():是否可写
  • boolean canExecute():是否可执行(对于目录而言,是否可执行表示能否列出它包含的文件和子目录)
  • long length():文件字节大小

常用方法

  • String getName():返回文件或目录名;
  • String getParent():返回上一级目录的绝对路径;
  • String getCanonicalPath():返回规范的路径;
  • boolean isDirectory():是否是目录;
  • boolean isFile():是否是文件;
  • long length():返回目录长度;
  • boolean exists():目录或文件是否存在;

InputStream

InputStream就是java标准库提供的基本的输入流。InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。这个抽象类的最重要的方法是 int read():

public abstract int read() throws IOException;

这个方法读取输入流的下一个字节,并返回字节表示的int值(0~255),如果已读到末尾,返回-1表示不能继续读取。

FileInputStream是InputStream的一个子类,它表示从文件流中读取数据。

public static void main(String[] args) throws IOException {
    File f = new File("C:\\Users\\xiong\\Documents\\xiong\\pom.xml");
    FileInputStream fileInputStream = new FileInputStream(f); // 创建流
    for (;;) {
        int read = fileInputStream.read();
        if (read == -1) {
            break;
        }
        System.out.println(read);
    }
    fileInputStream.close(); // 关闭流
}

在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。InputStream和OutPutStream都是通过close()方法来关闭流。关闭就会释放对应的底层资源。我们还要注意到在读取或写入IO流的过程中,可能会发生错误,例如文件不存在,文件权限导致读取失败等,这些底层错误由JVM自动封装了IOException异常并抛出。因此所有与IO操作相关的代码都必须正确处理IOException。我看上边的代码发现,如果上边的的代码报错抛异常的话,流就没法关闭。所以我们要通过try…finally来保证及时代码出错,流也能正确的关闭。

public static void main(String[] args) throws IOException {
    File f = null;
    FileInputStream fileInputStream = null;
    try {
        f = new File("C:\\Users\\xiong\\Documents\\xiong\\pom.xml");
        fileInputStream = new FileInputStream(f);
        for (;;) {
            int read = fileInputStream.read();
            if (read == -1) {
                break;
            }
            System.out.println(read);
        }
    } finally {
        fileInputStream.close();
    }
}

但是更推荐使用try-with-resource写法,让编译器自动给我们关闭流:

public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiong\\Documents\\xiong\\pom.xml")) {
        int i;
        while ((i = fileInputStream.read()) != -1) {
            System.out.println(i);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

缓冲

在读取流的时候一次读取一个字节的方式效率不高。我们可以采用一次读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区,一次读取多个字节效率要高很多。InputStream提供了两个重载方法来支持读取多个字节:

  • int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
  • int read(byte[] b, int off, int len):指定bytep[]数组的偏移量和最大填充数

利用上述方法一次读取多个字节时,需要先定义一个byte[]数组作为缓冲区,read()方法会尽可能多的读取自己到缓冲区,但不会超过缓冲区的大小。read()方法返回值不在是字节的int值,而是读取了多少个字节,当返回-1时,表示没有数据了。

public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiong\\Documents\\xiong\\pom.xml")) {
        byte[] bytes = new byte[1024];
        int i;
        while ((i = fileInputStream.read(bytes)) != -1) {
            System.out.println(i);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

OutputStream

OutputStream是Java标准库的基本输出流。和InputStream类似,它也是抽象类,它最重要的一个方法就是:

 public abstract void write(int b) throws IOException;

这个方法写入一个字节到输出流。虽然传入的是int参数,但是只会写入一个字节。

OutputStream也提供了close()方法来关闭流。同时它还有一个flush()方法,作用是将缓冲区的内容input到目的地。为什么会有这么个操作?因为向磁盘、网络写入数据的时候,考虑到效率问题,操作系统并不是输出一个字节就立刻写入到磁盘或网络,则是把字节先放入内存的一个缓存区里,等缓存区满了,在一次性写入到磁盘或者网络。使用flush()方法可以强制把缓冲区内容输出。通常我们不需要手动调用它,在缓存区满了后会自动调用,并且在调用close()方法之前也会调用。

FileOutputStream

将若干字节写入文件流:

public static void main(String[] args) {
    try (FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\xiong\\Documents\\xiong\\out.txt")) {
        String str = "test outputStream";
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        fileOutputStream.write(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

序列化

序列化是指把一个java对象变成二进制内容,本质上就是一个byte[]数组。序列化可以把byte[]保存到文件中或者通过网络传输出去。

有序列化就有反序列化,反序列化就是把一个二进制内容也就是byte[]数组变回java对象。

一个Java对象要能序列化必须实现java.io.Serializable接口,这个接口没有定义任何方法,我们把这样的空接口称为“标记接口”。

序列化

把一个java对象序列化,需要使用OutputStream,它负责把一个java对象写入一个字节流。

public void test() throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
        // 写入int:
        output.writeInt(12345);
        // 写入String:
        output.writeUTF("Hello");
        // 写入Object:
        output.writeObject(Double.valueOf(123.456));
    }
    System.out.println(Arrays.toString(buffer.toByteArray()));
}

反序列化

public void test() throws Exception {
    byte[] bytes;
    try (ByteArrayOutputStream buffer = new ByteArrayOutputStream();
         ObjectOutputStream output = new ObjectOutputStream(buffer)) {
        output.writeObject("Hello World");
        bytes = buffer.toByteArray();
    }

    try (ByteArrayInputStream bis =  new ByteArrayInputStream(bytes);
         ObjectInputStream intput = new ObjectInputStream(bis)) {
        String s = (String) intput.readObject();
        System.out.println(s);
    }
}

注意:这里的ByteArrayOutputStream一定要是ObjectOutputStream的,负责会报错。

其次要注意:反序列化时JVM直接构造出java对象,不调用构造方法,构造方法内部的代码不会被执行。

安全性

因为java的序列化和反序列化可以导致java对象能直接从byte[]创建,而不经过构造方法,因此,它存在一定的安全隐患。实际开发中,我们一般通过JSON这样的通用数据结构来实现。

Reader

Reader是java的IO库提供的另一个接口,和InputStream的区别是,InputStream是字节流,即以byte为单位,而Reader是字符流,以char为单位。

InputStreamReader
字节流,以byte为单位字符流,以char为单位
读取字节(-1,0~255):int read()读取字符(-1,0~65535):int read()
读到字节数组:int read(byte[] b)读到字符数组:int read(char[] c)

java.io.Reader是所有字符输入的流的超类,它的主要方法是:

public int read() throws IOException;

这个方法读取字符流的下一个字符,并返回字符表示的int,范围是0~65535,如果已读到末尾返回-1。

FileReader

FilleReader是reader的一个子类,它可以打开文件并获取Reader,下面代码演示了如何完整地读取一个FileReader的所有字符:

public void testFileReader() {
    try (FileReader fileReader = new FileReader("C:\\Users\\xiong\\Documents\\xiong\\pom.xml");){
        int i;
        while ((i = fileReader.read()) != -1) {
            System.out.println((char) i);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Reader也是一种资源,需要保证出错的时候也能正确的关闭,所以我们需要try(resource)来保证Reader无论有没有IO错误,都可以被关闭。

如果我们读取一个纯ASCIII编码的文本文件,是没有问题的,但如果文件包含中文,就会出现乱码,因为FileReader默认的编码与系统相关。要避免乱码,在创建FileReader的时候就要指定编码:

Reader reader = new FileReader("/src/test.txt", StanderCharsets.UTF_8);

Reader也提供了一次读取若干字符到char[]的方法:

public int read(char[] c) throws IOException

它返回实际读入的字符个数,最大不超过char[]数组的长度。返回-1表示结束。

利用这个方法,我们先设置一个缓冲区,每次尽可能地填充缓冲区:

public void testFileReader() {
    try (FileReader fileReader = new FileReader("C:\\Users\\xiong\\Documents\\xiong\\pom.xml");){
        char[] buffer = new char[1024];
        int i;
        while ((i = fileReader.read(buffer)) != -1) {
            System.out.println("read" + i + "chars");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Writer

Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出。

Writer和OutputStream的区别如下:

OutputStreamWriter
字节流,以byte为单位字符流,以char为单位
写入字节(0~255):void write(int b)写入字符(0~65535):void write(int c)
写入字节数组:void write(byte[] b)写入字符数组:void write(char[] c)
无对应方法写入String:void write(String s)

Writer是所有字符输出流的超类,它提供的方法主要有:

  • 写入一个字符(0~65535):void write(int c);
  • 写入字符数组的所有字符:void write(char[] c);
  • 写入String表示的所有字符:void write(String s)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值