IO流的概念
流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象
流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
流的作用:为数据源和目的地建立一个输送通道。
IO流的分类
按照不同的分类方式,可以把流分为不同的类型。常用的分类有三种
1、 按照流的流向分,可以分为输入流和输出流。
输入流:只能从中读取数据,而不能向其写入数据。 把文件中的信息读取到程序中
输出流:只能向其写入数据,而不能向其读取数据。把程序中的信息输出到文件中
2、按照操作单元划分,可以划分为字节流和字符流。
字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
字节流主要是由InputStream和outPutStream作为基类,而字符流则主要有Reader和Writer作为基类。
编码表:就是生活中的字符和计算机二机制的对应关系表
- ASCII表:一个字符中的7位就可以表示:对应的字节都是正数 0-XXXXXXX
- ISO-8859-1:拉丁码表latin,用一个字节中的8位表示,可表示整数和负数 1-XXXXXXX
- GBK:目前最常用的中文码表,用2个字节表示,约2万的中文和符号,2个字节的第一个字节开图是1,第二个字节的开头是0
- Unicode:国际标准码表,无论什么文字,都用2个字节存储
- utf-8:基于Unicode,一个字节就可以存储数据,不要用两个字节存储,而且这个码表更加标准化,
编码解码
能识别中文的码表:GBK,utf-8;正因为识别中文码表不唯一,涉及到编码解码问题
字符->字节:编码过程 eg:"haha".getByte() byte[]
字节->字符:解码过程 eg:byte[] b = {23,34,54}, new String(b,”GBK /”)
字节流与字符流的区别
字节流和字符流使用是非常相似的,那么除了操作代码的不同之外,还有哪些不同呢?
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的
字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容
为什么字符流使用到了缓冲区?
什么叫缓冲区?
在很多地方都碰到缓冲区这个名词,那么到底什么是缓冲区?又有什么作用呢?
回答:缓冲区可以简单地理解为一段内存区域。
可以简单地把缓冲区理解为一段特殊的内存。
某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。
如果想在不关闭时也可以将字符流的内容全部输出,则可以使用Writer类中的flush()方法完成。
那开发中究竟用字节流好还是用字符流好呢?
在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。
3、节点流和处理流
boolean exists() //表示当前路径是否存在
boolean isFile() //判断当前路劲是否是文件
boolean isDirectory() //判断当前是否为目录
boolean isHidden() //判断是否是隐藏文件
void deleteOnExit() //在程序退出时删除
boolean delete() //直接删除
boolean createNewFile() //创建新文件 在当前文件不存在的情况下,创建成功返回true,文件存在返回false
file.mkdir(); //创建目录
file.getName();// 获取文件名
file.getAbsolutePath();//获取绝对路径
file.list(); //获取当前目录下所有文件
String[] list(FilenameFilter filter) //获取过滤后的目录或文件
序号 | 方法描述 |
1 | public String getName() |
2 | public String getParent()、 |
3 | public File getParentFile() |
4 | public String getPath() |
5 | public boolean isAbsolute() |
6 | public String getAbsolutePath() |
7 | public boolean canRead() |
8 | public boolean canWrite() |
9 | public boolean exists() |
10 | public boolean isDirectory() |
11 | public boolean isFile() |
12 | public long lastModified() |
13 | public long length() |
14 | public boolean createNewFile() throws IOException |
15 | public boolean delete() |
16 | public void deleteOnExit() |
17 | public String[] list() |
18 | public String[] list(FilenameFilter filter) |
19 | public File[] listFiles() |
20 | public File[] listFiles(FileFilter filter) |
21 | public boolean mkdir() |
22 | public boolean mkdirs() |
23 | public boolean renameTo(File dest) |
24 | public boolean setLastModified(long time) |
25 | public boolean setReadOnly() |
26 | public static File createTempFile(String prefix, String suffix, File directory) throws IOException |
27 | public static File createTempFile(String prefix, String suffix) throws IOException |
28 | public int compareTo(File pathname) |
29 | public int compareTo(Object o) |
30 | public boolean equals(Object obj) |
31 | public String toString() |
Mkdirs 和mkdir之间的关系
new File("/tmp/one/two/three").mkdirs();
执行后, 会建立tmp/one/two/three四级目录
new File("/tmp/one/two/three").mkdir();
则不会建立任何目录, 因为找不到/tmp/one/two目录, 结果返回false
- InputStream
InputStream 是所有的输入字节流的父类,它是一个抽象类,主要包含三个方法:
//读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。 int read() ; 字节数转成int //读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。 int read(byte[] buffer) ; //读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。 int read(byte[] buffer, int off, int len) ; |
2.Reader
//读取一个字符并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。 返回的int值的含义,每一个二进制位(bit)有0和1两种状态,八个二进制位(bit)被称为一个字节(Byte),一个字节可以表示出2^8=256种状态,从0000 0000到1111 1111。每个状态可以对应一个符号。 int read() ; //读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1。 int read(char[] cbuf) ; //读取length个字符,并存储到一个数组buffer,从off位置开始存,最多读取len,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1。 int read(char[] cbuf, int off, int len) ,如果读取前以到输入流的末尾返回NUll String readLine() null //对比InputStream和Reader所提供的方法,就不难发现两个基类的功能基本一样的, //只不过读取的数据单元不同。在执行完流操作后,要调用close()方法来关系输入流, //因为程序里打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。 //除此之外,InputStream和Reader还支持如下方法来移动流中的指针位置: //在此输入流中标记当前的位置 //readlimit - 在标记位置失效前可以读取字节的最大限制。 void mark(int readlimit) // 测试此输入流是否支持 mark 方法 boolean markSupported() // 跳过和丢弃此输入流中数据的 n 个字节/字符 long skip(long n) //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置 |
3.OutputStream
OutputStream 是所有的输出字节流的父类,它是一个抽象类,主要包含如下四个方法:
//向输出流中写入一个字节数据,该字节数据为参数b的低8位。 void write(int b) ; a ===> 97 read() //将一个字节类型的数组中的数据写入输出流。 void write(byte[] b); //将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。 void write(byte[] b, int off, int len); //将输出流中缓冲的数据全部写出到目的地。 void flush(); |
4.Writer
Writer 是所有的输出字符流的父类,它是一个抽象类,主要包含如下六个方法:
//向输出流中写入一个字符数据,该字节数据为参数b的低16位。 void write(int c); //将一个字符类型的数组中的数据写入输出流, void write(char[] cbuf) //将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。 void write(char[] cbuf, int offset, int length); //将一个字符串中的字符写入到输出流。 void write(String string); //将一个字符串从offset开始的length个字符写入到输出流。 void write(String string, int offset, int length); //将输出流中缓冲的数据全部写出到目的地。 void flush() |
|
BufferedWriter
void close() 关闭此流、释放与此流有关的资源。 void flushBuffer() 将cb中缓存的字符flush到底层out中、 void flush() 刷新此流、同时刷新底层out流 void newLine() 写入一个换行符。 void write(int c) 将一个单个字符写入到cb中。 void write(char cbuf[], int off, int len) 将一个从下标off开始长度为len个字符写入cb中 void write(String s, int off, int len) 将一个字符串的一部分写入cb中 从流里面读取文本,通过缓存的方式提高效率,读取的内容包括字符、数组和行。 缓存的大小可以指定,也可以用默认的大小。大部分情况下,默认大小就够了 |
BufferedReader
void close() 关闭此流、释放与此流有关的所有资源 void mark(int readAheadLimit) 标记此流此时的位置 boolean markSupported() 判断此流是否支持标记 void reset() 重置in被最后一次mark的位置 boolean ready() 判断此流是否可以读取字符 int read() 读取单个字符、以整数形式返回。如果读到in的结尾则返回-1。 int read(char[] cbuf, int off, int len) 将in中len个字符读取到cbuf从下标off开始长度len中 String readLine() 读取一行 long skip(long n) 丢弃in中n个字符! |
FileInputStream
FileInputStream(File file) // 创建“File对象”对应的“文件输入流” FileInputStream(FileDescriptor fd) // 创建“文件描述符”对应的“文件输入流” 可以看出,FileDescriptor可以看做一种指向文件引用的抽象化概念。它能表示一个开放文件,一个开放的socket或者一个字节的源。它最主要的用途就是去创建FileInputStream或者FileOutputStream。并且也说了不应该创建应用自己的文件描述符。 FileInputStream(String path) // 创建“文件(路径为path)”对应的“文件输入流” int available() // 返回“剩余的可读取的字节数”或者“skip的字节数” void close() // 关闭“文件输入流” FileChannel getChannel() // 返回“FileChannel” final FileDescriptor getFD() // 返回“文件描述符” int read() // 返回“文件输入流”的下一个字节 int read(byte[] buffer, int off, int len) // 读取“文件输入流”的数据并存在到buffer,从off开始存储,存储长度是len。 long skip(long n) // 跳过n个字节 |
FileOutputStream
FileOutputStream(File file);//如果文件不存在则创建文件,如果文件存在则删除文件后重新创建文件。 FileOutputStream(File file, boolean append);//第二个参数如果为true的话在文件后面追加写入的内容。 FileOutputStream(String name);//和第1个一样 FileOutputStream(String name, boolean append) FileOutputStream(FileDescriptor fdObj);//通过文件描述符创建文件输出字节流 //文件描述符表示一个到文件系统中的某个实际文件的现有连接。
void write(byte[] b);//把字节数组写入文件 void write(byte[] b, int off, int len);//把从字节数组b中的off下标开始,把字节写入文件,写入len个字节。 void write(int b);把int低八位写入文件,则写一个int(32位)要写四次: 每次右移八位. |
对象序列化
一、对象如何序列化
对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。(注意:静态数据不能被序列化,因为静态数据不在堆内存中,而是在静态方法区中)
Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。
serialVersionUID(串行化版本统一标识符) 作用:
具体的序列化过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
serialVersionUID 用来表明类的不同版本间的兼容性。
有两种生成方式: 一个是默认的1L;
另一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段 。
序列化需要注意:
1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化(非节点流需要通过其他流来链接数据源)
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,
可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,
如 int 型的是 0,对象型的是 null。
7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,
希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,
才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全
序列化的使用场景
1、把对象的字节序列化永久的存放在磁盘上,通常放在一个文件夹下
2、在网络上传输对象的字节序列
二、非序列化使用场景
如何将非静态的数据不进行序列化?用 关键字修饰此变量即可。使用场景:为了安全起见,有时候我们不需要在网络间
传输一些数据(如身份证号码,密码,银行卡号等)
三、Serializable是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:
writeObject ---> writeObject0--->writeOrdinaryObject--->writeSerialData--->invokeWriteObject
在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,
如果不是则直接抛出NotSerializableException。
1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出NotSerializableException异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
为什么需要重写
生成新对象的方法
BufferedInputStream
一、有了InputStream为什么还要有BufferedInputStream?
BufferedInputStream和BufferedOutputStream这两个类分别是FilterInputStream和FilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。我们有必要知道不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader和BufferedWriter两个类。
二、flush的作用
- BufferedOutputStream在close()时会自动flush
2. BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.
close
一、close()方法的作用
1、关闭输入流,并且释放系统资源
2、BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。
二、那么如果我们想逐个关闭流,我们该怎么做?
答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
看懂了怎么正确的关闭流之后,那么我们就可以优化上面的代码了,只关闭外层的处理流
转换流
转换流的特点:
1. 其是字符流和字节流之间的桥梁
2. 可对读取到的字节数据经过指定编码转换成字符
3. 可对读取到的字符数据经过指定编码转换成字节
什么时候使用转换流呢?
1,源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。提高对文本操作的便捷。
2,一旦操作文本涉及到具体的指定编码表时,必须使用转换流 。
关于指定编码表的作用,建议参考这篇文章从内存角度解析Java字符编码,一定会豁然开朗的。
具体的对象体现:
1. InputStreamReader:字节到字符的桥梁
2. OutputStreamWriter:字符到字节的桥梁
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
RandomAccessFile类
1. RandomAccessFile 模式说明
RandomAccessFile共有4种模式:"r", "rw", "rws"和"rwd"。
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。
"rws" 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
"rwd" 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。
说明:
(01) 什么是“元数据”,即metadata?
英文解释如下:
The definition of metadata is "data about other data." With a file system, the data is contained in its files and directories, and the metadata tracks information about each of these objects: Is it a regular file, a directory, or a link? What is its size, creation date, last modified date, file owner, group owner, and access permissions?
大致意思是:
metadata是“关于数据的数据”。在文件系统中,数据被包含在文件和文件夹中;metadata信息包括:“数据是一个文件,一个目录还是一个链接”,“数据的创建时间(简称ctime)”,“最后一次修改时间(简称mtime)”,“数据拥有者”,“数据拥有群组”,“访问权限”等等。
(02) "rw", "rws", "rwd" 的区别。
当操作的文件是存储在本地的基础存储设备上时(如硬盘, NandFlash等),"rws" 或 "rwd", "rw" 才有区别。
当模式是 "rws" 并且 操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]” 或 “修改文件元数据(如文件的mtime)”时,都会将这些改变同步到基础存储设备上。
当模式是 "rwd" 并且 操作的是基础存储设备上的文件;那么,每次“更改文件内容[如write()写入数据]”时,都会将这些改变同步到基础存储设备上。
当模式是 "rw" 并且 操作的是基础存储设备上的文件;那么,关闭文件时,会将“文件内容的修改”同步到基础存储设备上。至于,“更改文件内容”时,是否会立即同步,取决于系统底层实现。
方法
RandomAccessFile raf = new RandomAccessFile(path, "r");
raf.seek(raf.length());
raf.write("我是追加的 ".getBytes("gbk"));
raf.read(buff)