精华内容
下载资源
问答
  • 常用的缓冲技术是解决
    2018-04-02 20:14:57

    计算机为什么需要缓冲机制?如何实现缓冲呢?

    我们知道cpu的主频可以达到2.6GHz,这还是在单核的情况下,如果多核再加上超线程技术,cpu的计算主频可以更高。而普通的硬盘只有几十MHz,我们自己用的u盘如果3MHz就感觉可以了。可以看到cpu计算速度是硬盘等存储设备拷贝数据的几十甚至上百倍,如果cpu等待从硬盘的来的数据(硬盘存储数据而数据运算必须拷贝到内存)进行运算,不仅仅会极大的浪费资源还会影响我们操作员的体验。缓冲机制就是为了解决这一问题,所有的速度不匹配的系统都会用到缓冲机制。

    缓冲机制就是开辟一块存储区域(缓冲区),根据中断机制来解决速度不匹配问题。当输入和输出公用一片内存时,在前面的内存管理我们知道,必须要为内存设定临界量,使得一块区域内只有一个进程在使用这个资源。在数据拷贝时,输入进程将数据拷贝到缓冲区,而输出进程负责将缓冲区中的数据拷贝走。数据输入进程拷贝完数据后,发出一个信号让数据输出进程从缓冲区取走数据。取完数据后,发出一个信号让数据输入进程开始向缓存区放入数据。这样就能有效避免了数据的冲突,但是这样还是不能有效解决cpu利用率问题,毕竟拷贝数据很慢,cpu不可能一直等待数据拷完。于是系统采用中断机制来进行数据拷贝。

    cpu将放权让IO通道子系统进行全权控制数据的考入和考出,只是在数据开始输入和数据结束传递,cpu才参与其中。这样就大大解放了cpu,从而可以使得cpu完成更高效的工作。开始向缓冲区写入数据时,cpu发出指令让IO通道进行数据读取,然后cpu完成其他工作。待到缓冲区完成数据接收时,IO通道利用中断机制向cpu发出中断请求,cpu暂停其他进程,转而响应中断,将缓冲区数据取出。

    为了进一步解决速率不匹配的问题,可以创建一个缓冲池。缓冲池可以是一个链表,当输入数据后,数据不急着从缓冲区输出,而是将输入数据放入到链表结构中。当有进程发出指令从缓冲池读取数据时,缓冲数据从链表的头部取出相关数据结构,发送给指定的线程。

    双缓冲可以实现双向通信,同时还能实现串行到并行的转换。


    更多相关内容
  • java的双缓冲技术

    千次阅读 2016-03-19 23:54:17
    Java的强大特性让其在游戏编程和多媒体动画处理方面也毫不逊色。在Java游戏编程和动画编程中最常见的就是对于屏幕闪烁的处理。本文从J2SE的一个再现了屏幕闪烁的Java ...双缓冲是计算机动画处理中的传统技术,在

    Java的强大特性让其在游戏编程和多媒体动画处理方面也毫不逊色。在Java游戏编程和动画编程中最常见的就是对于屏幕闪烁的处理。本文从J2SE的一个再现了屏幕闪烁的Java Appilication简单动画实例展开,对屏幕闪烁的原因进行了分析,找出了闪烁成因的关键:update(Graphics g)函数对于前端屏幕的清屏。由此引出消除闪烁的方法——双缓冲。双缓冲是计算机动画处理中的传统技术,在用其他语言编程时也可以实现。本文从实例出发,着重介绍了用双缓冲消除闪烁的原理以及双缓冲在Java中的两种常用实现方法(即在update(Graphics g)中实现和在paint(Graphics g)中实现),以期读者能对双缓冲在Java编程中的应用能有个较全面的认识。

    一、问题的引入

    在编写Java多媒体动画程序或用Java编写游戏程序的时候,我们得到的动画往往存在严重的闪烁(或图片断裂)。这种闪烁虽然不会给程序的效果造成太大的影响,但着实有违我们的设计初衷,也给程序的使用者造成了些许不便。闪烁到底是什么样的呢?下面的JavaApplication再现了这种屏幕闪烁的情况:

    代码段一,闪烁再现

    [java]  view plain  copy
    1. import java.awt.*;  
    2. import java.awt.event.*;  
    3. public class DoubleBuffer extends Frame//主类继承Frame类  
    4. {  
    5.     public paintThread pT;//绘图线程  
    6.     public int ypos=-80//小圆左上角的纵坐标  
    7.     public DoubleBuffer()//构造函数  
    8.     {  
    9.        pT=new paintThread(this);  
    10.        this.setResizable(false);  
    11.        this.setSize(300,300); //设置窗口的首选大小  
    12.        this.setVisible(true); //显示窗口  
    13.        pT.start();//绘图线程启动  
    14.     }  
    15.    public void paint(Graphics scr) //重载绘图函数  
    16.    {  
    17.        scr.setColor(Color.RED);//设置小圆颜色  
    18.        scr.fillOval(90,ypos,80,80); //绘制小圆  
    19.     }  
    20.     public static void main(String[] args)  
    21.     {  
    22.        DoubleBuffer DB=new DoubleBuffer();//创建主类的对象  
    23.        DB.addWindowListener(new WindowAdapter()//添加窗口关闭处理函数  
    24.        {  
    25.            public void windowClosing(WindowEvent e)  
    26.            {  
    27.               System.exit(0);  
    28.            }});  
    29.     }  
    30. }  
    31. class paintThread extends Thread//绘图线程类  
    32. {  
    33.     DoubleBuffer DB;  
    34.        public paintThread(DoubleBuffer DB) //构造函数  
    35.        {  
    36.            this.DB=DB;  
    37.        }  
    38.        public void run()//重载run()函数  
    39.        {  
    40.            while(true)//线程中的无限循环  
    41.            {  
    42.               try{  
    43.                   sleep(30); //线程休眠30ms  
    44.                   }catch(InterruptedException e){}  
    45.               DB.ypos+=5//修改小圆左上角的纵坐标  
    46.               if(DB.ypos>300//小圆离开窗口后重设左上角的纵坐标  
    47.                   DB.ypos=-80;  
    48.               DB.repaint();//窗口重绘  
    49.            }  
    50.        }  
    51. }  

    编译、运行上述例子程序后,我们会看到窗体中有一个从上至下匀速运动的小圆,但仔细观察,你会发现小圆会不时地被白色的不规则横纹隔开,即所谓的屏幕闪烁,这不是我们预期的结果。


    这种闪烁是如何出现的呢?

    首先我们分析一下这段代码。DoubleBuffer的对象建立后,显示窗口,程序首先自动调用重载后的paint(Graphics g)函数,在窗口上绘制了一个小圆,绘图线程启动后,该线程每隔30ms修改一下小圆的位置,然后调用repaint()函数。

    注意,这个repaint()函数并不是我们重载的,而是从Frame类继承而来的。它先调用update(Graphics g)函数,update(Graphics g)再调用paint(Graphics g)函数。问题就出在update(Graphics g)函数,我们来看看这个函数的源代码:

    [java]  view plain  copy
    1. public void update(Graphics g)  
    2. {  
    3. if (isShowing())  
    4. {  
    5.         if (! (peer instanceof LightweightPeer))  
    6. {  
    7.              g.clearRect(00, width, height);  
    8.          }  
    9.         paint(g);  
    10.     }  
    11. }  

    以上代码的意思是:(如果该组件是轻量组件的话)先用背景色覆盖整个组件,然后再调用paint(Graphics g)函数,重新绘制小圆。这样,我们每次看到的都是一个在新的位置绘制的小圆,前面的小圆都被背景色覆盖掉了。这就像一帧一帧的画面匀速地切换,以此来实现动画的效果。

        但是,正是这种先用背景色覆盖组件再重绘图像的方式导致了闪烁。在两次看到不同位置小圆的中间时刻,总是存在一个在短时间内被绘制出来的空白画面(颜色取背景色)。但即使时间很短,如果重绘的面积较大的话花去的时间也是比较可观的,这个时间甚至可以大到足以让闪烁严重到让人无法忍受的地步。

        另外,用paint(Graphics g)函数在屏幕上直接绘图的时候,由于执行的语句比较多,程序不断地改变窗体中正在被绘制的图象,会造成绘制的缓慢,这也从一定程度上加剧了闪烁。

        就像以前课堂上老师用的旧式的幻灯机,放完一张胶片,老师会将它拿下去,这个时候屏幕上一片空白,直到放上第二张,中间时间间隔较长。当然,这不是在放动画,但上述闪烁的产生原因和这很类似。

    二、问题的解决

    知道了闪烁产生的原因,我们就有了更具针对性的解决闪烁的方案。已经知道update(Graphics g)是造成闪烁的主要原因,那么就从这里入手。

    1尝试这样重载update(Graphics g)函数(基于代码段一修改):


    [java]  view plain  copy
    1. public void update(Graphics scr)  
    2. {  
    3.     paint(scr);  
    4. }  

    以上代码在重绘小圆之前没有用背景色重绘整个画面,而是直接调用paint(Graphics g)函数,这就从根本上避免了上述的那幅空白画面。

        看看运行结果,闪烁果然消除了!但是更大的问题出现了,不同时刻绘制的小圆重叠在一起形成了一条线!这样的结果我们更不能接受了。为什么会这样呢?仔细分析一下,重载后的update(Graphics g)函数中没有了任何清屏的操作,每次重绘都是在先前已经绘制好的图象的基础上,当然会出现重叠的现象了。

    2)使用双缓冲:

    这是本文讨论的重点。所谓双缓冲,就是在内存中开辟一片区域,作为后台图象,程序对它进行更新、修改,绘制完成后再显示到屏幕上。  

    1、重载paint(Graphics g)实现双缓冲:

        这种方法要求我们将双缓冲的处理放在paint(Graphics g)函数中,那么具体该怎么实现呢?先看下面的代码(基于代码段一修改):

    DoubleBuffer类中添加如下两个私有成员:

    [java]  view plain  copy
    1. private Image iBuffer;  
    2. private Graphics gBuffer;  
    3. //重载paint(Graphics scr)函数:  
    4. public void paint(Graphics scr)  
    5. {  
    6.     if(iBuffer==null)  
    7.     {  
    8.        iBuffer=createImage(this.getSize().width,this.getSize().height);  
    9.        gBuffer=iBuffer.getGraphics();  
    10.     }  
    11.     gBuffer.setColor(getBackground());  
    12.     gBuffer.fillRect(0,0,this.getSize().width,this.getSize().height);  
    13.    gBuffer.setColor(Color.RED);  
    14.    gBuffer.fillOval(90,ypos,80,80);  
    15.     scr.drawImage(iBuffer,0,0,this);  
    16. }  

    分析上述代码:我们首先添加了两个成员变量iBuffergBuffer作为缓冲(这就是所谓的双缓冲名字的来历)。在paint(Graphics scr)函数中,首先检测如果iBuffernull,则创建一个和屏幕上的绘图区域大小一样的缓冲图象,再取得iBufferGraphics类型的对象的引用,并将其赋值给gBuffer,然后对gBuffer这个内存中的后台图象先用fillRect(int,int,int,int)清屏,再进行绘制操作,完成后将iBuffer直接绘制到屏幕上。

        这段代码看似可以完美地完成双缓冲,但是,运行之后我们看到的还是严重的闪烁!为什么呢?回想上文所讨论的,问题还是出现在update(Graphics g)函数!这段修改后的程序中的update(Graphics g)函数还是我们从父类继承的。在update(Graphics g)中,clearRect(int,int,int,int)对前端屏幕进行了清屏操作,而在paint(Graphics g)中,对后台图象又进行了清屏操作。那么如果保留后台清屏,去掉多余的前台清屏应该就会消除闪烁。所以,我们只要按照(1)中的方法重载update(Graphics g)即可:


    [java]  view plain  copy
    1. public void update(Graphics scr)  
    2. {  
    3.     paint(scr);  
    4. }  

    这样就避开了对前端图象的清屏操作,避免了屏幕的闪烁。虽然和(1)中用一样的方法重载update(Graphics g),但(1)中没有了清屏操作,消除闪烁的同时严重破坏了动画效果,这里我们把清屏操作放在了后台图象上,消除了闪烁的同时也获得了预期的动画效果。

    2、重载update(Graphics g)实现双缓冲:

        这是比较传统的做法。也是实际开发中比较常用的做法。我们看看实现这种方法的代码(基于代码段一修改):

    DoubleBuffer 类中添加如下两个私有成员:

    [java]  view plain  copy
    1. private Image iBuffer;  
    2. private Graphics gBuffer;  
    3. //重载paint(Graphics scr)函数:  
    4. public void paint(Graphics scr)  
    5. {  
    6.     scr.setColor(Color.RED);  
    7.     scr.fillOval(90,ypos,80,80);  
    8. }  

    重载update(Graphics scr)函数:


    [java]  view plain  copy
    1. public void update(Graphics scr)  
    2. {  
    3.     if(iBuffer==null)  
    4.     {  
    5.        iBuffer=createImage(this.getSize().width,this.getSize().height);  
    6.        gBuffer=iBuffer.getGraphics();  
    7.     }  
    8.        gBuffer.setColor(getBackground());  
    9.        gBuffer.fillRect(0,0,this.getSize().width,this.getSize().height);  
    10.        paint(gBuffer);  
    11.        scr.drawImage(iBuffer,0,0,this);  
    12. }  

    分析上述代码:我们把对后台图象的创建、清屏以及重绘等一系列动作都放在了update(Graphicsscr)函数中,而paint(Graphics g)函数只是负责绘制什么样的图象,以及怎样绘图,函数的最后实现了后台图象向前台绘制的过程。

    运行上述修改后的程序,我们会看到完美的消除闪烁后的动画效果。就像在电影院看电影,每张胶片都是在后台准备好的,播放完一张胶片之后,下一张很快就被播放到前台,自然不会出现闪烁的情形。

        为了让读者能对双缓冲有个全面的认识现将上述双缓冲的实现概括如下:

    1)定义一个Graphics对象gBuffer和一个Image对象iBuffer。按屏幕大小建立一个缓冲对象给iBuffer。然后取得iBufferGraphics赋给gBuffer。此处可以把gBuffer理解为逻辑上的缓冲屏幕,而把iBuffer理解为缓冲屏幕上的图象。

    2gBuffer(逻辑上的屏幕)上用paint(Graphics g)函数绘制图象。

    3将后台图象iBuffer绘制到前台。

    以上就是一次双缓冲的过程。注意,将这个过程联系起来的是repaint()函数。paint(Graphics g)是一个系统调用语句,不能由程序员手工调用。只能通过repaint()函数调用。

    三、问题的扩展

    1、关于闪烁的补充:

    其实引起闪烁的不仅仅是上文提到的那样,多种物理因素也可以引起闪烁,无论是CRT显示器还是LCD显示器都存在闪烁的现象。本文只讨论软件编程引起的闪烁。但是即使双缓冲做得再好,有时也是会有闪烁,这就是硬件方面的原因了,我们只能修改程序中的相关参数来降低闪烁(比如让画面动得慢一点),而不是编程方法的问题。

    2、关于消除闪烁的方法的补充:

        上文提到的双缓冲的实现方法只是消除闪烁的方法中的一种。如果在swing中,组件本身就提供了双缓冲的功能,我们只需要进行简单的函数调用就可以实现组件的双缓冲,在awt中却没有提供此功能。另外,一些硬件设备也可以实现双缓冲,每次都是先把图象画在缓冲中,然后再绘制在屏幕上,而不是直接绘制在屏幕上,基本原理还是和文中的类似的。还有其他用软件实现消除闪烁的方法,但双缓冲是个简单的、值得推荐的方法。

    2、关于双缓冲的补充:

    双缓冲技术是编写J2ME游戏的关键技术之一。双缓冲付出的代价是较大的额外内存消耗。但现在节省内存已经不再是程序员们考虑的最首要的问题了,游戏的画面在游戏制作中是至关重要的,所以以额外的内存消耗换取程序质量的提高还是值得肯定的。

    3、双缓冲的改进:

    有时动画中相邻的两幅画面只是有很少部分的不同,这就没必要每次都对整个绘图区进行清屏。我们可以对文中的程序进行修改,使之每次只对部分屏幕清屏,这样既能节省内存,又能减少绘制图象的时间,使动画更加连贯!
    展开全文
  • 文章尝试用尝试用通俗的语言还原密码学应用逻辑与信息传输加密方法,梳理常用加密技术原理及学科学习中的关键要点。

    一直以来,有很多小伙伴在区块链学习亦或是网络安全学习的过程中,后台私聊我一些关于密码学的问题。在这篇文章里,我将尝试用通俗的语言,还原密码学应用逻辑与信息传输加密方法,帮助大家在密码学学习过程中更好的理解学科中的关键要点。希望无论是新手小白,还是在密码学学习道路上不断探索的“我们”,都能够通过这篇文章,收获一二。

    在这里插入图片描述


    密码,最初的目的是用于对信息加密,但随着密码学的运用,密码还被广泛用于身份认证、防止否认等问题上。针对信息传输过程中可能面对的“窃听”、“篡改”、“假冒”、“否认”等安全性隐患,计算机领域衍生出种类繁多的密码技术,针对不同的应用场景,可以总结出下表:

    威胁特征相应技术
    窃听破坏数据机密性对称加密、非对称加密、混合加密
    假冒第三方假冒一方发送信息消息认证码、数字签名、数字证书
    篡改破坏数据完整性单向散列、消息认证码、数字签名、数字证书
    否认事后否认自己行为数字签名、数字证书

    在这里插入图片描述


    一、加密——防止“窃听”

    密码最基本的功能是信息的加解密,这就涉及到了密码算法。每个密码算法都基于相应的数学理论。密码学发展至今,已经产生了大量优秀的密码算法,通常分为两类:对称密码算法(Sysmmetric Cryptography)和非对称密码算法(Public-Key Cryptography,Asymmetric Cryptography),这两者的区别是是否使用了相同的密钥。

    (1)对称密码算法(Sysmmetric Cryptography)

    所谓对称加密,是信息传递双方用同一个密钥来进行加密解密,实践中最为常用,效率高。

    在这里插入图片描述
    上图向大家展示了对称加密流程:

    1. 发送方A、接收方B共享密钥A;
    2. 发送方A通过密钥A对明文加密;
    3. 发送方A向接收方B发送密文;
    4. 接收方B通过密钥A解密密文,得到明文。

    对称密码算法以A、B间密钥A的共享(传递)不被第三方C获取为前提,无法解决密钥分配过程中的安全性问题(即密钥A存在被第三方C获取的可能)。

    (2)非对称密码算法(Public-Key Cryptography,Asymmetric Cryptography)

    非对称加密可以用于解决密钥配送问题。

    相对于对称密码加解密采用相同的密码,非对称密码加解密采用的是不同的密钥,公钥和私钥成对,公钥加密信息,相应的私钥解密。公钥是公开的,私钥归信息的接受者所有。由于非对称密码算法可以把加密密钥公开,因此也叫做公开密钥密码算法,简称公钥密码算法,或公钥算法。公钥算法非常优雅地解决了密钥既要保密又要公开的矛盾。
    在这里插入图片描述

    上图向大家展示了非对称加密流程:

    1. 接收方B生成公私钥对,私钥由接收方B自己保管;
    2. 接收方B将公钥发送给信息发送方A;
    3. 发送方A通过公钥对明文加密,得到密文;
    4. 发送方A向接收方B发送密文;
    5. 接收方B通过私钥解密密文,得到明文。

    非对称加密算法似乎能够解决密钥分配问题,但是依然存在缺陷,其中最明显的缺陷是:加密解密效率慢!

    RSA算法的速度是DES的1000分之一,并且密钥越长,速度会急剧变慢。基于此,在工业场景下,往往选择的是通过非对称加密配送密钥,对称加密加密明文的混合加密方式加密报文。

    (3)混合加密

    混合加密就是对称加密与非对称加密的结合。用对称密码来加密明文(速度),用非对称密码来加密对称密码中所使用的密钥(安全)。对称加密算法加密解密速度快,强度高,但存在密钥分配问题;非对称加密不存在密钥分配问题,但算法效率低。基于上述特征,可以选择通过非对称加密配送对称密钥,再采用对称密钥用来加密的方式,实现网络的密钥配送与通信加密,以取长补短,优化加密方法。
    在这里插入图片描述
    上图向大家展示了混合加密流程:

    1. 接收方B生成公私钥对(公钥A、私钥A),私钥(私钥A)由接收方B自己保管;
    2. 接收方B将公钥(公钥A)发送给信息发送方A;
    3. 发送方A通过公钥对对称密钥(对称密钥S)加密,得到密钥密文;
    4. 发送方A向接收方B发送密钥密文;
    5. 接收方B通过私钥(私钥A)解密密文,得到对称密钥(对称密钥S);
    6. 此时,发送方A、接收方B成功共享密钥S,此后信息就可以用对称加密方法传递;
      • 发送方A通过密钥S对明文加密;
      • 发送方A向接收方B发送密文;
      • 接收方B通过密钥S解密密文,得到明文。

    混合加密利用对称加密算法解决了信息加解密效率缺陷,利用非对称加密算法配送对称密钥解决了密钥分配问题,但即便如此,仍然存在一定的安全隐患,如:无法避免中间人攻击、信息篡改

    例如:中间人C可以在劫持B发出的公钥A后,发送一个伪造的公钥B给A,A通过公钥B加密后的密文,可以被劫持者C通过私钥B解密,之后所有的密文都对劫持者透明了;C还能用之前劫持到的公钥A 假冒 A,通过加密信息甚至篡改信息,发送给B,此时AB均无从发觉公钥已被中间人C 伪造,信息已被窃听,甚至篡改


    二、单向散列、消息认证码——防止“假冒”与“篡改”

    加密算法为我们解决了数据的窃听威胁,但无法有效应对数据的篡改与假冒,这就有必要了解消息认证码等相关技术。

    (1)单向散列技术(One-Way Hash Function)

    在了解消息认证码前,我们需要先了解单向散列技术(即哈希技术)。所谓单向散列技术又称密码检验(Cryptographic Checksum)、指纹 (Fingerprint)、消息摘要 (Message Digest),是为了保证信息的完整性(Integrity),防止信息被篡改的一项技术。单向散列算法,又称单向Hash函数、杂凑函数,就是把任意长的输入消息串变化成固定长的输出串且由输出串难以得到输入串的一种函数。其具有如下特点:

    1. 任意的消息大小。哈希函数对任何大小的消息x都适用。
    2. 固定的输出长度。无论信息长度,计算出的长度永远不变。
    3. 计算快速,即函数的计算相对简单
    4. 具有单向性(one-way),不可由散列值推出原信息。即教材所谓的抗第一原像性【给定一个输出z,找到满足h(x)=z的输入x是不可能的】
    5. 信息不同,散列值不同,具有抗碰撞性(Collision Resistance)。
      (1)强抗碰撞性:要找到散列值相同的两条不同的消息是非常困难的(知道散列值找两条消息)。即找到满足h(x1)=h(x2)的一对x1≠x2在计算上是不可行的;
      (2)弱抗碰撞性:要找到和该消息具有相同散列值的另一个消息是非常困难的(知道该消息和其散列值找另一条消息)。即抗第二原像性【给定x1和h(x1),找到满足h(x1)=h(x2)的x2在计算上是不可能的】。

    常见的单向散列函数(Hash函数)有消息摘要算法MD(Message Digest)、安全散列算法SHA(Secure Hash Algorithm),其中采用 Keccak 算法的SHA-3采用海绵结构。就安全性而言,MD4、MD5为产生128比特散列值,均已不安全,存在可碰撞。SHA-1产生160比特散列值,也是不推荐使用的,存在可碰撞。RIPEMD-128、RIPEMD-160也不推荐使用。推荐使用SHA-2、SHA-3算法(目前对它的支持还不是很广泛)。

    • 哈希函数是没有密钥的。哈希函数两个最主要的应用就是数字签名和消息验证码(比如 HMAC)
    • 哈希函数的三个安全性要求为单向性、抗第二原像性和抗冲突性。
    • 为了抵抗冲突攻击,哈希函数的输出长度至少为160 位;对长期安全性而言,最好使用256位或更多的哈希函数。
    • MD5 的使用非常广泛,但却是不安全的。人们发现了 SHA-1中存在的严重安全漏洞,这样的哈希函数应该被逐步淘汰。SHA-2算法看上去是安全的。
    • 正在进行的 SHA-3竞争将在几年后产生新的标准化的哈希函数。

    结合密码学的加解密技术和单向散列技术,有了用于防止篡改的消息认证码技术,防止伪装的数字签名技术以及认证证书,这一部分请看下文。

    (2)消息认证码(Message Authentication Code)

    消息认证码算法(MAC)的作用在于验证传输数据的完整性,这就要求其应当是一串需要与共享密钥相关而且足够有区分度的串。因此,可以通过多种方式获得 MAC 值,如单向散列(Hash函数)、分组密码截取最后一组作为 MAC 值、流密码、非对称加密等。

    以单向散列技术(Hash函数)为例:

    在这里插入图片描述
    首先,消息认证码采用的是对称加密,因此A需要准备一个用于加密密文的密钥(密钥W)和一个用来生成消息验证码的密钥(密钥X),将其通过安全的方法(如非对称加密算法)发送给接收方B。

    在这里插入图片描述

    上图向大家展示了密钥建立共享后信息的传输流程:

    1. 发送方 A 与接收方 B 共享密钥(加密密文的密钥W和生成消息验证码的密钥X);
    2. 发送方A通过密钥W对明文加密,得到密文;
    3. 发送方 A 通过密钥X计算 MAC 值,hash加密后生成消息验证码(MAC-A);
    4. 发送方A向接收方B发送密文和消息验证码(MAC-A);
    5. 接收方 B 对密文通过密钥X计算 MAC 值,再hash加密一次获得消息验证码(MAC-B)。
    6. 接收方 B 比较 MAC-A 与 MAC-B,若一致则说明信息未被篡改,可以通过密钥W对密文解密读取信息。
    7. 若MAC-A 与 MAC-B不一致,则说明密文被篡改,或者消息验证码被篡改,或两者皆被篡改。

    需要注意的是,消息认证码仍然存在问题:

    1. 密钥配送的问题,因为 MAC 需要发送者与接收者使用相同的密钥;
    2. 暴力破解;
    3. 无法防止事后否认、也无法对第三方证明。因为密钥是共享的,接收者可以伪造对发送者不利的信息。
    4. 重放攻击,窃取某一次通信中的正确的 MAC,然后攻击者重复多次发送相同的信息。由于信息与 MAC 可以匹配,在不知道密钥的情况下,攻击者就可以完成攻击。

    以下方法可以避免重放攻击:

    • 序号,约定信息中带上递增序号,MAC 值为加上序号的 MAC
    • 时间戳,约定信息中带上时间戳。使用时间戳避免重放攻击要保证发送者和接受者的时钟必须一致,但考虑到网络延迟,必须在时间的判断上留下缓冲,因此仍有可以进行重放攻击的时间。
    • 随机数 nonce,每次传递前,先发送随机数 nonce,通信时再将随机数包含在消息中并计算MAC值,基于此将无法进行重放。

    三、数字签名、数字证书——防止“事后否认”,兼顾“假冒”与“篡改”

    (1)数字签名

    消息验证码之所以无法解决事后否认的问题,是因为其采用的是对称加密算法(因为密钥是共享的,接收者存在伪造发送者信息之可能,因此发送者可以事后否认信息的真实性。),采用非对称加密的消息认证码的技术,就是数字签名。

    在非对称加密中,私钥用来解密,公钥用来加密。
    在数字签名技术中,私钥用来加密,公钥用来解密。

    在这里插入图片描述
    上图向大家展示了数字签名及验证流程:

    1. 签名方 A 生成非对称公私钥对 公钥A、私钥A;
    2. A 向消息接收方 B 发送公钥A;
    3. A 使用私钥A对消息加密(一般是对消息的散列值进行加密,防止因数据量过大或非对称加密效率低下特征导致签名时间过长等问题),生成数字签名;
    4. A 将消息与数字签名发往 B;
    5. 接收方B通过公钥解密数字签名;
    6. 验证签名,即比对Hash值,如相等,大概率(存在Hash碰撞)表明数据未被篡改。

    用更为归纳性的语言表述,即先计算消息的散列值,再对散列值进行私钥加密,得到的即为签名,签名人将消息和签名发送,接收方用公钥对签名进行解密并做验证(只要接收方B能够用A的公钥解密数字签名,就代表该签名是A签署的,因为公钥A只能解锁由A保管的私钥A签写的签名)。

    当然数字签名的方式也有两种:

    • 明文签名:不对消息进行加密,只对消息进行签名,主要用于发布消息。
    • 密文签名:对消息进行加密和签名。

    数字签名不仅能够防止A的事后否认,同样能够防止数据的假冒与篡改。但需要注意的是,数字签名仍然存在问题:

    数字签名由于采用了非对称加密算法,尽管能够防止否认,但发送方却不能知道所收到的公钥是否是接收方私钥所对应的公钥,因此无法防止中间人攻击

    伪造公钥是中间人攻击中重要的一环。之所以能够伪造,是因为消息发送方无法确认公钥的身份问题。如下图所示,如果公钥A被中间人C所劫持,替换成自己的公钥C,接受者B采用了攻击者C的公钥,此后接收了攻击者私钥签名的信息,公私钥完全匹配,AB均无从发觉公钥已被中间人C 伪造、篡改。
    在这里插入图片描述

    (2)数字证书

    对数字签名所发布的公钥进行权威的认证,便是证书。公钥证书记录个人信息及个人公钥,并由认证机构施加数字签名。数字证书能够有效避免中间人攻击的问题。

    为实现不同成员在不见面的情况下进行安全通信,公钥基础设施(PKI)当前采用的模型是基于可信的第三方机构,也就是证书颁发机构(CA)签发的证书。PKI是为了能够有效的运用公钥而制定的一系列规范和规格的总称(并非某单一规范)。其组成包括用户、认证机构(CA)、证书仓库。其中证书颁发机构(CA)是指我们都信任的证书颁发机构,它会在确认申请用户的身份之后签发证书。同时CA会在线提供其所签发证书的最新吊销信息(CRL,证书作废清单),用户要定期查询认证机构最新的CRL,确认证书是否失效。

    在这里插入图片描述
    上图向大家展示了数字证书的生成及验证流程:

    1. A 生成公私钥对;
    2. A 向认证中心CA注册自己的公钥A(该步骤只在第一次注册时存在);
    3. 认证中心CA用自己的私钥(私钥CA)对A的公钥(公钥A)施加数字签名并生成数字证书;
    4. B得到带有认证机构CA的数字签名的A的公钥(证书);
    5. B使用认证机构CA的公开公钥(公钥CA)验证数字签名,确认公钥A的合法性。

    对于认证机构的公钥,一般由其它的认证机构施加数字签名,从而对认证机构的公钥进行验证,即生成一张认证机构的公钥证书,这样的关系可以迭代好几层,以杜绝中间人假冒认证机构CA。最高一层的认证机构被称为根CA,RCA会对自己的公钥进行数字签名,即自签名,也会在RCA间互相签名。

    这与https加密传输原理是一致的。以CSDN站点为例,我们先从CSDN的证书中拿到CSDN的公钥,对我们本地生成的对称加密密钥进行加密,此后发送给CSDN,CSDN用其私钥解密获得我们的对称加密密钥,此时本地计算机与CSDN就拥有相同的对称密钥,此后用对称加密和消息认证码、数字签名来防止信息传输中的窃听、假冒、篡改以及事后否认。
    在这里插入图片描述


    密码学是一门非常有趣的学科,它与计算机科学、数学以及电子工程都存在交叉。随着密码学日新月异地取得发展,人们现在已经很难跟上它的发展步伐。密码学领域的理论基础在过去的30余年里已经得到加强和巩固:现在,人们对安全的定义和证明结构安全的方法有了更深入的认识。同时,我们也见证了应用密码学的快速发展:旧算法不断地被破解和抛弃,同时,新算法和协议也在不断涌现。

    之所以写这篇文章,是为了能够以最通俗易懂的方式帮助大家快速了解现代加密方案的工作原理。但由于抛开了微积分等必要的数学概念以及个人知识理解的局限性,所以,难免在相关问题的表述上略显稚嫩,供期望更深入理解现代密码学的读者以参考借鉴,也欢迎大家的批评交流。

    本文简单梳理了实际应用中使用的部分加密算法,其实还有加密协议、运作模式、安全服务和密钥建立技术等内容未展开论述,我们之后有机会再谈。


    如果您有任何疑问或者好的建议,期待你的留言与评论!

    展开全文
  • 【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

    待续…

    展开全文
  • Java双缓冲技术详解

    千次阅读 2016-07-08 16:06:59
    Java双缓冲技术 返回 存档 删除 添加到收藏夹 分享 显示选项 Pocket 我的列表添加到收藏夹存档列表 文本视频图像标签 › Careers at PocketBecome a SponsorMore
  • Java限流及常用解决方案总结

    千次阅读 2020-09-20 11:43:42
    说到限流,想必大家都不陌生,一个很简单的例子就是,在12306上面买票的时候,遇到某时刻开始抢票的时候,经常页面会弹出一个类似请稍后重试的提示,从后端的技术层面来看,大概有2层解释,第一是服务器担心扛不住...
  • Java双缓冲技术

    万次阅读 多人点赞 2012-10-28 10:38:52
    Java的强大特性让其在游戏编程和多媒体动画处理方面也毫不逊色。在Java游戏编程和动画编程中最常见的就是对于屏幕闪烁的处理。本文从J2SE的一个再现了屏幕闪烁的Java ...双缓冲是计算机动画处理中的传统技术,在
  • EGE中的双缓冲
  • 什么是缓冲区溢出?有说明危害?

    千次阅读 多人点赞 2019-04-19 20:40:04
     缓存溢出(或译为缓冲溢出)为黑客最为常用的攻击手段之-一,蠕虫病毒对操作系统高危漏洞的溢出高速与大规模传播均是利用此技术。  缓存溢出攻击从理论上来讲可以用于攻击任何有缺陷不完美的程序,包括对杀毒软件...
  • IO缓冲(buffer)和高速缓存(cache)

    千次阅读 2021-07-26 19:10:51
    在计算机存储体系中,缓存(cache)的使用非常的广泛,结合程序的局部性原理,为了提高寻址的效率,在CPU寻址的体系中采用了缓存技术,简单来说就是将数据存储起来以备后续使用。 如高速缓存(cache)产生的原理类似,...
  • 引入缓冲技术的原因: 1、 为了进一步缓和CPU和I/O设备之间速度不匹配的矛盾。 2、 提高CPU与I/O设备之间的并行性。 3、 为了减少中断次数和CPU的中断处理时间。如果没有缓冲,慢速I/O设备每传一个字节就要产生一...
  • 这是作者网络安全自学教程...这篇文章将详细总结恶意代码检测技术,包括恶意代码检测的对象和策略、特征值检测技术、校验和检测技术、启发式扫描技术、虚拟机检测技术和主动防御技术。基础性文章,希望对您有所帮助~
  • 【Java核心技术卷】I/O详析

    千次阅读 多人点赞 2019-09-09 08:34:27
    文章目录概述Java io基本概念关于流流的分类Java io框架一、以字节为单位的输出流的框架图(1)框架图图示(2)OutputStream详解...缓冲流(含字节输出流的内容)(5)引申:数据流(含字节输出流的内容)三、以字...
  • 缓冲技术 在互联网服务中,大部分的用户交互,都是需要立刻返回结果的,所以对于延迟有一定的要求。而类似网络游戏之类服务,延迟更是要求缩短到几十毫秒以内。所以为了降低延迟,缓冲是互联网服务中最常见的技术...
  • C++ 3D 绘图技术调研常用库介绍

    千次阅读 2021-12-15 11:36:02
    C++ 3D 绘图技术调研常用库介绍
  • 工业级高精度电磁流量计解决方案

    万次阅读 2021-10-02 13:51:16
    本文提供流量计技术概述,重点讨论液体流量测量中精度最高之一的电磁流量计。
  • 这篇文章将带着大家来学习《Windows黑客编程技术详解》,其作者是甘迪文老师,推荐大家购买来学习。作者将采用实际编程和图文结合的方式进行分享,并且会进一步补充相关知识点。第六篇文章主要介绍木马病毒提权技术...
  • 容灾备份的关键技术

    千次阅读 2020-07-06 16:38:07
    在建立容灾备份系统时会涉及到多种技术,如:SAN或NAS技术、远程镜像技术、基于IP的SAN的互连技术、快照技术等。这里重点介绍远程镜像、快照和互连技术。 1. 远程镜像技术 远程镜像技术是在主数据中心和备援中心...
  • 1 产品技术方案1.1 技术方案概述 1.1.1 系统功能架构   在银行电商平台中,包括各总分支行的管理人员、各家合作商户、会员、物流公司、支付平台等主要角色,通过电商平台进行信息流、资金流的交互,并借助物流...
  • mysql的缓冲池buffer pool

    万次阅读 2020-08-12 18:18:05
    在计算机中,缓存技术随处可见,其主要目的是提高访问速度。由于底层磁盘的存取速度比较慢,尝尝会使用内存作为缓存,这是我们程序员最常遇到的方式,比如java中的各种buffer,都是为了加快读取或者写入速度。另外...
  • 从这篇文章开始,作者将带着大家来学习《Windows黑客编程技术详解》,其作者是甘迪文老师,推荐大家购买来学习。作者将采用实际编程和图文结合的方式进行分享,并且会进一步补充知识点,希望对您有所帮助。第二篇...
  • 计算机操作系统简介 目标 了解操作系统的发展历史 知道 Linux 内核及发行版的区别 知道 Linux 的应用领域 任何计算机都必须在加载相应的操作系统之后,才能构成一个可以运转的、完整...操作系统是软件技术的核心...
  • 操作系统名词解释 常用操作系统概念定义

    千次阅读 多人点赞 2020-11-07 16:39:02
    是一个资源管理器,有效组织管理计算机硬件和软件资源,解决资源访问冲突。 分时系统 在一台主机上连接了多个配有显示器和键盘的终端并由此组成的系统,该系统允许多个用户同时通过自己的终端,以交互方式使用计算机...
  • 计算机操作系统复习题(六)

    千次阅读 2021-01-16 23:19:48
    ( √ )2、常用缓冲技术解决慢速设备与快速CPU处理之间协调工作。 ( √ )3、DMA方式可以完全脱离CPU直接与内存进行交换数据。 ( × )4、文件系统的主要功能是“按名存取”,所以从磁盘读取数据的工作是由...
  • OpenGL双缓冲

    千次阅读 2017-08-07 09:58:37
    1.双缓冲技术解决的问题 在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单...
  • 向下则以系统缓冲区的存储器接口作为实现基础。接口关系如下: 数据存储层所涉及的主要数据结构为逻辑数据记录、逻辑块、逻辑存取路径。 存取层的任务主要包括: 提供一次一个元组的查找、插入、删除、修改的等...
  • Flink-CEP之带版本的共享缓冲

    千次阅读 2017-03-05 23:30:39
    带版本的共享缓冲区当股票模式以一个事件流作为输入时,状态转换将会作用于事件流从而引起事件的状态变化。结合窗口对参与匹配的事件的限制以及模式中结合事件上下文(状态)的过滤条件,同一事件流随着时间的流动...
  • Buffer(缓冲/字节容器)详解

    千次阅读 2021-09-06 13:56:58
    本文来说下Buffer(缓冲/字节容器)详解 文章目录概述 概述 正如我们先前所指出的,网络数据的基本单位永远是 byte(字节)。Java NIO 提供 ByteBuffer 作为字节的容器,但这个类是过于复杂,有点难以使用。 Netty ...
  • (7)对总线仲裁问题的解决是以优先级(优先权)的概念为基础。   8 电平转换电路   (1)数字集成电路可以分为两大类:双极型集成电路(TTL)、金属氧化物半导体(MOS)。   (2)CMOS电路由于其静态...
  • 环形缓冲区的实现原理(ring buffer)

    千次阅读 2017-11-14 13:21:43
    消息队列锁调用太频繁的问题算是解决了,另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的服务器长期稳定运行。也许我们可以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,157
精华内容 33,262
热门标签
关键字:

常用的缓冲技术是解决