读取和写入文件的I/O操作都是调用操作系统提供的接口,因为与读写相关的磁盘设备是由操作系统管理的,应用程序要访问物理设备只能通过系统调用的方式来工作。而只要存在系统调用就可能存在内核空间地址和用户空间地址切换的问题,这是操作系统为了保护系统本身的运行安全,将内核程序运行的内存空间和用户程序运行的内存空间进行隔离造成的。这样的设计虽说保证了内核程序运行的安全性,但是也必然存在数据从内核空间向用户空间复制的问题。
1.标准访问文件的方式
标准访问文件的方式示意图
当应用程序调用read()接口时,操作系统检查在内核的高速缓存中有没有需要的数据,如果已经缓存,则直接从缓存中返回,没有则通过磁盘中读取,然后缓存在操作系统的缓存中。
调用write()接口时,将数据从用户地址空间复制到内核地址空间的缓存中,这时对用户程序来说,写操作就已经完成了,至于什么时候再写到磁盘中,由操作系统决定,除非显示的调用了sync同步命令(将高速页缓存中的数据即刻写入物理磁盘)
2.直接I/O的方式访问磁盘
解释:应用程序直接访问磁盘数据,而不经过操作系统内核数据缓冲区。
目的:减少一次从内核缓冲区到用户程序缓存的数据复制。
应用:对数据的缓存管理由应用程序实现的数据库管理系统中。即系统明确的知道应该缓存哪些数据,应该失效哪些数据。提前将热点数据加载到内存中,加速数据访问效率。
缺点:如果访问的数据不在应用缓存中,那么每次数据都从磁盘进行加载,从而导致整体数据读取速度延迟(相对于在高速缓存页加载速度而言).
实际应用过程中,直接I/O与异步I/O同时使用才会得到相对好的性能。
直接I/O访问文件的方式示意图
3.同步访问文件的方式
解释:数据的读取和写入都是同步操作,它与标准访问不同的是,只有当数据被成功写入到磁盘时才返回给应用程序成功的标志
同步访问文件的方式示意图
4.异步访问文件的方式
解释:当访问数据的线程发出请求后,线程会接着去处理其他事情。而不是阻塞等待,当请求的数据返回后继续处理下面的操作。提高应用程序效率,不会影响访问文件的效率。
异步访问文件方式示意图
5.内存映射方式
解释:操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换为访问文件的某一段数据。
目的:减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为此时两个空间的数据是共享的。
内存映射方式示意图
二.java访问磁盘文件(如何将数据持久化到磁盘)
File对象根据文件路径(文件名)根据这个路径创建一个File对象标识这个文件,根据File对象创建真正读取文件的操作对象,这时将会创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过这个对象可以直接控制这个磁盘文件,由于读取的是字符格式,传输过程中是字节,所以需要StreamDecoder类将byte转化为char格式。然后由操作系统完成建立数据结构,将数据持久化到磁盘工作。
三.Java序列化技术
java序列化就是将一个对象转化成一串二进制表示的字符数组,通过保存或者转移这些字节数据达到持久化的目的。
前提:继承Serializable接口。
反序列化,以原始类作为模版,将这个字节数组在重新构造成对象。
序列化后的类并没有保存全部的类的信息。
序列化后保存的信息有。
1.序列化文件头(序列化协议,序列化协议版本、声明新对象)
2.对要序列化的类的描述
3.对对象中各个属性的描述
4.输出该对象的父类信息描述
5.输出对象的属性项的实际值,如果属性项是一个对象,那么这个对象也会被序列化。
序列化情况总结:
1.当父类继承Serializable接口时,所有子类都可以被序列化
2.子类实现了Serializable接口,父类没有,父类中的属性不能序列化,(不报错,但数据会丢失)子类中的属性能正确序列化
3.如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则报错。
4.在反序列化时,如果对象属性有修改或删减,则修改部删减部分属性会丢失,但不会报错。
5.在反序列化时,如果serialVersionUID被修改,则反序列化时会失败。
protobuf Google的序列化工具