javaIO

IO

IO是指Input/Output,即输入和输出。以内存为中心
Input指从外部读入数据到内存 例如,把文件从磁盘读取到内存,从网络读取数据到内存等等
Output指把数据从内存输出到外部,例如,把数据从内存写入到文件,把数据从内存输出到网络等等。

I是把外部输入内存,O是从内存输出

IO的传输方式: 字节 和 字符

字节流接口:InputStream/OutputStream; 二进制数据以byte为最小单位在InputStream/OutputStream中单向流动
字符流接口:Reader/Writer。            字符数据以char为最小单位在Reader/Writer中单向流动

同步IO和异步IO

同步IO是指,读写IO时代码必须等待数据返回后才继续执行后续代码,它的优点是代码编写简单,缺点是CPU执行效率低
异步IO是指,读写IO时仅发出请求,然后立刻执行后续代码,它的优点是CPU执行效率高,缺点是代码编写复杂

File

File f = new File("C:\\Windows\\notepad.exe");
构造File对象时,既可以传入绝对路径,也可以传入相对路径

获取路径的三种方式:
getPath()           获取的是相对路径
getAbsolutePath()   获取的是绝对路径
getCanonicalPath()  获取的是规范的绝对路径 就是会把.. .计算之后转换一下

File.separator  这个是根据系统来判断是 \ 还是 /

文件和目录

File对象既可以表示文件,也可以表示目录

构造一个File对象  即使传入的文件或目录不存在,代码也不会出错
因为构造一个File对象,并不会导致任何磁盘操作  只有当我们调用File对象的某些方法的时候,才真正进行磁盘操作

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

创建和删除文件

创建文件
File f = new File("/usr/local/aa");
f.createNewFile()
删除文件
f.delete()

创建临时文件
File f = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
f.deleteOnExit(); // JVM退出时自动删除

遍历文件和目录

当File对象表示一个目录时,
可以使用list()和listFiles()列出目录下的文件和子目录名。
listFiles()提供了一系列重载方法,
可以过滤不想要的文件和目录

和文件操作类似,File对象如果表示一个目录,可以通过以下方法创建和删除目录:

boolean mkdir():创建当前File对象表示的目录;
boolean mkdirs():创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;
boolean delete():删除当前File对象表示的目录,当前目录必须为空才能删除成功。

Path

Java标准库还提供了一个Path对象,它位于java.nio.file包。Path对象和File对象类似,但操作更加简单

Path p1 = Paths.get(".", "project", "study"); // 构造一个Path对象
System.out.println(p1);
Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
System.out.println(p2);
Path p3 = p2.normalize(); // 转换为规范路径
System.out.println(p3);
File f = p3.toFile(); // 转换为File对象
System.out.println(f);
for (Path p : Paths.get("..").toAbsolutePath()) { // 可以直接遍历Path
    System.out.println("  " + p);
}

InputStream

InputStream就是Java标准库提供的最基本的输入流。它位于java.io这个包里。java.io包提供了所有同步IO的功能
InputStream不是一个接口,而是一个抽象类,这是所有输入流的超类,这个类里最重要的一个方法就是 int read()
当read返回的是-1 时,说明读取完毕


InputStream input = null;
try {
    input = new FileInputStream("src/readme.txt");
    int n;
    while ((n = input.read()) != -1) { // 利用while同时读取并判断
        System.out.println(n);
    }
} finally {
    if (input != null) { input.close(); }
}
但时上边这种写法不够简练
需要使用java7的try(resource)的语法,只需要编写try语句,让编译器自动为我们关闭资源
推荐这种
try (InputStream input = new FileInputStream("src/readme.txt")) {
    int n;
    while ((n = input.read()) != -1) {
        System.out.println(n);
    }
} // 编译器在此自动为我们写入finally并调用close()

缓冲
read的时候一个一个读取效率很低
很多流支持一次性读取多个字节到缓冲区
InputStream提供了两个重载方法来支持读取多个字节:
int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填充数
利用上述方法一次读取多个字节时,需要先定义一个byte[]数组作为缓冲区,
try (InputStream input = new FileInputStream("src/readme.txt")) {
    // 定义1000个字节大小的缓冲区:
    byte[] buffer = new byte[1000];
    int n;
    while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
        System.out.println("read " + n + " bytes.");
    }
}

InputStream实现类
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
    int n;
    while ((n = input.read()) != -1) {
        System.out.println((char)n);
    }
}

阻塞

int n;
n = input.read(); // 必须等待read()方法返回才能执行下一行代码
int m = n;
执行到第二行代码时,必须等read()方法返回后才能继续。

注意:重点和易错点

很重要,之前一次美观网络请求接口,导致docker死了

在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的
应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时关闭,让操作系统的资源释放掉
否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行

OutputStream

跟InputStream基本上一样 也是抽象类
里边的重要方法就是 write(int b) 注意参数是int 
但是这个方法会写入一个字节到输出流。要注意的是,虽然传入的是int参数,
但只会写入一个字节,即只写入int最低8位表示字节的部分

缓冲区
因为磁盘和网络写入数据的时候,出于效率的考虑,操作系统不会输出一个字节,就马上写入文件或者发送
只有当超过了缓冲区,才会发送消息。

如果要想写入的文件,没超过缓冲区的时候也写入文件,需要手动调用flush()

阻塞
和InputStream一样,OutputStream的write()方法也是阻塞的

OutputStream实现类

ByteArrayOutputStream的实现
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
    output.write("Hello ".getBytes("UTF-8"));
    output.write("world!".getBytes("UTF-8"));
    data = output.toByteArray();
}
System.out.println(new String(data, "UTF-8"));

Filter模式

FileInputStream:从文件读取数据,是最终数据源;
ServletInputStream:从HTTP请求读取数据,是最终数据源;
Socket.getInputStream():从TCP连接读取数据,是最终数据源;

各种各样的读流模式,如果要想扩展怎么办。肯定不是对具体的某个实现方法进行扩展,那样面向对象就没意义了
JAVA提供了
                 ┌─────────────┐
                 │ InputStream │
                 └─────────────┘
                       ▲ ▲
┌────────────────────┐ │ │ ┌─────────────────┐
│  FileInputStream   │─┤ └─│FilterInputStream│
└────────────────────┘ │   └─────────────────┘
只需要定义自己的FilterInputStream 就可以了,还可以写多个进行组合使用。
例子:
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data = "hello, world!".getBytes("UTF-8");
        try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {
            int n;
            while ((n = input.read()) != -1) {
                System.out.println((char)n);
            }
            System.out.println("Total read " + input.getBytesRead() + " bytes");
        }
    }
}

class CountInputStream extends FilterInputStream {
    private int count = 0;

    CountInputStream(InputStream in) {
        super(in);
    }

    public int getBytesRead() {
        return this.count;
    }

    public int read() throws IOException {
        int n = in.read();
        if (n != -1) {
            this.count ++;
        }
        return n;
    }

    public int read(byte[] b, int off, int len) throws IOException {
        int n = in.read(b, off, len);
        this.count += n;
        return n;
    }
}

操作Zip

ZipInputStream是一种FilterInputStream
JarInputStream->ZipInputStream->InflaterInputStream->FilterInputStream->InputStream
JarInputStream它增加的主要功能是直接读取jar文件里面的MANIFEST.MF文件

读取zip包
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
    ZipEntry entry = null;
    while ((entry = zip.getNextEntry()) != null) {
        String name = entry.getName();
        if (!entry.isDirectory()) {
            int n;
            while ((n = zip.read()) != -1) {
                ...
            }
        }
    }
}

写入zip包
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(...))) {
    File[] files = ...
    for (File file : files) {
        zip.putNextEntry(new ZipEntry(file.getName())); // 考虑文件目录输入相对路径
        zip.write(getFileDataAsBytes(file));
        zip.closeEntry();
    }
}

读取classpath资源

在classpath中的资源文件 路径总是以/开头,我们先获取当前的Class对象,
然后调用getResourceAsStream()就可以直接从classpath读取任意的资源文件:
try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
    // TODO:
}
Class对象的getResourceAsStream()可以从classpath中读取指定资源
Properties props = new Properties();
props.load(inputStreamFromClassPath("/default.properties"));
props.load(inputStreamFromFile("./conf.properties"));

序列化

什么是序列化?
序列化就是把一个java对象变成二进制,本质上就是一个byte[]数组

为什么要序列化?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了

什么是反序列化?
即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化
保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下
public interface Serializable {
}
Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface)
实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法

怎么进行序列化?
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)); // 因为Double已经实现了Serializable接口
}
System.out.println(Arrays.toString(buffer.toByteArray()));
ObjectOutputStream既可以写入基本类型,如int,boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。

怎么进行反序列化?
和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:

try (ObjectInputStream input = new ObjectInputStream(...)) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}
除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型。

readObject是很有可能抛出异常的
1. ClassNotFoundException 找不到类
2. InvalidClassException 类不匹配

通常为了防止异常发生,通常用IDE生成一个serialVersionUID静态变量
如果增加或修改了字段,
可以改变serialVersionUID的值,
public class Person implements Serializable {
    private static final long serialVersionUID = 2709425275741743919L;
}

安全性
因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,


可序列化的Java对象必须实现java.io.Serializable接口
类似Serializable这样的空接口被称为“标记接口”(Marker Interface);

Reader

Reader和InputStream的区别是什么?
InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取

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

FileReader
FileReader是Reader的一个子类,它可以打开文件并获取Reader。下面的代码演示了如何完整地读取一个FileReader的所有字符:
public void readFile() throws IOException {
    // 创建一个FileReader对象:
   // Reader reader = new FileReader("src/readme.txt"); // 字符编码是???  但如果文件中包含中文,就会出现乱码
    Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
    for (;;) {
        int n = reader.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
            break;
        }
        System.out.println((char)n); // 打印char
    }
    reader.close(); // 关闭流
}

还提供了一次性读取到char[]数组的方法
public int read(char[] c) throws IOException

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

public void readFile() throws IOException {
    try (Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8)) {
        char[] buffer = new char[1000];
        int n;
        while ((n = reader.read(buffer)) != -1) {
            System.out.println("read " + n + " chars.");
        }
    }
}

CharArrayReader
CharArrayReader可以在内存中模拟一个Reader,它的作用实际上是把一个char[]数组变成一个Reader,
这和ByteArrayInputStream非常类似:

try (Reader reader = new CharArrayReader("Hello".toCharArray())) {
}

StringReader
StringReader可以直接把String作为数据源,它和CharArrayReader几乎一样:

try (Reader reader = new StringReader("Hello")) {
}

Reader和InputStream有什么关系?

除了特殊的CharArrayReader和StringReader,
普通的Reader实际上是基于InputStream构造的,
因为Reader需要从InputStream中读入字节流(byte),
然后,根据编码设置,再转换为char就可以实现字符流。
如果我们查看FileReader的源码,它在内部实际上持有一个FileInputStream。

try (Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt"), "UTF-8")) {
    // TODO:
}
上述代码实际上就是FileReader的一种实现方式。
 使用InputStreamReader,可以把一个InputStream转换成一个Reader。

Writer

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

OutputStream	                            Writer
字节流,以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是所有字符输出流的超类,它提供的方法主要有

FileWriter
try (Writer writer = new FileWriter("readme.txt", StandardCharsets.UTF_8)) {
    writer.write('H'); // 写入单个字符
    writer.write("Hello".toCharArray()); // 写入char[]
    writer.write("Hello"); // 写入String
}

CharArrayWriter
CharArrayWriter可以在内存中创建一个Writer,它的作用实际上是构造一个缓冲区,可以写入char,
最后得到写入的char[]数组,这和ByteArrayOutputStream非常类似:

try (CharArrayWriter writer = new CharArrayWriter()) {
    writer.write(65);
    writer.write(66);
    writer.write(67);
    char[] data = writer.toCharArray(); // { 'A', 'B', 'C' }
}

StringWriter

StringWriter也是一个基于内存的Writer,它和CharArrayWriter类似。
实际上,StringWriter在内部维护了一个StringBuffer,并对外提供了Writer接口。

OutputStreamWriter
除了CharArrayWriter和StringWriter外,普通的Writer实际上是基于OutputStream构造的,
它接收char,然后在内部自动转换成一个或多个byte,并写入OutputStream。
因此,OutputStreamWriter就是一个将任意的OutputStream转换为Writer的转换器:

try (Writer writer = new OutputStreamWriter(new FileOutputStream("readme.txt"), "UTF-8")) {
    // TODO:
}
上述代码实际上就是FileWriter的一种实现方式。这和上一节的InputStreamReader是一样的

PrintStream和PrintWriter

PrintStream是一种FilterOutputStream,它在OutputStream的接口上,
额外提供了一些写入各种数据类型的方法:

写入int:print(int)
写入boolean:print(boolean)
写入String:print(String)
写入Object:print(Object),实际上相当于print(object.toString())
...
以及对应的一组println()方法,它会自动加上换行符
我们经常使用的System.out.println()实际上就是使用PrintStream打印各种数据


PrintStream和OutputStream相比
除了添加了一组print()/println()方法,可以打印各种数据类型,比较方便外,
它还有一个额外的优点,就是不会抛出IOException,这样我们在编写代码的时候,就不必捕获IOException


PrintWriter
PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,
它的print()/println()方法最终输出的是char数据。两者的使用方法几乎是一模一样的:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值