zoukankan      html  css  js  c++  java
  • 12

    IO

    以内存为中心:

    • Input指从外部读入数据到内存,如把文件从磁盘读取到内存,从网络读取数据到内存等;

    • Output指把数据从内存输出到外部,如把数据从内存写入到文件,把数据从内存输出到网络等。

    IO流是一种顺序读写数据的模式,特点是单向流动。

    InputStream/OutputStream

    字节流接口

    IO流以byte(字节)为最小单位,因此也称为字节流。在Java中,InputStream代表输入字节流,OutputStream代表输出字节流,是最基本的两种IO流。

    Reader/Writer

    字符流接口

    如果需要读写的是字符,并且字符不全是单字节表示的ASCII字符,那么按照char来读写更加方便,这种流称为字符流。

    Java提供了Reader和Writer表示字符流,字符流传输的最小数据单位是char。Reader和Writer本质上是一个能自动编解码的InputStream和OutputStream。使用Reader,数据源虽然是字节,但读入的数据都是char类型的字符,原因是Reader内部把读入的byte做了解码,转换成了char。使用InputStream,读入的数据和原始二进制数据一模一样,是byte[]数组,但是可以自己把二进制byte[]数组按照某种编码转换为字符串。选择Reader和InputStream,取决于具体的使用场景,如果数据源不是文本,就只能使用InputStream,如果数据源是文本,使用Reader更为方便。

    同步和异步

    同步IO:读写IO时代码必须等待数据返回后才继续执行后续代码,优点是代码编写简单,缺点是CPU执行效率低;

    异步IO:读写IO时仅发出请求,然后立刻执行后续代码,优点是CPU执行效率高,缺点是代码编写复杂。

    Java标准库的包java.io提供了同步IO,而java.nio则是异步IO。InputStream、OutputStream、Reader和Writer都是同步IO的抽象类,对应的具体实现类,以文件为例,有FileInputStream、FileOutputStream、FileReader和FileWriter。

    1. File对象

    Java的标准库java.io提供了File对象来操作文件和目录。

    要构造一个File对象,需要传入文件路径,既可以传入绝对路径,也可以传入相对路径。绝对路径是以根目录开头的完整路径;传入相对路径时,相对路径前面加上当前目录就是绝对路径。

    注意:Windows平台使用“”作为路径分隔符,在Java字符串中需要用“”表示一个“”。Linux平台使用“/”作为路径分隔符。

    File对象有3种形式表示的路径:

    ①getPath():返回构造方法传入的路径;

    ②getAbsolutePath():返回绝对路径;

    ③getCanonicalPath():和绝对路径类似,但是返回的是规范路径。

    文件和目录

    File对象既可以表示文件,也可以表示目录。构造File对象不会导致任何磁盘操作,只有调用File对象的某些方法时才真正进行磁盘操作。调用isFile()判断该File对象是否是一个已存在的文件;调用isDirectory()判断File对象是否是一个已存在的目录。

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

    • boolean canRead()

    • boolean canWrite()

    • boolean canExecute()

    • boolean length()

    对目录而言,是否可执行表示能否列出它包含的文件和目录。

    创建和删除文件

    当File对象表示一个文件时,可以通过createNewFile()创建一个新文件,用delete()删除文件。File对象提供了createTempFile()来创建一个临时文件,以及deletOnExit()在JVM退出时自动删除该文件。

    遍历文件和目录

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

    File对象表示一个目录时,可以通过以下方法创建和删除目录:

    • boolean mkdir()

    • boolean mkdirs():在必要时将不存在的父目录也创建出来;

    • boolean delete():当前目录必须为空才能删除成功。

    Path

    Java标准库提供的Path对象位于java.nio.file包,Path对象和File对象类似,但操作更加简单。需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便。

    2. InputStream

    不是一个接口,而是一个抽象类,它是所有输入流的超类。int read()方法读取输入流的下一个字节,并返回字节表示的int值(0~255)。如果已读到末尾,返回-1表示不能继续读取。

    注意:打开一个文件进行读写,完成后要及时关闭,以便操作系统把资源释放掉。InputStream和OutputStream都是通过close()方法来关闭流,关闭流就会释放对应的底层资源。总是使用try(resource)来保证InputStream正确关闭。

    缓冲

    在读取流时,一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。InputStream提供了两个重载方法来支持读取多个字节:

    • int read(byte[] b):返回读取的字节数;

    • int read(byte[] b, int off, int len):指定byte数组的偏移量和最大填充数。

    阻塞

    在调用InputStream的read()方法读取数据时,read()方法是阻塞的,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底花费多长时间。

    InputStream实现类

    FileInputStream:从文件获取输入流;

    ByteArrayInputStream:可以在内存中模拟一个InputStream。实际上是把一个byte[]数组在内存中变成一个InputStream。

    3. OutputStream

    所有输出流的超类。void write(int b)方法写入一个字节到输出流。虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分。close()方法关闭输出流,以便释放系统资源。flush()方法将缓冲区的内容真正输入到目的地,缓冲区写满了或者调用close()方法前OutputStream会自动调用它。

    FileOutputStream

    每次写入一个字节非常麻烦,更常见的方法是一次性写入若干字节,可以用OutputStream提供的重载方法void write(byte[])来实现。

    阻塞

    OutputStream的write()方法也是阻塞的。

    OutputStream实现类

    FileOutputStream:从文件获取输出流;

    ByteArrayOutputStream:在内存中模拟一个OutputStream。实际上是把一个byte[]数组在内存中变成一个OutputStream。

    4. Filter模式

    InputStream根据来源可以包括:

    • FileInputStream:从文件读取数据;

    • ServletInputStream:从HTTP请求读取数据;

    • Socket.getInputStream():从TCP连接读取数据。

    • ……

    针对依赖继承会导致子类数量失控的问题,JDK首先将InputStream分为两大类:

    • 一类是直接提供数据的基础InputStream,如,FileInputStream, ByteArrayInputStream, ServletInputStream……

    • 一类是提供额外附加功能的InputStream,如,BufferedInputStream, DigestInputStream, CipherInputStream……

    在为“基础”InputStream附加功能时,①先确定这个能提供数据源的InputStream(需要的数据必须来自某个地方),②使用BufferedInputStream包装这个InputStream以提供缓冲的功能来提高读取效率,得到的包装类型是BufferedInputStream,但它仍然被视为一个InputStream;③同样地,再使用GZIPInputStream包装这个InputStream以直接读取解压缩的内容。无论包装多少次,得到的对象始终是InputStream,直接用InputStream引用它就可以正常读取。

    这种通过一个“基础”组件再叠加更各种“附加”功能组件的模式,称之为Filter模式/装饰器模式(Decorator),可以在运行期动态增加功能。

    编写FilterInputStream

    在叠加多个FilterInputStream时,只需要持有最外层的InputStream,并且当最外层的InputStream关闭时,内层的InputStream的close()方法也会被自动调用,并最终调用到最核心的“基础”InputStream,因此不存在资源泄露。

    5. 操作Zip

    ZipInputStream是一种FilterInputStream,它可以直接读取zip包的内容;ZipOutputStream是一种FilterOutputStream,可以直接写入内容到zip包。

    读取zip包

    首先创建一个ZipInputStream,通常是传入一个FileInputStream作为数据源,然后循环调用getNextEntry(),直到返回null,表示zip流结束。

    一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,就用read()方法不断读取,直到返回-1。

    写入zip包

    首先创建一个ZipOutputStream,通常是包装一个FileOutputStream,然后每写入一个文件前,先调用putNextEntry(),再用write()写入byte[]数据,写入完毕后调用closeEntry()结束这个文件的打包。

    6. 读取classpath资源

    从classpath读取文件就可以避免不同环境下文件路径不一致的问题:如果把default.properties文件放到classpath中,就不用关心它的实际存放路径。

    在classpath中的资源文件,路径总是以“/”开头,先获取当前的Class对象,然后调用getResourceAsStream()就可以直接从classpath读取任意的资源文件。

    注意:调用getResourceAsStream()时,如果资源文件不存在,它将返回null。

    把默认的配置放到jar包中,再从外部文件系统读取一个可选的配置文件,就可以做到既有默认的配置文件,又可以让用户自己修改配置,使得应用程序启动更加灵活。

    7. 序列化

    序列化和反序列化

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

    反序列化是指把一个二进制内容(即byte[]数组)变回Java对象。

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

    为了避免class定义变动导致的不兼容问题,Java序列化允许class定义一个特殊的serialVersionUID静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,就能自动阻止不匹配的class版本。

    注意:反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

    安全性

    Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。

    Java的反序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,如JSON。

    8. Reader

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

    FileReader

    Reader的一个子类,可以打开文件并获取Reader。读取纯ASCII编码的文本文件,无需指定编码;若文件中包含中文,为避免乱码问题,需要在创建FileReader时指定编码:

    Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);

    同样的,需要注意使用try(resource)来包装Reader在无论有没有IO错误时都能正确关闭。

    CharArrayReader

    可以在内存中模拟一个Reader,实际上是把一个char[]数组变成了一个Reader,与ByteArrayInputStream类似。

    StringReader

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

    InputStreamReader

    Reader和InputStream的关系:

    普通的Reader(除特殊的CharArrayReader和StringReader外)实际上是基于InputStream构造的,因为Reader需要从InputStream中读入字节流(byte),然后根据编码设置再转换为char来实现字符流。

    Reader本质上是一个基于InputStream的byte到char的转换器。已经有一个InputStream,将其转换为Reader是可行的,通过InputStreamReader就可以把任何InputStream转换为Reader(构造时需要指定编码)。

    9. Writer

    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)

    FileWriter

    向文件中写入字符流的Writer,使用方法和FileReader类似。

    CharArrayWriter

    可以在内存中创建一个Writer,实际上时构造一个缓冲区,可以写入char,最后得到写入的char[]数组,和ByteArrayOutputStream类似。

    StringWriter

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

    OutputStreamWriter

    普通的Writer(除特殊的CharArrayWriter和StringWriter外)实际上是基于OutputStream构造的,它接收char,然后在内部自动转换成一个或多个byte,并写入OutputStream。

    OutputStreamWriter是一个将任意的OutputStream转换为Writer的转换器(需要指定编码)。

    10. PrintStream和PrintWriter

    PrintStream是FilterOutputStream的一种,是一种能接收各种数据类型的输出,打印数据时比较方便(print()/println()),且不会抛出IOException,编写代码时就不用进行捕获:

    • System.out:系统默认提供的标准输出;

    • System.err:系统默认提供的标准错误输出。

    PrintWriter

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

    附:廖雪峰Java IO教程链接

  • 相关阅读:
    伪元素 first-letter
    html语义化 -------<fieldset>和<legend>
    《ASP.NET MVC4 WEB编程》学习笔记------ViewBag、ViewData和TempData的使用和区别
    《ASP.NET MVC4 WEB编程》学习笔记------.net mvc实现原理ActionResult/View
    《ASP.NET MVC4 WEB编程》学习笔记------RenderBody,RenderPage,RenderSection
    《转》Visual Studio 2010 终极定制安装精简方法
    《转》IIS中配置通配符应用程序映射
    IIS安装时,添加/编辑应用程序扩展名映射 确定按钮不可用。
    异常:操作可能会破坏运行时稳定性
    petri网学习心得
  • 原文地址:https://www.cnblogs.com/java-learning-xx/p/13096605.html
Copyright © 2011-2022 走看看