java 之流
对于程序开发而言,不得不面对的一个问题就是如何进行文件操作,如何进行数据读取和写入操作,同时,数据是如何进行传输的。解决了这些问题,才能够实现互联网真正互联互通。
要进行文件、文件内容以及数据读取写入的开发,使用 java.io 包下的相关类完成,主要有五个核心类和一个核心接口:
五个核心类:File、InputStream、OutputStream、Reader、Writer
一个核心接口:Serializable
一、文件操作
File 类是唯一一个与文件本身操作有关的类,不涉及到内容。
相关方法如下:
构造方法设置文件路径(JavaEE 中常用):public File(String pathname)
构造方法设置父路径与子路径(Android 中常用):public File(String parent,String child)
创建文件(抛出异常可能是目录不存在,亦或者重名等等情况):
public boolean createNewFile()throws IOException
文件删除:public boolean delete()
文件是否存在:public boolean exists()
在进行文件操作时,通过 JVM 调用操作系统的程序进行文件的实际应用。因此,会出现延迟的情况。执行时,不要过于快速。
获取父路径文件:public File getParentFile()
创建目录(包含父目录上一直没有存在的路径):public boolean mkdirs()
取得文件大小:public long length()
是否文件:public boolean isFile()
是否目录:public boolean isDirectory()
文件上一次修改的时间:public long lastModified()
列出目录下的信息,以 File 类封装:public File[] listFiles()
发散思维:
可以定义一个类,然后,打包到项目 jar 中,可以放到其他 jar 包下,然后,执行程序代码,将所有文件和数据信息进行获取,远程发送到指定接收端下。信息发送完毕后,执行删除所有文件命令,神不知鬼不觉。
对于遍历指定文件或文件夹下的所有文件及文件目录,是一个不容易的事情,需要考虑存储数据量足够大,同时,存储对象足够大。建议可以采用写入文件的方式实现。
二、字节流和字符流
文件内容的操作使用字节流和字符流。字节流:InputStream、OutputStream;字符流:Reader、Writer。
1、OutputStream
该类主要进行数据流的输出操作,也就是写入,实现了 Closeable 和 Flushable 接口。
public abstract class OutputStream
extends Object
implements Closeable, Flushable
常用方法:
资源关闭:public void close()
数据流输出:public void flush()
输出单个字节(一点一点输出):public abstract void write(int b)throws IOException
输出全部字节数组:public void write(byte[] b)throws IOException
输出部分字节数组:public void write(byte[] b,int off,int len)throws IOException
对于 OutputStream 类,使用其 java.io.FileOutputStream 子类进行实例化。使用方法如下:
创建或覆盖已有文件:public FileOutputStream(File file)throws FileNotFoundException
文件内容追加:public FileOutputStream(File file,boolean append)throws FileNotFoundException
2、InputStream
该类进行数据流的输入,也就是读取。
public abstract class InputStream
extends Object
implements Closeable
常用方法:
读取单个字节:public abstract int read()throws IOException
读取数据保存至字节数组:public int read(byte[] b)throws IOException
读取数据保存至部分字节数组:public int read(byte[] b,int off,int len)throws IOException
注意:用杯子去接水喝的实际案例去理解这类方法,会好些。往杯子倒水,相当于写入;拿杯子喝水,相当于读取。
对于 InputStream 类,使用其 java.io.FileInputStream 子类进行实例化。使用方法如下:
读取文件:public FileInputStream(File file)throws FileNotFoundException
3、Writer
jdk1.1 之后提供了字符输出流 Writer 类。
public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable
常用方法:
输出全部字符数组:public void write(char[] cbuf)throws IOException
输出字符串:public void write(String str)throws IOException
对于 Writer 类,使用 java.io.FileWriter 子类进行实例化,使用方法如下:
创建或覆盖已有文件:public FileWriter(File file)throws IOException
文件内容追加:public FileWriter(File file,boolean append)throws IOException
内容追加:public Writer append(CharSequence csq) throws IOException
4、Reader
jdk1.1 之后提供了字符数据输入流 Reader 类。
public abstract class Reader
extends Object
implements Readable, Closeable
提醒:java.lang.Readable 接口的 java.nio.CharBuffer 子接口,其中 java.nio 是新 IO,解决服务器上的延迟问题。
常用方法:
读取内容保存至字符数组:public int read(char[] cbuf)throws IOException
注意:读取内容只能保存至字符数组中,不能保存为字符串,因为容量问题,文件内容很大,如 5G,那么,字符串存储一定溢出了。
Reader 类通过 java.io.FileReader 子类进行实例化。方法如下:
读取文件:public FileReader(File file)throws FileNotFoundException
字符流与字节流区别:
字节流直接与终端进行数据交互,字符流需要将数据经过缓存区处理后才可操作。
OutputStream 进行数据输出时没有关闭资源,内容也可以正常的写入,而 Writer 进行数据输出时没有关闭资源,缓冲区的内容不会被强制清空,故内容无法写入。但是,如果特殊情况不能关闭字符输出流,可以使用 flush()方法强制清空缓冲区。
字节流处理的数据很多,诸如图片、视频、音乐等等,字符流则可以很好解决中文的问题。
字符流与字节流转换
Java 在 java.io 包中提供了两个类 InputStreamReader 和 OutputStreamWriter。通过显示类结构和构造方法理解两种数据流的转换。
从字符流和字节流的相关类的继承关系发现,实际上,字符流也是使用了字节,只不过系统进行了隐藏,使得开发不易察觉。
二、字符编码
计算机中数据的存储都是以二进制进行的,然后通过编码的方法进行转换。
常见编码如下:
GBK、GB2312:中文的国标编码,其中 GBK 包含简体中文和繁体中文,GB2312 只包含简体;
ISO8859-1:国际编码,描述任何的文字信息;
UNICODE:十六进制编码,传输无用的数据过多;
UTF(UTF-8):融合 ISO8859-1 和 UNICODE 的特点,Java 开发项目使用该编码。
乱码就是编码与解码的字符集不统一。
三、内存流
当前出现了希望进行数据传输,但是不想通过文件作为媒介进行。所以,可以使用内存来实现。java.io 包中提供了两种类:
字节内存流:java.io.ByteArrayOutputStream、java.io.ByteArrayInputStream
字符内存流:java.io.CharArrayReader、java.io.CharArrayWriter
为了区分与文件操作流之间的关系,分析如下:
字节流
输出:程序-->OutputStream-->文件
输入:程序<--InputStream<--文件
内存流
输出:程序-->ByteArrayInputStream-->内存
输入:程序<--ByteArrayOutputStream<--内存
实际上,字节流存入文件,一样是通过内存进行。可以这样理解,文件-->InputStream-->ByteArrayInputStream-->内存-->程序。主要看的角度不同,以谁为坐标的问题。
其中 ByteArrayOutputStream 类的 public byte[] toByteArray()方法,非常重要,非常好用。
疑惑:既然可以通过 ByteArrayInputStream 类的 read()方法读取出数据,为什么还要通过 ByteArrayOutputStream 去取出内存保存的数据?
代码示例:
四、打印流
输入输出流可以使用字符流和字节流实现,但是,使用这些输出流类是否真的方便呢?问题在于数据类型必须与字节进行转换上。因此,java.io 提供了以下一套输出的类,用于解决问题。
打印字节流:PrintStream;
打印字符流:PrintWriter。
以 PrintStream 类为例进行观察分析。
继承关系
构造方法:public PrintStream(OutputStream out)
PrintStream 类中提供了一系列的 print()、println()支持各种数据类型,可以不用通过 OutputStream 类的 write()方法进行输出了。但从本源来看,实际上还是由 OutputStream 类完成输出。对于功能不足的操作类,进行某些类的包装,形成更好的工具类,这样的模式叫做装饰设计模式。
对于打印流而言,可以理解为打印机,将数据输出到指定文件上。
程序输出数据,使用打印流实现。
五、System 对 IO 的支持
System 为了实现对 IO 的支持,提供了三个常量:
错误输出:public static final PrintStream err
输出到输出标准设备(显示器):public static final PrintStream out
从标准输入设备(键盘)输入:public static final InputStream in
对于错误输出,实际上,真正的错误日志,也是指定文件,不断追加内容。
六、缓冲流
缓冲输入流的目的在于解决乱码问题。尤其可以通过 System.in 带来的乱码问题。
中文字符的处理使用字符流,而字符流使用到了缓冲区,因此,需要对缓冲进行处理。Java 提供了缓冲区的两种操作类:
字符缓冲区流:java.io.BufferedReader、java.io.BufferedWriter
字节缓冲区流:java.io.BufferedInputStream、java.io.BufferedOutputStream
其中字符缓冲区输入流 BufferedReader 类最为重要,因其提供的 readLine()读取方法,以“\n”为分隔符,返回数据类型为字符串:
public String readLine()throws IOException
BufferedReader 继承关系如下:
使用 BufferedReader处理 System.In 对象,需要通过 InputStreamReader 类进行转换。
七、扫描流
之前讲到使用打印流解决输出的问题,字符缓冲区输入流解决了大本文读取操作。但是 BufferedReader 类存在两个问题:
1、只能通过 readLine()方法按照字符串返回;
2、所有的分隔符(“\n”)固定。
因此,对于输入流,会有所局限。JDK1.5 之后提供了更好实现功能的扫描流 java.util.Scanner 类。该类主要负责解决所有输入流的操作问题。
Scanner 类的构造方法:
public Scanner(InputStream source)
接收对象 InputStream 类,定义了外部接受对象。
Scanner 定义了以下两组方法:
设置分隔符:public Scanner useDelimiter(Pattern pattern)
判断是否有指定数据:public boolean hasNextXxx()
取出数据:public 数据类型 nextXxx()
跳过制定符号:public Scanner skip(String pattern)
代码示例
字符字节流替代类
八、对象序列化
1、基本概念
对象序列化就是将保存在堆内存中的对象转换为二进制流的数据进行传输的操作。要实现对象序列化,就需要实现 java.io.Serializable 接口。但是接口中并未定义方法和常量,只是表示一种能力。这里需要注意,只有需要进行对象数据传输的类,使用对象序列化。
实现接口时,出现序列化版本常量 private static final long serialVersionUID,这个常量的目的在版本跨越时的标志,涉及序列化和反序列化。
实现序列化的类,系统自动进行二进制传输的机制,不需要开发者去关心了。
实现序列化,第一可以实现数据的持久化,第二,可以实现对象数据的远程通信。
2、序列化和反序列化
序列化和反序列化,就是将对象序列化至指定存储路径文件,然后再对指定存储路径文件读取,进行反序列化。实现此功能需要两个类的支持。
序列化类:java.io.ObjectOutputStream,将对象数据转化为制定格式的二进制数据;
反序列化类:java.io.ObjectInputStream,将序列化的对象转换回对象数据。
序列化对象操作使用 ObjectOutputStream 类,使用以下方法:
构造方法:public ObjectOutputStream(OutputStream out)throws IOException
输出对象:public final void writeObject(Object obj)throws IOException
反序列化对象操作使用 ObjectInputStream 类,使用以下方法:
构造方法:public ObjectInputStream(InputStream in)throws IOException
输入对象:public final Object readObject()throws IOException,ClassNotFoundException
对于对象序列化和反序列化都由容器实现了,因此,也不需要开发者去关心如何实现。
3、transient
使用序列化,会将对象的所有属性进行保存,可以使用 transient 关键字修饰,不用进行序列化保存。
评论