精华内容
下载资源
问答
  • 2022-05-09 17:35:34

    一、buffer pool

    1. 缓冲池(buffer pool)

    背景

    InnoDB是基于磁盘存储的,并将其中的数据按页的方式进行管理。因此InnoDB可视为基于磁盘的数据库系统。由于CPU的速度和磁盘IO速度的巨大鸿沟,需要缓冲池(buffer pool)来提高数据库的整体性能

    作用

    为了提高大容量读取操作的效率,缓冲池被分为多个,这些可能包含多个行。为了提高缓存管理的效率,缓冲池被实现为页的链接列表。使用LRU算法的变体将很少使用的数据从缓存中老化掉 。

    架构

    简要架构图,在 Buffer Pool 中,是以数据页为数据单位叫做缓存页

    在这里插入图片描述

    原理

    • 第一次读取数据时,首先从磁盘中读取数据页,并放到(FIX)缓冲池中。
    • 再次读取同样的数据时,先看缓冲池中是否有相同的数据页。有则命中,从缓冲池中读取。否则从磁盘读取
    mysql> show variables like 'innodb_buffer_pool_size';
    +-------------------------+---------+
    | Variable_name           | Value   |
    +-------------------------+---------+
    | innodb_buffer_pool_size | 8388608 |
    +-------------------------+---------+
    1 row in set (0.02 sec)
    

    缓冲池中缓存的数据页类型

    • 索引页
    • 数据页
    • undo页
    • 插入缓冲(Insert buffer)
    • 自适应哈希索引
    • 锁信息
    • 数据字典信息(data dictionary)

    缓存页数据结构

    缓存页都会对应着一个描述数据块,里面包含数据页所属的表空间、数据页的编号,缓存页在 Buffer Pool 中的地址等等。

    描述数据块本身也是一块数据,它的大小大概是缓存页大小的5%左右,大概800个字节左右的大小。

    描述如图所示:

    在这里插入图片描述

    缓冲池实例

    即缓冲池的个数。每页根据哈希值分配到不同缓冲池实例减少资源竞争、支持更大的并发处理,加快查询速度

    mysql> show variables like 'innodb_buffer_pool_instances';
    +------------------------------+-------+
    | Variable_name                | Value |
    +------------------------------+-------+
    | innodb_buffer_pool_instances | 1     |
    +------------------------------+-------+
    1 row in set (0.03 sec)
    

    ​ 可通过配置文件修改实例个数

    2. Buffer Pool 高并发场景

    单个 Buffer Pool 的问题

    如果 InnoDB 存储引擎只有一个 Buffer Pool,当高并发时,多个请求进来,那么为了保证数据的一致性(缓存页、free 链表、flush 链表、lru 链表等多种操作),必须得给缓冲池加锁了,每一时刻只能有一个请求获得锁去操作 Buffer Pool,其他请求只能排队等待锁释放。那么此时 MySQL 的性能是多么的低!

    多个 Buffer Pool

    在生产环境中,其实我们是可以给 MySQL 设置多个 Buffer Pool 来提升 MySQL 的并发能力的

    如果Buffer Pool 分配的内存小于1GB,那么最多就只会给你一个 Buffer Pool

    但是呢,如果你给 MySQL 设置的内存很大,此时你可以利用下面两个参数来设置 Buffer Pool 的总大小和总实例数,这样,MySQL 就能有多个 Buffer Pool 来支撑高并发了。

    [server] 
    #缓冲池大小 8G
    innodb_buffer_pool_size = 8589934592 
    #缓冲池的数量 4个 则每一个缓冲池大小2G
    innodb_buffer_pool_instances = 4
    

    每个 Buffer Pool 负责管理着自己的描述数据块和缓存页,有自己独立一套 free 链表、flush 链表和 lru 链表。

    3. 调整 Buffer Pool

    修改配置调整

    在 MySQL 5.7 后,MySQL 允许我们动态调整参数 innodb_buffer_pool_size 的值来调整 Buffer Pool 的大小了。

    假如就这样直接调大会存在啥问题?

    假设调整前的配置:Buffer Pool 的总大小为8G,一共4个 Buffer Pool,每个大小为 2G。

    [server] 
    innodb_buffer_pool_size = 8589934592 
    innodb_buffer_pool_instances = 4
    

    假设给 Buffer Pool 调整到 16 G,就是说参数 innodb_buffer_pool_size 改为 17179869184。

    此时,MySQL 会为 Buffer Pool 申请一块大小为16G 的连续内存,然后分成 4块,接着将每一个 Buffer Pool 的数据都复制到对应的内存块里,最后再清空之前的内存区域。那这是相当耗费时间的操作

    chunk 机制调整

    为了解决上面的问题,Buffer Pool 引入一个机制:chunk 机制。

    1. 每个 Buffer Pool 其实是由多个 chunk 组成的。每个 chunk 的大小由参数 innodb_buffer_pool_chunk_size 控制,默认值是 128M。
    2. 每个 chunk 就是一系列的描述数据块和对应的缓存页。
    3. 每个 Buffer Pool 里的所有 chunk 共享一套 free、flush、lru 链表。

    得益于 chunk 机制,通过增加 Buffer Pool 的chunk个数就能避免了上面说到的问题。当扩大 Buffer Pool 内存时,不再需要全部数据进行复制和粘贴,而是在原本的基础上进行增减内存,

    下面继续用上面的例子,介绍一下 chunk 机制下,Buffer Pool 是如何动态调整大小的。

    调整前 `Buffer Pool` 的总大小为 8G,调整后的 `Buffer Pool` 大小为 16 G。
    由于 `Buffer Pool` 的实例数是不可以变的,所以是每个 `Buffer Pool` 增加 2G 的大小,此时只要给每个 `Buffer Pool` 申请 (2000M/128M)个chunk就行了,但是要注意的是,新增的每个 chunk 都是连续的128M内存。
    

    4. Buffer Pool内存

    如何设置 Buffer Pool 的总内存大小

    比较合理的比例,应该是 Buffer Pool 的内存大小占机器总内存的 50% ~ 60%,例如机器的总内存有16G,那么你给 Buffer Pool 分配个8G~10G左右就挺合理的了。

    如何设置 Buffer Pool 的实例数

    假设此时 Buffer Pool 的总大小为 16G,即 16384M,那么 Buffer Pool 的数量应该是多少个呢?

    Buffer Pool 总大小 = (chunk 大小 * Buffer Pool数量)* chunk个数

    16384 = ( 128 * Buffer Pool 数量)* n

    64 个:也是可以的,但是每个 Buffer Pool 就只要2个 chunk。

    16 个:也是可以的,每个 Buffer Pool 拥有8个个 chunk。

    8 个:也是可以的,每个 Buffer Pool 拥有16个 chunk。

    5. Buffer Pool 的初始化

    • MySQL 启动时,会根据参数 innodb_buffer_pool_size 的值来为 Buffer Pool 分配内存区域。

    • 然后会按照缓存页的默认大小 16k 以及对应的描述数据块的 800个字节 左右大小,在 Buffer Pool 中划分中一个个的缓存页和一个个的描述数据库块。

    二、buffer pool的缓存结构

    ​ 上面我们知道缓冲池被设计为页的链表结构根据页的不同状态(未使用,已使用,已经使用未刷新到缓存):

    • FREE链表: 空闲列表,页属于缓存池但是并没有缓存mysql数据(查询出来的数据会使用该链表的节点存储数据并从free链表移除 进入到LRU链表)。
    • FLUSH链表: LRU列表中的数据一旦被修改(和磁盘不一致),则该页被标记为flush链表中的一员。
    • LRU链表: 采用最近最久未使用LRU的变种(新增了midPoint)内存淘汰策略。

    1. LRU list

    实现方式

    ​ 基于LRU(最近最久未使用)内存淘汰策略并新增了midPoint位置。新读取到的页并没有直接放在LRU列的首部,而是放在距离尾部37%的位置。这个算法称之为midpoint insertion stategy

    • midpoint之前的是new区域(热数据)midpoint之后的数据是不活跃数据,old区域。midpoint处,是新子列表的尾部与旧子列表的头相交的边界

    • 当InnoDB将页面读入缓冲池时,它首先将其插入中点(旧子列表的头部)。因为它是用户发起的操作(如SQL查询)所需的,或者是InnoDB自动执行的预读操作的一部分,所以这些页面可以被读取。

    • 访问旧子列表中的页面会使其变得“年轻”,并将其移动到新子列表的头部。如果由于用户启动的操作需要该页而读取该页,则会立即进行第一次访问,并使该页变得年轻。如果该页是由于预读操作而被读取的,则第一次访问不会立即发生,并且在该页被逐出之前可能根本不会发生。

    • 当数据库运行时,缓冲池中未被访问的页通过向列表的尾部移动来“老化”。新子列表和旧子列表中的页面都会随着其他页面变为新页面而老化。旧子列表中的页面也会随着页面插入到中点处而老化。最终,未使用的页面到达旧子列表的尾部并被逐出。

    在这里插入图片描述

    查看midpoint

    mysql> show variables like 'innodb_old_blocks_pct';
    +-----------------------+-------+
    | Variable_name         | Value |
    +-----------------------+-------+
    | innodb_old_blocks_pct | 37    |
    +-----------------------+-------+
    1 row in set (0.04 sec)
    
    • 37:末尾处的37%的位置,即末尾 3/8 的位置

    设计好处

    避免偶发性的读操作获取的数据替换热点数据

    • 全表扫描:大量只有本次需要数据替换了热点数据,影响效率。
    • 预读机制:会读到其他未使用的数据页占据lru表的头部

    unZip_LRU

    InnoDB从1.0.X开始支持页压缩技术。原本16k的页,可以压缩到2k、4k、8k。因此需要unzip_LRU列来管理,但是注意:LRU list中包含了unzip_LRU

    如何给unzip_LRU分配内存:(假设需要从缓冲池中申请4KB大小的内存)

    • 检查4KB的unzip_LRU列表,检查是否有可用的空闲页
    • 若有,则直接使用
    • 否则申请8KB的unzip_LRU页
    • 若能申请到页,则拆分成两个4KB页,并存放到unzip_LRU列表
    • 如果申请不到,则从LRU列表申请一个16K的页面,拆分成一个8K,两个4k,分别放到对应的unzip_LRU列表中

    2、Free list

    结构

    双向链表,链表的每个节点就是一个个空闲的缓存页对应的描述数据块。由 Buffer Pool 里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是 free_pre 指针,一个是 free_next 指针,分别指向自己的上一个 free 链表的节点,以及下一个 free 链表的节点。

    通过 Buffer Pool 中的描述数据块的 free_pre 和 free_next 两个指针,就可以把所有的描述数据块串成一个 free 链表。

    //伪代码来描述一下 free 链表中描述数据块节点的数据结构:
    DescriptionDataBlock{
        block_id = block1;
        //指向自己的上一个 free 链表的节点指针
        free_pre = null;
        //指向下一个 free 链表的节点指针
        free_next = block2;
    }
    

    free 链表有一个基础节点,他会引用链表的头节点和尾节点,里面还存储了链表中有多少个描述数据块的节点,也就是有多少个空闲的缓存页。

    下面我们也用

    //伪代码来描述一下基础节点的数据结构:
    FreeListBaseNode{
        start = block01;
        end = block03;   
        count = 2;
    }
    

    使用流程

    补充知识

    数据页缓存哈希表,它的 key 是表空间+数据页号,而 value 是对应缓存页的地址。

    描述如图所示:

    在这里插入图片描述

    1. 首先,执行SQL(除了插入操作) ,判断数据对应的数据页能否在 数据页缓存哈希表里 找到对应的缓存页。

    2. 如果找到,将直接在 Buffer Pool 中进行增删改查。

    3. 如果找不到,则从 free 链表中找到一个空闲的缓存页,然后从磁盘文件中读取对应的数据页的数据到缓存页中,并且将数据页的信息和缓存页的地址写入到对应的描述数据块中,然后修改相关的描述数据块的 free_pre 指针和 free_next 指针,将使用了的描述数据块从 free 链表中移除。记得,还要在数据页缓存哈希表中写入对应的 key-value 对。最后也是在 Buffer Pool 中进行增删改查。

    3. Flush list

    脏页(dirty page)

    Buffer Pool 中的缓存页因为不断被修改而导致和磁盘文件中的数据不一致了会有很多个脏页,脏页里面很多脏数据。所以,MySQL 会有一条后台线程,定时地将 Buffer Pool 中的脏页刷回到磁盘文件中。

    MySQL通过checkPoint技术将脏页刷新到磁盘

    • Flush list中的页,即为脏页
    • 脏页既存在于LRU列表中,也存在于Flush list中(LRU列表用来管理缓冲池中页的可用性,Flush list脏页刷新到磁盘,两者互不影响)

    结构

    同Free List结构一下 一个双向链表,使用 flush_pre 指针和 flush_next 指针,分别指向自己的上一个 flush链表的节点,以及下一个 flush链表的节点。

    DescriptionDataBlock{
        block_id = block1;
        // free 链表的
        free_pre = null;
        free_next = null;
    
        // flush 链表的
        flush_pre = null;
        flush_next = block2;
    }
    

    flush 链表也有对应的基础节点,也是包含链表的头节点和尾节点,还有就是修改过的缓存页的数量。

    FlushListBaseNode{
        start = block1;
        end = block2;
        count = 2;
    }
    

    到这里,我们都知道,SQL 的增删改都会使得缓存页变为脏页,此时会修改脏页对应的描述数据块的 flush_pre 指针和 flush_next 指针,使得描述数据块加入到 flush 链表中,之后 MySQL 的后台线程就可以将这个脏页刷回到磁盘中。

    描述如图所示:

    在这里插入图片描述

    各种 List 关系

    数据库刚启动时,LRU list是空的。Free list是最大的。当需要从缓冲池中分页时,看Free list有空闲页:

    • 有则删除Free list的页,加入到LRU list中。维持一个数量平衡

    • 否则,根据LRU算法,淘汰LRU末尾的页,省出内存来,分配给新的页

    • LRU列中数据被修改后,产生脏页。数据库通过checkpoint机制将脏页刷新会磁盘,flush list中的页即为脏页列表。脏页即存在于LRU中,也存在于Flush中

    • lru 链表尾部的缓存页何时刷入磁盘?

      **没有空闲的free list **: 当 free list 为空了,此时需要将数据页加载到缓冲池里,就会 lru list 的 old 数据区域尾部的缓存页刷入磁盘,然后清空,再加载数据页的数据。

      后台定时任务|:定时将 lru list 的 old 数据区域的尾部的一些缓存页刷入磁盘,然后清空,最后把他们对应的描述数据块加入到 free 链表中去。

      当然了,除了 lru list尾部的缓存页会被刷入磁盘,还有的就是 flush list 的缓存页。

      后台线程同时也会在 MySQL 不繁忙的时候,将 flush 链表中的缓存页刷入磁盘中,这些缓存页的描述数据块会从 lru 链表和 flush 链表中移除,并加入到 free 链表中。

      在这里插入图片描述

    总结

    到此,我已经将缓冲池 Buffer Pool介绍完毕了。

    下面简单总结一下 Buffer Pool 从初始化到使用的整个流程。

    1、MySQL 启动时会根据分配指定大小内存给 Buffer Pool,并且会创建一个个描述数据块和缓存页。

    2、SQL 进来时,首先会根据数据的表空间和数据页编号查询 数据页缓存哈希表 中是否有对应的缓存页。

    3、如果有对应的缓存页,则直接在 Buffer Pool中执行。

    4、如果没有,则检查 free 链表看看有没有空闲的缓存页。

    5、如果有空闲的缓存页,则从磁盘中加载对应的数据页,然后将描述数据块从 free 链表中移除,并且加入到 lru 链表的old数据区域的链表头部。后面如果被修改了,还需要加入到 flush 链表中。

    6、如果没有空闲的缓存页,则将 lru 链表的old数据区域的链表尾部的缓存页刷回磁盘,然后清空,接着将数据页的数据加载到缓存页中,并且描述数据块会加入到 lru 链表的old数据区域的链表头部。后面如果被修改了,还需要加入到 flush 链表中。

    7、5或者6后,就接着在 Buffer Pool 中执行增删改查。

    注意:5和6中,缓存页加入到old数据区域的链表头部后,如果在 1s 后被访问,则将入到new数据区域的链表头部。

    8、最后,就是描述数据块随着 SQL 语句的执行不断地在 free 链表、flush 链表和 lru 链表中移动了。

    更多相关内容
  • 【Java基础-3】吃透Java IO:字节流、字符流、缓冲

    万次阅读 多人点赞 2020-09-23 20:12:33
    什么是Java-IO?字符流和字节流的区别与适用场景是什么?缓冲流到底实现了什么?如何高效地读写文件? 本文用大量的示例图和实例,带你吃透Java IO。

    前言

    有人曾问fastjson的作者(阿里技术专家高铁):“你开发fastjson,没得到什么好处,反而挨了骂背了锅,这种事情你为什么要做呢?”

    高铁答道:“因为热爱本身,就是奖励啊!”

    这个回答顿时触动了我。想想自己,又何尝不是如此。写作是个痛苦的过程,用心写作就更加煎熬,需字字斟酌,反复删改才有所成。然而,当一篇篇精良文章出自己手而呈现眼前时,那些痛苦煎熬就都那么值得。如果这些博文能有幸得大家阅读和认可,就更加是莫大的鼓舞了。技术人的快乐就是可以这么纯粹和简单。

    点波关注不迷路,一键三连好运连连!

    IO流是Java中的一个重要构成部分,也是我们经常打交道的。这篇关于Java IO的博文干货满满,堪称全网前三(请轻喷!)

    下面几个问题(问题还会继续补充),如果你能对答如流,那么恭喜你,IO知识掌握得很好,可以立即关闭文章。反之,你可以在后面得文章中寻找答案。

    1. Java IO流有什么特点?
    2. Java IO流分为几种类型?
    3. 字节流和字符流的关系与区别?
    4. 字符流是否使用了缓冲?
    5. 缓冲流的效率一定高吗?为什么?
    6. 缓冲流体现了Java中的哪种设计模式思想?
    7. 为什么要实现序列化?如何实现序列化?
    8. 序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?

    1 初识Java IO

    IO,即inout,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。

    Java 中是通过流处理IO 的,那么什么是流

    流(Stream),是一个抽象的概念,是指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。

    当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。

    一般来说关于流的特性有下面几点:

    1. 先进先出:最先写入输出流的数据最先被输入流读取到。
    2. 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile除外)
    3. 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。

    1.1 IO流分类

    IO流主要的分类方式有以下3种:

    1. 按数据流的方向:输入流、输出流
    2. 按处理数据单位:字节流、字符流
    3. 按功能:节点流、处理流

    在这里插入图片描述

    1、输入流与输出流

    输入与输出是相对于应用程序而言的,比如文件读写,读取文件是输入流,写文件是输出流,这点很容易搞反。

    在这里插入图片描述
    2、字节流与字符流

    字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。

    为什么要有字符流?

    Java中字符是采用Unicode标准,Unicode 编码中,一个英文字母或一个中文汉字为两个字节。
    在这里插入图片描述
    而在UTF-8编码中,一个中文字符是3个字节。例如下面图中,“云深不知处”5个中文对应的是15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124
    在这里插入图片描述

    那么问题来了,如果使用字节流处理中文,如果一次读写一个字符对应的字节数就不会有问题,一旦将一个字符对应的字节分裂开来,就会出现乱码了。为了更方便地处理中文这些字符,Java就推出了字符流。

    字节流和字符流的其他区别:

    1. 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
    2. 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。详见文末效率对比。

    以写文件为例,我们查看字符流的源码,发现确实有利用到缓冲区:
    在这里插入图片描述
    在这里插入图片描述

    3、节点流和处理流

    节点流:直接操作数据读写的流类,比如FileInputStream

    处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)

    处理流和节点流应用了Java的装饰者设计模式。

    下图就很形象地描绘了节点流和处理流,处理流是对节点流的封装,最终的数据处理还是由节点流完成的。
    在这里插入图片描述
    在诸多处理流中,有一个非常重要,那就是缓冲流

    我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
    在这里插入图片描述

    联想一下生活中的例子,我们搬砖的时候,一块一块地往车上装肯定是很低效的。我们可以使用一个小推车,先把砖装到小推车上,再把这小推车推到车前,把砖装到车上。这个例子中,小推车可以视为缓冲区,小推车的存在,减少了我们装车次数,从而提高了效率。
    在这里插入图片描述
    需要注意的是,缓冲流效率一定高吗?不一定,某些情形下,缓冲流效率反而更低,具体请见IO流效率对比。

    完整的IO分类图如下:
    在这里插入图片描述

    1.2 案例实操

    接下来,我们看看如何使用Java IO。

    文本读写的例子,也就是文章开头所说的,将“松下问童子,言师采药去。只在此山中,云深不知处。”写入本地文本,然后再从文件读取内容并输出到控制台。

    1、FileInputStream、FileOutputStream(字节流)

    字节流的方式效率较低,不建议使用

    public class IOTest {
    	public static void main(String[] args) throws IOException {
    		File file = new File("D:/test.txt");
    
    		write(file);
    		System.out.println(read(file));
    	}
    
    	public static void write(File file) throws IOException {
    		OutputStream os = new FileOutputStream(file, true);
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		// 写入文件
    		os.write(string.getBytes());
    		// 关闭流
    		os.close();
    	}
    
    	public static String read(File file) throws IOException {
    		InputStream in = new FileInputStream(file);
    
    		// 一次性取多少个字节
    		byte[] bytes = new byte[1024];
    		// 用来接收读取的字节数组
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字节数组长度,为-1时表示没有数据
    		int length = 0;
    		// 循环取数据
    		while ((length = in.read(bytes)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(new String(bytes, 0, length));
    		}
    		// 关闭流
    		in.close();
    
    		return sb.toString();
    	}
    }
    

    2、BufferedInputStream、BufferedOutputStream(缓冲字节流)

    缓冲字节流是为高效率而设计的,真正的读写操作还是靠FileOutputStreamFileInputStream,所以其构造方法入参是这两个类的对象也就不奇怪了。

    public class IOTest {
    
    	public static void write(File file) throws IOException {
    		// 缓冲字节流,提高了效率
    		BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream(file, true));
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		// 写入文件
    		bis.write(string.getBytes());
    		// 关闭流
    		bis.close();
    	}
    
    	public static String read(File file) throws IOException {
    		BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));
    
    		// 一次性取多少个字节
    		byte[] bytes = new byte[1024];
    		// 用来接收读取的字节数组
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字节数组长度,为-1时表示没有数据
    		int length = 0;
    		// 循环取数据
    		while ((length = fis.read(bytes)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(new String(bytes, 0, length));
    		}
    		// 关闭流
    		fis.close();
    
    		return sb.toString();
    	}
    }
    

    3、InputStreamReader、OutputStreamWriter(字符流)

    字符流适用于文本文件的读写OutputStreamWriter类其实也是借助FileOutputStream类实现的,故其构造方法是FileOutputStream的对象

    public class IOTest {
    	
    	public static void write(File file) throws IOException {
    		// OutputStreamWriter可以显示指定字符集,否则使用默认字符集
    		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8");
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		osw.write(string);
    		osw.close();
    	}
    
    	public static String read(File file) throws IOException {
    		InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
    		// 字符数组:一次读取多少个字符
    		char[] chars = new char[1024];
    		// 每次读取的字符数组先append到StringBuilder中
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字符数组长度,为-1时表示没有数据
    		int length;
    		// 循环取数据
    		while ((length = isr.read(chars)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(chars, 0, length);
    		}
    		// 关闭流
    		isr.close();
    
    		return sb.toString()
    	}
    }
    

    4、字符流便捷类

    Java提供了FileWriterFileReader简化字符流的读写,new FileWriter等同于new OutputStreamWriter(new FileOutputStream(file, true))

    public class IOTest {
    	
    	public static void write(File file) throws IOException {
    		FileWriter fw = new FileWriter(file, true);
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		fw.write(string);
    		fw.close();
    	}
    
    	public static String read(File file) throws IOException {
    		FileReader fr = new FileReader(file);
    		// 一次性取多少个字节
    		char[] chars = new char[1024];
    		// 用来接收读取的字节数组
    		StringBuilder sb = new StringBuilder();
    		// 读取到的字节数组长度,为-1时表示没有数据
    		int length;
    		// 循环取数据
    		while ((length = fr.read(chars)) != -1) {
    			// 将读取的内容转换成字符串
    			sb.append(chars, 0, length);
    		}
    		// 关闭流
    		fr.close();
    
    		return sb.toString();
    	}
    }
    

    5、BufferedReader、BufferedWriter(字符缓冲流)

    public class IOTest {
    	
    	public static void write(File file) throws IOException {
    		// BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(new
    		// FileOutputStream(file, true), "UTF-8"));
    		// FileWriter可以大幅度简化代码
    		BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));
    
    		// 要写入的字符串
    		String string = "松下问童子,言师采药去。只在此山中,云深不知处。";
    		bw.write(string);
    		bw.close();
    	}
    
    	public static String read(File file) throws IOException {
    		BufferedReader br = new BufferedReader(new FileReader(file));
    		// 用来接收读取的字节数组
    		StringBuilder sb = new StringBuilder();
    
    		// 按行读数据
    		String line;
    		// 循环取数据
    		while ((line = br.readLine()) != null) {
    			// 将读取的内容转换成字符串
    			sb.append(line);
    		}
    		// 关闭流
    		br.close();
    
    		return sb.toString();
    	}
    }
    

    2 IO流对象

    第一节中,我们大致了解了IO,并完成了几个案例,但对IO还缺乏更详细的认知,那么接下来我们就对Java IO细细分解,梳理出完整的知识体系来。

    Java种提供了40多个类,我们只需要详细了解一下其中比较重要的就可以满足日常应用了。

    2.1 File类

    File类是用来操作文件的类,但它不能操作文件中的数据。

    public class File extends Object implements Serializable, Comparable<File>
    

    File类实现了SerializableComparable<File>,说明它是支持序列化和排序的。

    File类的构造方法

    方法名说明
    File(File parent, String child)根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
    File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
    File(String parent, String child)根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
    File(URI uri)通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。

    File类的常用方法

    方法说明
    createNewFile()当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
    delete()删除此抽象路径名表示的文件或目录。
    exists()测试此抽象路径名表示的文件或目录是否存在。
    getAbsoluteFile()返回此抽象路径名的绝对路径名形式。
    getAbsolutePath()返回此抽象路径名的绝对路径名字符串。
    length()返回由此抽象路径名表示的文件的长度。
    mkdir()创建此抽象路径名指定的目录。

    File类使用实例

    public class FileTest {
    	public static void main(String[] args) throws IOException {
    		File file = new File("C:/Mu/fileTest.txt");
    
    		// 判断文件是否存在
    		if (!file.exists()) {
    			// 不存在则创建
    			file.createNewFile();
    		}
    		System.out.println("文件的绝对路径:" + file.getAbsolutePath());
    		System.out.println("文件的大小:" + file.length());
    
    		// 刪除文件
    		file.delete();
    	}
    }
    

    2.2 字节流

    InputStreamOutputStream是两个抽象类,是字节流的基类,所有具体的字节流实现类都是分别继承了这两个类。

    InputStream为例,它继承了Object,实现了Closeable

    public abstract class InputStream
    extends Object
    implements Closeable
    

    InputStream类有很多的实现子类,下面列举了一些比较常用的:
    在这里插入图片描述
    详细说明一下上图中的类:

    1. InputStreamInputStream是所有字节输入流的抽象基类,前面说过抽象类不能被实例化,实际上是作为模板而存在的,为所有实现类定义了处理输入流的方法。
    2. FileInputSream:文件输入流,一个非常重要的字节输入流,用于对文件进行读取操作。
    3. PipedInputStream:管道字节输入流,能实现多线程间的管道通信。
    4. ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去。
    5. FilterInputStream:装饰者类,具体的装饰者继承该类,这些类都是处理类,作用是对节点类进行封装,实现一些特殊功能。
    6. DataInputStream:数据输入流,它是用来装饰其它输入流,作用是“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
    7. BufferedInputStream:缓冲流,对节点流进行装饰,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送,效率更高。
    8. ObjectInputStream:对象输入流,用来提供对基本数据或对象的持久存储。通俗点说,也就是能直接传输对象,通常应用在反序列化中。它也是一种处理流,构造器的入参是一个InputStream的实例对象。

    OutputStream类继承关系图:
    在这里插入图片描述

    OutputStream类继承关系与InputStream类似,需要注意的是PrintStream.

    2.3 字符流

    与字节流类似,字符流也有两个抽象基类,分别是ReaderWriter。其他的字符流实现类都是继承了这两个类。

    Reader为例,它的主要实现子类如下图:
    在这里插入图片描述
    各个类的详细说明:

    1. InputStreamReader:从字节流到字符流的桥梁(InputStreamReader构造器入参是FileInputStream的实例对象),它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以通过名称指定,也可以显式给定,或者可以接受平台的默认字符集。
    2. BufferedReader:从字符输入流中读取文本,设置一个缓冲区来提高效率。BufferedReader是对InputStreamReader的封装,前者构造器的入参就是后者的一个实例对象。
    3. FileReader:用于读取字符文件的便利类,new FileReader(File file)等同于new InputStreamReader(new FileInputStream(file, true),"UTF-8"),但FileReader不能指定字符编码和默认字节缓冲区大小。
    4. PipedReader :管道字符输入流。实现多线程间的管道通信。
    5. CharArrayReader:从Char数组中读取数据的介质流。
    6. StringReader :从String中读取数据的介质流。

    WriterReader结构类似,方向相反,不再赘述。唯一有区别的是,Writer的子类PrintWriter

    2.4 序列化

    待续…

    3 IO流方法

    3.1 字节流方法

    字节输入流InputStream主要方法:

    • read() :从此输入流中读取一个数据字节。
    • read(byte[] b) :从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
    • read(byte[] b, int off, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
    • close():关闭此输入流并释放与该流关联的所有系统资源。

    字节输出流OutputStream主要方法:

    • write(byte[] b) :将 b.length 个字节从指定 byte 数组写入此文件输出流中。
    • write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
    • write(int b) :将指定字节写入此文件输出流。
    • close() :关闭此输入流并释放与该流关联的所有系统资源。

    3.2 字符流方法

    字符输入流Reader主要方法:

    • read():读取单个字符。
    • read(char[] cbuf) :将字符读入数组。
    • read(char[] cbuf, int off, int len) : 将字符读入数组的某一部分。
    • read(CharBuffer target) :试图将字符读入指定的字符缓冲区。
    • flush() :刷新该流的缓冲。
    • close() :关闭此流,但要先刷新它。

    字符输出流Writer主要方法:

    • write(char[] cbuf) :写入字符数组。
    • write(char[] cbuf, int off, int len) :写入字符数组的某一部分。
    • write(int c) :写入单个字符。
    • write(String str) :写入字符串。
    • write(String str, int off, int len) :写入字符串的某一部分。
    • flush() :刷新该流的缓冲。
    • close() :关闭此流,但要先刷新它。

    另外,字符缓冲流还有两个独特的方法:

    • BufferedWriternewLine()写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。
    • BufferedReaderreadLine() :读取一个文本行。

    4 附加内容

    4.1 位、字节、字符

    字节(Byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位。

    字符(Character)计算机中使用的字母、数字、字和符号,比如’A’、‘B’、’$’、’&'等。

    一般在英文状态下一个字母或字符占用一个字节,一个汉字用两个字节表示。

    字节与字符:

    • ASCII 码中,一个英文字母(不分大小写)为一个字节,一个中文汉字为两个字节。
    • UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
    • Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
    • 符号:英文标点为一个字节,中文标点为两个字节。例如:英文句号 . 占1个字节的大小,中文句号 。占2个字节的大小。
    • UTF-16 编码中,一个英文字母字符或一个汉字字符存储都需要 2 个字节(Unicode 扩展区的一些汉字存储需要 4 个字节)。
    • UTF-32 编码中,世界上任何字符的存储都需要 4 个字节。

    4.2 IO流效率对比

    首先,对比下普通字节流和缓冲字节流的效率:

    public class MyTest {
    	public static void main(String[] args) throws IOException {
    		File file = new File("C:/Mu/test.txt");
    		StringBuilder sb = new StringBuilder();
    
    		for (int i = 0; i < 3000000; i++) {
    			sb.append("abcdefghigklmnopqrstuvwsyz");
    		}
    		byte[] bytes = sb.toString().getBytes();
    
    		long start = System.currentTimeMillis();
    		write(file, bytes);
    		long end = System.currentTimeMillis();
    
    		long start2 = System.currentTimeMillis();
    		bufferedWrite(file, bytes);
    		long end2 = System.currentTimeMillis();
    
    		System.out.println("普通字节流耗时:" + (end - start) + " ms");
    		System.out.println("缓冲字节流耗时:" + (end2 - start2) + " ms");
    
    	}
    
    	// 普通字节流
    	public static void write(File file, byte[] bytes) throws IOException {
    		OutputStream os = new FileOutputStream(file);
    		os.write(bytes);
    		os.close();
    	}
    
    	// 缓冲字节流
    	public static void bufferedWrite(File file, byte[] bytes) throws IOException {
    		BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(file));
    		bo.write(bytes);
    		bo.close();
    	}
    }
    

    运行结果:

    普通字节流耗时:250 ms
    缓冲字节流耗时:268 ms
    

    这个结果让我大跌眼镜,不是说好缓冲流效率很高么?要知道为什么,只能去源码里找答案了。翻看字节缓冲流的write方法:

    public synchronized void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {
            /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               In this way buffered streams will cascade harmlessly. */
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }
    

    注释里说得很明白:如果请求长度超过输出缓冲区的大小,刷新输出缓冲区,然后直接写入数据。这样,缓冲流将无害地级联。

    但是,至于为什么这么设计,我没有想明白,有哪位明白的大佬可以留言指点一下。

    基于上面的情形,要想对比普通字节流和缓冲字节流的效率差距,就要避免直接读写较长的字符串,于是,设计了下面这个对比案例:用字节流和缓冲字节流分别复制文件。

    public class MyTest {
    	public static void main(String[] args) throws IOException {
    		File data = new File("C:/Mu/data.zip");
    		File a = new File("C:/Mu/a.zip");
    		File b = new File("C:/Mu/b.zip");
    
    		StringBuilder sb = new StringBuilder();
    
    		long start = System.currentTimeMillis();
    		copy(data, a);
    		long end = System.currentTimeMillis();
    
    		long start2 = System.currentTimeMillis();
    		bufferedCopy(data, b);
    		long end2 = System.currentTimeMillis();
    
    		System.out.println("普通字节流耗时:" + (end - start) + " ms");
    		System.out.println("缓冲字节流耗时:" + (end2 - start2) + " ms");
    	}
    
    	// 普通字节流
    	public static void copy(File in, File out) throws IOException {
    		// 封装数据源
    		InputStream is = new FileInputStream(in);
    		// 封装目的地
    		OutputStream os = new FileOutputStream(out);
    		
    		int by = 0;
    		while ((by = is.read()) != -1) {
    			os.write(by);
    		}
    		is.close();
    		os.close();
    	}
    
    	// 缓冲字节流
    	public static void bufferedCopy(File in, File out) throws IOException {
    		// 封装数据源
    		BufferedInputStream bi = new BufferedInputStream(new FileInputStream(in));
    		// 封装目的地
    		BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(out));
    		
    		int by = 0;
    		while ((by = bi.read()) != -1) {
    			bo.write(by);
    		}
    		bo.close();
    		bi.close();
    	}
    }
    

    运行结果:

    普通字节流耗时:184867 ms
    缓冲字节流耗时:752 ms
    

    这次,普通字节流和缓冲字节流的效率差异就很明显了,达到了245倍。

    再看看字符流和缓冲字符流的效率对比:

    public class IOTest {
    	public static void main(String[] args) throws IOException {
    		// 数据准备
    		dataReady();
    
    		File data = new File("C:/Mu/data.txt");
    		File a = new File("C:/Mu/a.txt");
    		File b = new File("C:/Mu/b.txt");
    		File c = new File("C:/Mu/c.txt");
    
    		long start = System.currentTimeMillis();
    		copy(data, a);
    		long end = System.currentTimeMillis();
    
    		long start2 = System.currentTimeMillis();
    		copyChars(data, b);
    		long end2 = System.currentTimeMillis();
    
    		long start3 = System.currentTimeMillis();
    		bufferedCopy(data, c);
    		long end3 = System.currentTimeMillis();
    
    		System.out.println("普通字节流1耗时:" + (end - start) + " ms,文件大小:" + a.length() / 1024 + " kb");
    		System.out.println("普通字节流2耗时:" + (end2 - start2) + " ms,文件大小:" + b.length() / 1024 + " kb");
    		System.out.println("缓冲字节流耗时:" + (end3 - start3) + " ms,文件大小:" + c.length() / 1024 + " kb");
    	}
    
    	// 普通字符流不使用数组
    	public static void copy(File in, File out) throws IOException {
    		Reader reader = new FileReader(in);
    		Writer writer = new FileWriter(out);
    
    		int ch = 0;
    		while ((ch = reader.read()) != -1) {
    			writer.write((char) ch);
    		}
    		reader.close();
    		writer.close();
    	}
    
    	// 普通字符流使用字符流
    	public static void copyChars(File in, File out) throws IOException {
    		Reader reader = new FileReader(in);
    		Writer writer = new FileWriter(out);
    
    		char[] chs = new char[1024];
    		while ((reader.read(chs)) != -1) {
    			writer.write(chs);
    		}
    		reader.close();
    		writer.close();
    	}
    
    	// 缓冲字符流
    	public static void bufferedCopy(File in, File out) throws IOException {
    		BufferedReader br = new BufferedReader(new FileReader(in));
    		BufferedWriter bw = new BufferedWriter(new FileWriter(out));
    
    		String line = null;
    		while ((line = br.readLine()) != null) {
    			bw.write(line);
    			bw.newLine();
    			bw.flush();
    		}
    
    		// 释放资源
    		bw.close();
    		br.close();
    	}
    
    	// 数据准备
    	public static void dataReady() throws IOException {
    		StringBuilder sb = new StringBuilder();
    		for (int i = 0; i < 600000; i++) {
    			sb.append("abcdefghijklmnopqrstuvwxyz");
    		}
    		OutputStream os = new FileOutputStream(new File("C:/Mu/data.txt"));
    		os.write(sb.toString().getBytes());
    
    		os.close();
    		System.out.println("完毕");
    	}
    }
    

    运行结果:

    普通字符流1耗时:1337 ms,文件大小:15234 kb
    普通字符流2耗时:82 ms,文件大小:15235 kb
    缓冲字符流耗时:205 ms,文件大小:15234 kb
    

    测试多次,结果差不多,可见字符缓冲流效率上并没有明显提高,我们更多的是要使用它的readLine()newLine()方法。

    4.3 NIO

    待续…

    展开全文
  • 浅谈缓冲的理论与实践

    千次阅读 多人点赞 2022-03-23 15:53:30
    本文介绍了缓冲(Buffer)在Java开发中的常见用法和注意事项。

    博客主页:https://tomcat.blog.csdn.net
    博主昵称:农民工老王
    主要领域:Java、Linux、K8S
    期待大家的关注💖点赞👍收藏⭐留言💬
    家乡

    本文将详细介绍“缓冲”这个优化手段,之前在 Java性能优化的七个方向的复用优化中便提到过“缓冲”,你可以回看复习一下。

    深入理解缓冲的本质

    缓冲(Buffer)通过对数据进行暂存,然后批量进行传输或者操作,多采用顺序方式,来缓解不同设备之间次数频繁但速度缓慢的随机读写。

    你可以把缓冲区,想象成一个蓄水池。放水的水龙头一直开着,如果池子里有水,它就以恒定的速度流淌,不需要暂停;供水的水龙头速度却不确定,有时候会快一些,有时候会特别慢。它通过判断水池里水的状态,就可以自由控制进水的速度。

    或者再想象一下包饺子的过程,包馅的需要等着擀皮的。如果擀皮的每擀一个就交给包馅的,速度就会很慢;但如果中间放一个盆子,擀皮的只管往里扔,包馅的只管从盆里取,这个过程就快得多。许多工厂流水线也经常使用这种方法,可见“缓冲”这个理念的普及性和实用性。
    在这里插入图片描述
    从宏观上来说,JVM 的堆就是一个大的缓冲区,代码不停地在堆空间中生产对象,而垃圾回收器进程则在背后默默地进行垃圾回收。

    通过上述比喻和释意,你可以发现缓冲区的好处:

    • 缓冲双方能各自保持自己的操作节奏,操作处理顺序也不会打乱,可以 one by one 顺序进行;

    • 以批量的方式处理,减少网络交互和繁重的 I/O 操作,从而减少性能损耗;

    • 优化用户体验,比如常见的音频/视频缓冲加载,通过提前缓冲数据,达到流畅的播放效果。

    Java 语言广泛应用了缓冲,在 IDEA 中搜索 Buffer,可以看到长长的类列表,其中最典型的就是文件读取和写入字符流。
    在这里插入图片描述

    文件读写流

    接下来,本文将以文件读取和写入字符流为例进行讲解。

    Java 的 I/O 流设计,采用的是装饰器模式,当需要给类添加新的功能时,就可以将被装饰者通过参数传递到装饰者,封装成新的功能方法。下图是装饰器模式的典型示意图,就增加功能来说,装饰模式比生成子类更为灵活。
    在这里插入图片描述
    在读取和写入流的 API 中,BufferedInputStream 和 BufferedReader 可以加快读取字符的速度,BufferedOutputStream 和 BufferedWriter 可以加快写入的速度。

    下面是直接读取文件的代码实现:

    int result = 0;
    
    try(Reader reader=new FileReader(FILE_PATH)){
        int value;
        while((value=reader.read())!=-1){
            result+=value;
        }
    }
    return result;
    

    要使用缓冲方式读取,只需要将 FileReader 装饰一下即可:

    int result = 0;
    try (Reader reader = new BufferedReader(new FileReader(FILE_PATH))) {
        int value;
        while ((value = reader.read()) != -1) {
            result += value;
        }
    }
    return result;
    

    我们先看一下与之类似的,BufferedInputStream 类的具体实现方法:

    //代码来自JDK 
    public synchronized int read () throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
    

    当缓冲区的内容读取完毕,将尝试使用 fill 函数把输入流读入缓冲区:

    //代码来自JDK 
    private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte[] nbuf = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!U.compareAndSetObject(this, BUF_OFFSET, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }
    

    程序会调整一些读取的位置,并对缓冲区进行位置更新,然后使用被装饰的 InputStream 进行数据读取:

    int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    

    那么为什么要这么做呢?直接读写不行吗?

    这是因为:字符流操作的对象,一般是文件或者 Socket,要从这些缓慢的设备中,通过频繁的交互获取数据,效率非常慢;而缓冲区的数据是保存在内存中的,能够显著地提升读写速度。

    既然好处那么多,为什么不把所有的数据全部读到缓冲区呢?

    这就是一个权衡的问题,缓冲区开得太大,会增加单次读写的时间,同时内存价格很高,不能无限制使用,缓冲流的默认缓冲区大小是 8192 字节,也就是 8KB,算是一个比较折中的值。

    这好比搬砖,如果一块一块搬,时间便都耗费在往返路上了;但若给你一个小推车,往返的次数便会大大降低,效率自然会有所提升。

    下图是使用 FileReader 和 BufferedReader 读取文件的 JMH 对比,可以看到,使用了缓冲,读取效率有了很大的提升(暂未考虑系统文件缓存)。

    package cn.wja;
    import org.openjdk.jmh.annotations.*;
    import org.openjdk.jmh.runner.Runner;
    import org.openjdk.jmh.runner.options.Options;
    import org.openjdk.jmh.runner.options.OptionsBuilder;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.Reader;
    import java.util.concurrent.TimeUnit;
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Fork(1)
    public class BenchmarkReader {
        private static final String FILE_PATH = "F:\\农民工老王\\servertools.zip";
    
        @Benchmark
        public int bufferedReaderTest() throws Exception {
            int result = 0;
            try (Reader reader = new BufferedReader(new FileReader(FILE_PATH))) {
                int value;
                while ((value = reader.read()) != -1) {
                    result += value;
                }
            }
            return result;
        }
    
        @Benchmark
        public int fileReadTest() throws Exception {
            int result = 0;
            try (Reader reader = new FileReader(FILE_PATH)) {
                int value;
                while ((value = reader.read()) != -1) {
                    result += value;
                }
            }
            return result;
        }
    
        public static void main(String[] args) throws Exception {
            Options opts = new OptionsBuilder().include(BenchmarkReader.class.getSimpleName()).build();
            new Runner(opts).run();
        }
    }
    

    在这里插入图片描述

    日志缓冲

    日志是程序员们最常打交道的地方。在高并发应用中,即使对日志进行了采样,日志数量依旧惊人,所以选择高速的日志组件至关重要。

    SLF4J 是 Java 里标准的日志记录库,它是一个允许你使用任何 Java 日志记录库的抽象适配层,最常用的实现是 Logback,支持修改后自动 reload,它比 Java 自带的 JUL 还要流行。
    Logback 性能也很高,其中一个原因就是异步日志,它在记录日志时,使用了一个缓冲队列,当缓冲的内容达到一定的阈值时,才会把缓冲区的内容写到文件里。使用异步日志有两个考虑:

    • 同步日志的写入,会阻塞业务,导致服务接口的耗时增加;

    • 日志写入磁盘的代价是昂贵的,如果每产生一条日志就写入一次,CPU 会花很多时间在磁盘 I/O 上。

    Logback 的异步日志也比较好配置,我们需要在正常配置的基础上,包装一层异步输出的逻辑

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <property name="LOG_HOME" value="."/>
        <property name="ENCODER_PATTERN"
                  value="%d{yyyy-MM-dd  HH:mm:ss.SSS} %-5level [%10.10thread] [%X{X-B3-TraceId}] %logger{20} - %msg%n"/>
                  
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/test.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/backup/log.log.%d{yyyy-MM-dd}</fileNamePattern>
                <maxHistory>100</maxHistory>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${ENCODER_PATTERN}</pattern>
            </encoder>
        </appender>
    
        <appender name="FILE2" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/test2.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/backup/log2.log.%d{yyyy-MM-dd}</fileNamePattern>
                <maxHistory>100</maxHistory>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${ENCODER_PATTERN}</pattern>
            </encoder>
        </appender>
    
        <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
            <discardingThreshold>0</discardingThreshold>
            <queueSize>512</queueSize>
            <appender-ref ref="FILE"/>
        </appender>
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>${ENCODER_PATTERN}</pattern>
            </layout>
        </appender>
    
        <logger name="cn.wja.log.synclog" level="INFO">
            <appender-ref ref="ASYNC"/>
        </logger>
    
        <logger name="cn.wja.log.asynclog" level="INFO">
            <appender-ref ref="FILE2"/>
        </logger>
    </configuration>
    

    如下图,异步日志输出之后,日志信息将暂存在 ArrayBlockingQueue 列表中,后台会有一个 Worker 线程不断地获取缓冲区内容,然后写入磁盘中。
    在这里插入图片描述
    上图中有三个关键参数:

    • queueSize,代表了队列的大小,默认是256。如果这个值设置的太大,大日志量下突然断电,会丢掉缓冲区的内容;

    • maxFlushTime,关闭日志上下文后,继续执行写任务的时间,这是通过调用 Thread 类的 join 方法来实现的(worker.join(maxFlushTime));

    • discardingThreshold,当 queueSize 快达到上限时,可以通过配置,丢弃一些级别比较低的日志,这个值默认是队列长度的 80%;但若你担心可能会丢失业务日志,则可以将这个值设置成 0,表示所有的日志都要打印。

    缓冲区优化思路

    毫无疑问缓冲区是可以提高性能的,但它通常会引入一个异步的问题,使得编程模型变复杂。

    通过文件读写流和 Logback 两个例子,我们来看一下对于缓冲区设计的一些常规操作。

    如下图所示,资源 A 读取或写入一些操作到资源 B,这本是一个正常的操作流程,但由于中间插入了一个额外的存储层,所以这个流程被生生截断了,这时就需要你手动处理被截断两方的资源协调问题。
    在这里插入图片描述
    根据资源的不同,对正常业务进行截断后的操作,分为同步操作和异步操作。

    同步操作

    同步操作的编程模型相对简单,在一个线程中就可完成,你只需要控制缓冲区的大小,并把握处理的时机。比如,缓冲区大小达到阈值,或者缓冲区的元素在缓冲区的停留时间超时,这时就会触发批量操作。

    由于所有的操作又都在单线程,或者同步方法块中完成,再加上资源 B 的处理能力有限,那么很多操作就会阻塞并等待在调用线程上。比如写文件时,需要等待前面的数据写入完毕,才能处理后面的请求。
    在这里插入图片描述

    异步操作

    异步操作就复杂很多。

    缓冲区的生产者一般是同步调用,但也可以采用异步方式进行填充,一旦采用异步操作,就涉及缓冲区满了以后,生产者的一些响应策略。

    此时,应该将这些策略抽象出来,根据业务的属性选择,比如直接抛弃、抛出异常,或者直接在用户的线程进行等待。你会发现它与线程池的饱和策略是类似的,这部分的详细概念将在后续的文章中讲解。

    许多应用系统还会有更复杂的策略,比如在用户线程等待,设置一个超时时间,以及成功进入缓冲区之后的回调函数等。

    对缓冲区的消费,一般采用开启线程的方式,如果有多个线程消费缓冲区,还会存在信息同步和顺序问题。
    在这里插入图片描述

    Kafka缓冲区示例

    这里以一个常见的面试题来讲解上面的知识点:Kafka 的生产者,有可能会丢数据吗?
    在这里插入图片描述
    如图,要想解答这个问题,需要先了解 Kafka 对生产者的一些封装,其中有一个对性能影响非常大的点,就是缓冲。

    生产者会把发送到同一个 partition 的多条消息,封装在一个 batch(缓冲区)中。当 batch 满了(参数 batch.size),或者消息达到了超时时间(参数 linger.ms),缓冲区中的消息就会被发送到 broker 上。

    这个缓冲区默认是 16KB,如果生产者的业务突然断电,这 16KB 数据是没有机会发送出去的。此时,就造成了消息丢失。

    解决的办法有两种:

    • 把缓冲区设置得非常小,此时消息会退化成单条发送,这会严重影响性能;
    • 消息发送前记录一条日志,消息发送成功后,通过回调再记录一条日志,通过扫描生成的日志,就可以判断哪些消息丢失了。

    另外一个面试的问题是:Kafka 生产者会影响业务的高可用吗?

    这同样和生产者的缓冲区有关。缓冲区大小毕竟是有限制的,如果消息产生得过快,或者生产者与 broker 节点之间有网络问题,缓冲区就会一直处于 full 的状态。此时,有新的消息到达,会如何处理呢?

    通过配置生产者的超时参数和重试次数,可以让新的消息一直阻塞在业务方。一般来说,这个超时值设置成 1 秒就已经够大了,有的应用在线上把超时参数配置得非常大,比如 1 分钟,就造成了用户的线程迅速占满,整个业务不能再接受新的请求。

    缓冲区的其他案例

    使用缓冲区来提升性能的做法非常多,下面再举几个例子:

    • StringBuilder 和 StringBuffer,通过将要处理的字符串缓冲起来,最后完成拼接,提高字符串拼接的性能;

    • 操作系统在写入磁盘,或者网络 I/O 时,会开启特定的缓冲区,来提升信息流转的效率。通常可使用 flush 函数强制刷新数据,比如通过调整 Socket 的参数 SO_SNDBUF 和 SO_RCVBUF 提高网络传输性能;

    • MySQL 的 InnoDB 引擎,通过配置合理的 innodb_buffer_pool_size,减少换页,增加数据库的性能;

    • 在一些比较底层的工具中,也会变相地用到缓冲。比如常见的 ID 生成器,使用方通过缓冲一部分 ID 段,就可以避免频繁、耗时的交互。

    缓冲区的注意事项

    虽然缓冲区可以帮我们大大地提高应用程序的性能,但同时它也有不少问题,在我们设计时,要注意这些异常情况。

    其中,比较严重就是缓冲区内容的丢失。即使你使用 addShutdownHook 做了优雅关闭,有些情形依旧难以防范避免,比如机器突然间断电,应用程序进程突然死亡等。这时,缓冲区内未处理完的信息便会丢失,尤其金融信息,电商订单信息的丢失都是比较严重的。

    所以,内容写入缓冲区之前,需要先预写日志,故障后重启时,就会根据这些日志进行数据恢复。在数据库领域,文件缓冲的场景非常多,一般都是采用 WAL 日志(Write-Ahead Logging)解决。对数据完整性比较严格的系统,甚至会通过电池或者 UPS 来保证缓冲区的落地。这就是性能优化带来的新问题,必须要解决。

    小结

    可以看到,缓冲区优化是对正常的业务流程进行截断,然后加入缓冲组件的一个操作,它分为同步和异步方式,其中异步方式的实现难度相对更高。

    大多数组件,从操作系统到数据库,从 Java 的 API 到一些中间件,都可以通过设置一些参数,来控制缓冲区大小,从而取得较大的性能提升。但需要注意的是,某些极端场景(断电、异常退出、kill -9等)可能会造成数据丢失,若你的业务对此容忍度较低,那么你需要花更多精力来应对这些异常。

    在我们面试的时候,除了考察大家对知识细节的掌握程度,还会考察总结能力,以及遇到相似问题的分析能力。大家在平常的工作中,也要多多总结,多多思考,窥一斑而知全貌。如此回答,必会让面试官眼前一亮。


    如需转载,请注明本文的出处:农民工老王的CSDN博客https://blog.csdn.net/monarch91 。

    展开全文
  • 一、缓冲区管理 (一)什么是缓冲区?有什么作用? 缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可利用内存作为缓冲区。 使用硬件作为缓冲区的成本较高,容量也较小,一般仅用在对速度要求非常高的场合...

    一、缓冲区管理

    (一)什么是缓冲区?有什么作用?

    • 缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可利用内存作为缓冲区。
    • 使用硬件作为缓冲区成本较高,容量也较小,一般仅用在对速度要求非常高的场合(如存储器管理中所用的联想寄存器,由于对页表的访问频率极高,因此使用速度很快的联想寄存器来存放页表项的副本)
    • 一般情况下,更多的是利用内存作为缓冲区,“设备独立性软件”的缓冲区管理就是要组织管理好这些缓冲区。
      在这里插入图片描述

    (二)单缓冲

    • 假设某用户进程请求某种块设备读入若干块的数据。若采用单缓冲的策略,操作系统会在主存中为其分配一个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)。
    • 注意:当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出;当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满以后,才能从缓冲区把数据传出。
      在这里插入图片描述

    (1)情况一:T>C

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    (2)情况二:T<C

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    (3)总结:

    在这里插入图片描述

    • 结论:采用单缓冲策略,处理一块数据平均耗时 Max(C, T)+M

    (三)双缓冲

    • 假设某用户进程请求某种块设备读入若干块的数据。若采用双缓冲的策略,操作系统会在主存中为其分配两个缓冲区(若题目中没有特别说明,一个缓冲区的大小就是一个块)
    • 双缓冲题目中,假设初始状态为:工作区空,其中一个缓冲区满,另一个缓冲区空

    (1)情况一:T>C+M

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    (1)情况:T<C+M

    在这里插入图片描述

    • 结论:采用双缓冲策略,处理一个数据块的平均耗时为 Max (T, C+M)

    (四)使用单/双缓冲在通信时的区别

    • 两台机器之间通信时,可以配置缓冲区用于数据的发送和接受。
    • 显然,若两个相互通信的机器只设置单缓冲区,在任一时刻只能实现数据的单向传输。
      在这里插入图片描述
      在这里插入图片描述
    • 若两个相互通信的机器设置双缓冲区,则同一时刻可以实现双向的数据传输。
    • 注:管道通信中的“管道”其实就是缓冲区。要实现数据的双向传输,必须设置两个管道
      在这里插入图片描述

    (五)循环缓冲区

    • 将多个大小相等的缓冲区链接成一个循环队列
    • 注:以下图示中,橙色表示已充满数据的缓冲区,绿色表示空缓冲区。
      在这里插入图片描述

    (六)缓冲池

    • 缓冲池由系统中共用的缓冲区组成。这些缓冲区按使用状况可以分为:空缓冲队列、装满输入数据的缓冲队列(输入队列)、装满输出数据的缓冲队列(输出队列)。
    • 另外,根据一个缓冲区在实际运算中扮演的功能不同,又设置了四种工作缓冲区:用于收容输入数据的工作缓冲区(hin)、用于提取输入数据的工作缓冲区(sin)、用于收容输出数据的工作缓冲区(hout)、用于提取输出数据的工作缓冲区(sout)
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    展开全文
  • 21 Redis 缓冲区的用法

    万次阅读 2021-12-09 22:11:39
    21 Redis 缓冲区的用法前言一、客户端输入和输出缓冲区二、输入缓冲区溢出的应对方法三、输出缓冲区溢出的应对方法四、主从集群中的缓冲区总结 前言 缓冲区的功能是用一块内存空间来暂时存放命令数据,以免出现...
  • 缓冲与双缓冲

    千次阅读 2021-07-30 21:13:39
    缓冲缓冲即在发送进程与接收进程之间只有一个缓冲区,这是操作系统提供的最简单的一种缓冲形式,如图所示。 发送者向缓冲区发送数据后,接收者可从缓冲区中取出该数据。这种方法有一个明显的缺点,即发送者和...
  • 多图详解缓冲区溢出问题

    万次阅读 多人点赞 2020-11-18 23:43:21
    缓冲区溢出一个常见的后果是:黑客利用函数调用过程中程序的返回地址,将存放这块地址的指针精准指向计算机中存放攻击代码的位置,造成程序异常中止。为了防止发生严重的后果,计算机会采用栈随机化,利用金丝雀值...
  • STM32F103 使用定时器触发ADC,并使用DMA中断+双缓冲实现数据采集。 博客地址 http://blog.csdn.net/u014124220/article/details/50785775
  • 缓冲、双缓冲、循环缓冲

    千次阅读 2020-05-25 21:49:34
    缓冲区管理 什么是缓冲区?有什么作用? 缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可以利用内存作为缓冲区。 使用硬件作为缓冲区的成本较高,容量也较小,一般仅用在对速度非常高的场合(存储器管理...
  • 什么是缓冲区(buffer),什么是缓存(cache)

    万次阅读 多人点赞 2020-11-17 22:31:03
    比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故...
  • 缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。 缓冲区根据其对应的是输入设备还是输出设备,分为...
  • 缓冲液(Buffer solution)通常是由「弱酸及其共轭碱」或「弱碱及其共轭酸」缓冲对所组成的溶液,能够在加入一定量其他物质时减缓pH的改变。以生物实验中最常用的一种缓冲液PBS为例,是由Na2HPO4、KH2PO4组成的缓冲...
  • C语言常见漏洞-缓冲区溢出

    千次阅读 2021-07-21 11:27:21
    缓冲区溢出1.原理2.攻击方式2.1 利用shellcode2.2 跳到其它函数2.3 其它情况3.防护3.1 canary(栈保护)3.2 NX3.3 RELRO3.4 PIE 缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据覆盖...
  • Author:阿冬哥Created:2013-4-17Blog:http://blog.csdn.net/c359719435/Copyright 2013阿冬哥http://blog.csdn.net/c359719435/使用以及转载请注明出处1 设置...SNDBUF、SO_RCVBUF这连个默认缓冲区的值,再用ge...
  • GIS应用技巧之缓冲区分析

    千次阅读 2022-03-22 20:39:13
    一、缓冲区介绍 缓冲区是地理空间目标的一种影响范围或服务范围,是对选中的一组或一类地图要素(点、线或面)按设定的距离条件,围绕其要素而形成一定缓冲区多边形实体,从而实现数据在二维空间得以扩展的信息分析...
  • 在C和C ++中创建循环缓冲

    千次阅读 2021-03-12 10:02:52
    由于嵌入式系统的资源限制,在大多数项目中都可以找到循环缓冲区数据结构。 循环缓冲区(也称为环形缓冲区)是固定大小的缓冲区,其工作方式就像内存是连续的且本质上是循环的。随着内存的生成和消耗,不需要重新...
  • ringbuff | 通用FIFO环形缓冲区实现库

    千次阅读 2020-06-06 10:58:39
    丢包测试 经过3.2节的测试,不丢包的最小缓冲区大小是140个字节,接下里我们将缓冲区大小修改为100个字节,测试一下是否产生丢包: //开辟一块内存用于缓冲区 #define USART1_BUFFDATA_SIZE 100 //会发生丢包 //#...
  • 环形缓冲区(ringbuffer)

    千次阅读 2021-03-29 20:55:38
    环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在串口处理中,串口中断接收数据直接往环形缓冲区丢数据,而应用可以从环形缓冲区取数据进行处理,这样数据在读取和写入的时候都可以在这个缓冲区里循环进行,...
  • JavaScript WebGL 帧缓冲区对象

    千次阅读 2022-02-01 18:44:38
    引子 在看 How I built a wind...在默认情况下,WebGL 最终绘图结果存储在颜色缓冲区,帧缓冲区对象可以用来代替颜色缓冲区,如下图所示,绘制在帧缓冲区中的对象并不会直接显示在 Canvas 上,因此这种技术也被称为离屏
  • 缓冲、运放、跟随电路详解及区分

    千次阅读 2021-05-03 16:57:45
    文章目录前言运算放大器电路分析方法运算放大器工作原理经典电路图一——反向放大器经典...缓冲电路的概念缓冲电路的基本结构缓冲电路的工作原理电压跟随器缓冲隔离阻抗匹配、提高带载能力运算放大器和跟随器的区别分析...
  • OpenGL之帧缓冲

    千次阅读 2019-04-17 13:37:02
    本文主要包括帧缓冲的基本概念、函数使用,以及引出离屏渲染和后处理的介绍。 文章目录概要帧缓冲附件函数纹理附件渲染缓冲对象附件附件类型的选择后处理离屏渲染离屏渲染与双缓冲来源 概要 所谓帧缓冲是做什么用的...
  • } OK,这下应该明明白白了,用大白话总结如下(我们把BufferedInpuStream称之为缓冲流): 我们调用缓冲流来读取数据,系统会先看一下缓冲区中有没有可用数据,有的话直接从缓冲区中复制数据给用户 如果缓冲区中...
  • 缓冲和双缓冲的时间计算

    千次阅读 多人点赞 2019-05-14 20:01:56
    在块设备输入数据的过程中,首先把磁盘数据送到缓冲区,花费的时间为TsT_sTs​,(简记为TsT_sTs​过程) 然后把操作系统缓冲区的数据送到用户区,花费的时间为TmT_mTm​,(简记为TmT_mTm​过程) 最后用户进程对这批...
  • loading网页正在缓冲图标gif集锦

    热门讨论 2012-11-20 10:35:47
    loading网页正在缓冲图标gif集锦,里面有十几个漂亮的网页缓冲图标
  • 内存缓冲

    千次阅读 2022-04-20 08:48:27
    缓冲区 之所以要存在缓冲区,主要有两个原因: 1. **CPU**的读写速度与**硬盘**不匹配:由于CPU的读写速度远高于硬盘,所以当程序进行读写操作时是需要等待的,而当一个计算机的所有程序都需要等待硬盘时,大家的...
  • 三态缓冲器可以处于以下三种状态之一:逻辑0,逻辑1和Z(高阻抗)。它们的使用允许多个驱动程序共享一条公共线路。这使得它们在半双工通信中特别有用。让我们首先讨论半双工和全双工通信之间的区别。
  • AAudio 音频流内部缓冲区 与 音频数据读写缓冲区 概念 II . AAudio 音频流内部缓冲缓冲区帧容量 BufferCapacityInFrames 与 缓冲区帧大小 BufferSizeInFrames 区分 III . AAudio 音频流内部缓冲缓冲区帧容量 ...
  • STM32进阶之串口环形缓冲区实现

    万次阅读 多人点赞 2018-06-04 10:03:13
    如需转载请说明出处:STM32进阶之串口环形缓冲区实现 队列的概念 在此之前,我们来回顾一下队列的基本概念: 队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),...
  • Java随笔记录 - 内核缓冲区与进程缓冲区 Review 在上一篇博客Java随笔记 - 内核态和用户态,提到了一个常见的误区,放在这里进行一下个人角度的详解。简单来说,我们所理解的read & write系统调用并不会直接对...
  • 缓冲区溢出

    千次阅读 2018-08-10 17:30:15
    缓冲区溢出 相关安全文章 概览 描述 风险影响 例子 相关攻击 相关漏洞 相关控制 缓冲区溢出 今天又有点时间,花了大概4个小时不到翻译了这个大概3500字的文章,感觉自己阅读速度真的有待提高。虽说边翻译...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,228,502
精华内容 491,400
关键字:

缓冲

友情链接: BCGControlBarPro.rar