`
Donald_Draper
  • 浏览: 953108 次
社区版块
存档分类
最新评论

SocketChannelImpl 解析三(接收数据)

    博客分类:
  • NIO
nio 
阅读更多
SocketChannelImpl 解析一(通道连接,发送数据):http://donald-draper.iteye.com/blog/2372364
SocketChannelImpl 解析二(发送数据后续):http://donald-draper.iteye.com/blog/2372548
引言:
前一篇文章我们看了一下SocketChannelImpl发送多个字节序列的过程,先来回顾一下:
SocketChannelImpl写ByteBuffer数组方法,首先同步写锁,确保通道,输出流打开,连接建立委托给IOUtil,将ByteBuffer数组写到输出流中,这一过程为获取存放i个字节缓冲区的IOVecWrapper,遍历ByteBuffer数组m,将字节缓冲区添加到iovecwrapper的字节缓冲区数组中,如果ByteBuffer非Direct类型,委托Util从当前线程的缓冲区获取容量为j2临时DirectByteBuffer,并将ByteBuffer写到DirectByteBuffer,并将DirectByteBuffer添加到iovecwrapper的字节缓冲区(Shadow-Direct)数组中,将字节缓冲区的起始地址写到iovecwrapper,字节缓冲区的实际容量写到iovecwrapper;遍历iovecwrapper的字节缓冲区(Shadow-Direct)数组,将Shadow数组中的DirectByteBuffer通过Util添加到本地线程的缓存区中,并清除DirectByteBuffer在iovecwrapper的相应数组中的信息;最后通过
SocketDispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中。
今天我们来看一下接受数据
再来看SocketChannelImpl的读操作
 public int read(ByteBuffer bytebuffer)
        throws IOException
    {
        if(bytebuffer == null)
            throw new NullPointerException();
        Object obj = readLock;//获取读锁
        JVM INSTR monitorenter ;//进入同步,try
        if(!ensureReadOpen())//确保通道,输入流打开,通道连接建立
            return -1;
        int i = 0;
        begin();
        int k;
        Object obj3;
        Exception exception;
        synchronized(stateLock)
        {
            if(isOpen())
                break MISSING_BLOCK_LABEL_146;
            k = 0;
        }
	//清除读线程
        readerCleanup();
        end(i > 0 || i == -2);
        obj3 = stateLock;
        JVM INSTR monitorenter ;
        if(i > 0 || isInputOpen) goto _L2; else goto _L1
_L1:
        -1;
        obj;
        JVM INSTR monitorexit ;
        return;
_L2:
        obj3;
        JVM INSTR monitorexit ;
          goto _L3
        exception;
        obj3;
        JVM INSTR monitorexit ;
        throw exception;
_L3:
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        obj;
        JVM INSTR monitorexit ;
        return k;
	//初始化本地读线程
        readerThread = NativeThread.current();
        obj1;
        JVM INSTR monitorexit ;
        int j;
        do
	   //委托IOUtil从输入流读取字节序列,写到bytebuffer
            i = IOUtil.read(fd, bytebuffer, -1L, nd, readLock);
        while(i == -3 && isOpen());
        ...
 }

从输入流读取字节序列,写到buffer,有几点要关注
1.
 if(!ensureReadOpen())//确保通道,输入流打开,通道连接建立
            return -1;

2.
//清除读线程
readerCleanup();

3.
do
   //委托IOUtil从输入流读取字节序列,写到bytebuffer
    i = IOUtil.read(fd, bytebuffer, -1L, nd, readLock);
while(i == -3 && isOpen());

下面我们分别来看这几点:
1.
 if(!ensureReadOpen())//确保通道,输入流打开,通道连接建立
            return -1;

//确保通道,输入流打开,通道连接建立
 private boolean ensureReadOpen()
        throws ClosedChannelException
    {
        Object obj = stateLock;
        JVM INSTR monitorenter ;
        if(!isOpen())//通道打开
            throw new ClosedChannelException();
        if(!isConnected())//连接建立
            throw new NotYetConnectedException();
        if(!isInputOpen)//输入流打开
            return false;
        true;
        obj;
        JVM INSTR monitorexit ;
        return;
        Exception exception;
        exception;
        throw exception;
    }

2.
//清除读线程
readerCleanup();

private void readerCleanup()
      throws IOException
  {
      //同步通道状态锁,清除读线程,如果通道关闭则,执行清除工作
      synchronized(stateLock)
      {
          readerThread = 0L;
          if(state == 3)
              kill();//这个后面再讲
      }
  }

3.
do
   //委托IOUtil从输入流读取字节序列,写到bytebuffer
    i = IOUtil.read(fd, bytebuffer, -1L, nd, readLock);
while(i == -3 && isOpen());

这里循环的原因,线程读输入流,有可能因为某种原因被中断,中断位消除,继续读取输入流,写到buffer
//IOUtil
static int read(FileDescriptor filedescriptor, ByteBuffer bytebuffer, long l, NativeDispatcher nativedispatcher, Object obj)
       throws IOException
   {
       ByteBuffer bytebuffer1;
       //如果buffer为只读,则抛出IllegalArgumentException
       if(bytebuffer.isReadOnly())
           throw new IllegalArgumentException("Read-only buffer");
       //如果buffer为DirectBuffer,则委托给readIntoNativeBuffer
       if(bytebuffer instanceof DirectBuffer)
           return readIntoNativeBuffer(filedescriptor, bytebuffer, l, nativedispatcher, obj);
       //从当前线程缓存区获取临时的DirectByteBuffer
       bytebuffer1 = Util.getTemporaryDirectBuffer(bytebuffer.remaining());
       int j;
       //委托readIntoNativeBuffer方法,读取输入流数据,到临时DirectByteBuffer
       int i = readIntoNativeBuffer(filedescriptor, bytebuffer1, l, nativedispatcher, obj);
       //读写模式切换
       bytebuffer1.flip();
       if(i > 0)
           //如果有数据被读取,则放到byteBuffer中
           bytebuffer.put(bytebuffer1);
       j = i;//记录读取的字节数
       //添加临时DirectByteBuffer到当前线程的缓冲区,以便重用,
       //因为重新DirectByteBuffer为直接操作物理内存,频繁分配物理内存,将耗费过多的资源。
       Util.offerFirstTemporaryDirectBuffer(bytebuffer1);
       return j;
       Exception exception;
       exception;
       Util.offerFirstTemporaryDirectBuffer(bytebuffer1);
       throw exception;
   }

来看readIntoNativeBuffer方法
private static int readIntoNativeBuffer(FileDescriptor filedescriptor, ByteBuffer bytebuffer, long l, NativeDispatcher nativedispatcher, Object obj)
        throws IOException
    {
        int i = bytebuffer.position();
        int j = bytebuffer.limit();
	//如果断言开启,buffer的position大于limit,则抛出断言错误
        if(!$assertionsDisabled && i > j)
            throw new AssertionError();
	//获取需要读的字节数
        int k = i > j ? 0 : j - i;
        if(k == 0)
            return 0;
        int i1 = 0;
	//从输入流读取k个字节到buffer
        if(l != -1L)
            i1 = nativedispatcher.pread(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k, l, obj);
        else
            i1 = nativedispatcher.read(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k);
	//重新定位buffer的position
        if(i1 > 0)
            bytebuffer.position(i + i1);
        return i1;
    }

readIntoNativeBuffer方法中一点我们需要关注:
//从输入流读取k个字节到buffer
if(l != -1L)
    i1 = nativedispatcher.pread(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k, l, obj);
else
    i1 = nativedispatcher.read(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k);

//NativeDispatcher
 int pread(FileDescriptor filedescriptor, long l, int i, long l1, Object obj)
        throws IOException
    {
        throw new IOException("Operation Unsupported");
    }

从NativeDispatcher的pread方法可以看出,当前JDK版本,还不支持pread操作,我的JDK版本为1.7.0.17。
//SocketDispatcher
 int read(FileDescriptor filedescriptor, long l, int i)
        throws IOException
    {
        return read0(filedescriptor, l, i);
    }
 static native int read0(FileDescriptor filedescriptor, long l, int i)
        throws IOException;

至此读输入流到buffer,已经看完,首先同步读写,确保通道,输入流打开,通道连接建立,
清除原始读线程,获取新的本地读线程,委托IOUtil读输入流到buffer;IOUtil读输入流到buffer,首先确保buffer是可写的,否则抛出IllegalArgumentException,然后判断buffer是否为Direct类型,是则委托给readIntoNativeBuffer,否则通过Util从当前线程缓冲区获取一个临时的DirectByteBuffer,然后通过readIntoNativeBuffer读输入流数据到临时的DirectByteBuffer,这一个过程是通过SocketDispatcher的read方法实现,读写数据到DirectByteBuffer中后,将DirectByteBuffer中数据,写到原始buffer中,并将
DirectByteBuffer添加到添加临时DirectByteBuffer到当前线程的缓冲区,以便重用,
因为重新DirectByteBuffer为直接操作物理内存,频繁分配物理内存,将耗费过多的资源。
在来看从输入流读取数据,写到多个buffer:
public long read(ByteBuffer abytebuffer[], int i, int j)
        throws IOException
    {
        //校验参数
        if(i < 0 || j < 0 || i > abytebuffer.length - j)
            throw new IndexOutOfBoundsException();
        Object obj = readLock;//获取读锁
        JVM INSTR monitorenter ;//进入同步,try
        if(!ensureReadOpen())//确保通道打开,连接建立,输入流打开
            return -1L;
        long l = 0L;
        begin();//与end协同,记录中断器,处理读操作过程中的中断问题
        long l2;
        Object obj3;
        Exception exception;
        synchronized(stateLock)
        {
            if(isOpen())
                break MISSING_BLOCK_LABEL_177;
            l2 = 0L;
        }
	//清除原始读线程
        readerCleanup();
        end(l > 0L || l == -2L);
        obj3 = stateLock;
        JVM INSTR monitorenter ;
        if(l > 0L || isInputOpen) goto _L2; else goto _L1
_L1:
        -1L;
        obj;
        JVM INSTR monitorexit ;
        return;
_L2:
        obj3;
        JVM INSTR monitorexit ;
          goto _L3
        exception;
        obj3;
        JVM INSTR monitorexit ;
        throw exception;
_L3:
        if(!$assertionsDisabled && !IOStatus.check(l))
            throw new AssertionError();
        obj;
        JVM INSTR monitorexit ;
        return l2;
	//获取本地读线程
        readerThread = NativeThread.current();
        obj1;
        JVM INSTR monitorexit ;
        long l1;
        do
	    //委托给IOUtil,从输入流读取数据,写到多个buffer
            l = IOUtil.read(fd, abytebuffer, i, j, nd);
        while(l == -3L && isOpen());
        l1 = IOStatus.normalize(l);
}

从输入流读取数据,写到多个buffer,我们只需要关注下面这点
 do
    //委托给IOUtil,从输入流读取数据,写到多个buffer
    l = IOUtil.read(fd, abytebuffer, i, j, nd);
while(l == -3L && isOpen());

这里循环的原因,线程读输入流,有可能因为某种原因被中断,中断位消除,继续读取输入流,写到buffer;
//IOUtil
static long read(FileDescriptor filedescriptor, ByteBuffer abytebuffer[], int i, int j, NativeDispatcher nativedispatcher)
        throws IOException
    {
        IOVecWrapper iovecwrapper;
        boolean flag;
        int k;
	//获取存放i个byteBuffer的IOVecWrapper
        iovecwrapper = IOVecWrapper.get(j);
        flag = false;
        k = 0;
        long l1;
        int l = i + j;
        for(int i1 = i; i1 < l && k < IOV_MAX; i1++)
        {
            ByteBuffer bytebuffer = abytebuffer[i1];
            if(bytebuffer.isReadOnly())
                throw new IllegalArgumentException("Read-only buffer");
            int j1 = bytebuffer.position();
            int k1 = bytebuffer.limit();
            if(!$assertionsDisabled && j1 > k1)
                throw new AssertionError();
            int j2 = j1 > k1 ? 0 : k1 - j1;
            if(j2 <= 0)
                continue;
	   //将buffer添加到iovecwrapper的字节缓冲区数组中
            iovecwrapper.setBuffer(k, bytebuffer, j1, j2);
            if(!(bytebuffer instanceof DirectBuffer))
            {
	       //获取容量为j2临时DirectByteBuffer
                ByteBuffer bytebuffer2 = Util.getTemporaryDirectBuffer(j2);
		//添加DirectByteBuffer到iovecwrapper的shadow buffer数组
                iovecwrapper.setShadow(k, bytebuffer2);
                bytebuffer = bytebuffer2;
                j1 = bytebuffer2.position();
            }
	    //将字节缓冲区的起始地址写到iovecwrapper
            iovecwrapper.putBase(k, ((DirectBuffer)bytebuffer).address() + (long)j1);
	     //将字节缓冲区的实际容量写到iovecwrapper
            iovecwrapper.putLen(k, j2);
            k++;
        }

        if(k != 0)
            break MISSING_BLOCK_LABEL_263;
        l1 = 0L;
        if(!flag)
        {
            for(int i2 = 0; i2 < k; i2++)
            {   
	        //获取iovecwrapper索引i2对应的字节序列副本
                ByteBuffer bytebuffer1 = iovecwrapper.getShadow(i2);
                if(bytebuffer1 != null)
		//如果字节序列不为空,则添加到当前线程的缓存区中
                    Util.offerLastTemporaryDirectBuffer(bytebuffer1);
		 //清除索引i2对应的字节序列在iovecwrapper中的字节序列数组,及相应副本数组的信息
                iovecwrapper.clearRefs(i2);
            }

        }
        return l1;
        long l4;
	//委托给nativedispatcher,从filedescriptor对应的输入流读取数据,写到iovecwrapper的缓冲区中。
        long l2 = nativedispatcher.readv(filedescriptor, iovecwrapper.address, k);
}

再来看IOUtil写buffer数组的关键点
long l2 = nativedispatcher.readv(filedescriptor, iovecwrapper.address, k);

//SocketDispatcher
 long readv(FileDescriptor filedescriptor, long l, int i)
        throws IOException
    {
        return readv0(filedescriptor, l, i);
    }
     static native long readv0(FileDescriptor filedescriptor, long l, int i)
        throws IOException;

至此我们把SocketChannelImpl从输入流读取数据,写到ByteBuffer数组的read方法看完,首先同步写锁,确保通道,连接建立,输入流打开,委托给IOUtil,从输入流读取数据写到ByteBuffer数组中;IOUtil首先获取存放i个字节缓冲区的IOVecWrapper,遍历ByteBuffer数组m,将buffer添加到iovecwrapper的字节缓冲区数组中,如果ByteBuffer非Direct类型,委托Util从当前线程的缓冲区获取容量为j2临时DirectByteBuffer,并将ByteBuffer写到DirectByteBuffer,并将DirectByteBuffer添加到iovecwrapper的字节缓冲区(Shadow-Direct)数组中,将字节缓冲区的起始地址写到iovecwrapper,字节缓冲区的实际容量写到iovecwrapper;遍历iovecwrapper的字节缓冲区(Shadow-Direct)数组,将Shadow数组中的DirectByteBuffer通过Util添加到本地线程的缓存区中,并清除DirectByteBuffer在iovecwrapper的相应数组中的信息;最后通过
SocketDispatcher,从filedescriptor对应的输入流读取数据,写到iovecwrapper的缓冲区中。
总结:
    读输入流到buffer,首先同步读写,确保通道,输入流打开,通道连接建立,
清除原始读线程,获取新的本地读线程,委托IOUtil读输入流到buffer;IOUtil读输入流到buffer,首先确保buffer是可写的,否则抛出IllegalArgumentException,然后判断buffer是否为Direct类型,是则委托给readIntoNativeBuffer,否则通过Util从当前线程缓冲区获取一个临时的DirectByteBuffer,然后通过readIntoNativeBuffer读输入流数据到临时的DirectByteBuffer,这一个过程是通过SocketDispatcher的read方法实现,读写数据到DirectByteBuffer中后,将DirectByteBuffer中数据,写到原始buffer中,并将
DirectByteBuffer添加到添加临时DirectByteBuffer到当前线程的缓冲区,以便重用,因为重新DirectByteBuffer为直接操作物理内存,频繁分配物理内存,将耗费过多的资源。
    从输入流读取数据,写到ByteBuffer数组的read方法,首先同步写锁,确保通道,连接建立,输入流打开,委托给IOUtil,从输入流读取数据写到ByteBuffer数组中;IOUtil首先获取存放i个字节缓冲区的IOVecWrapper,遍历ByteBuffer数组m,将buffer添加到iovecwrapper的字节缓冲区数组中,如果ByteBuffer非Direct类型,委托Util从当前线程的缓冲区获取容量为j2临时DirectByteBuffer,并将ByteBuffer写到DirectByteBuffer,并将DirectByteBuffer添加到iovecwrapper的字节缓冲区(Shadow-Direct)数组中,将字节缓冲区的起始地址写到iovecwrapper,字节缓冲区的实际容量写到iovecwrapper;遍历iovecwrapper的字节缓冲区(Shadow-Direct)数组,将Shadow数组中的DirectByteBuffer通过Util添加到本地线程的缓存区中,并清除DirectByteBuffer在iovecwrapper的相应数组中的信息;最后通过
SocketDispatcher,从filedescriptor对应的输入流读取数据,写到iovecwrapper的缓冲区中。

SocketChannelImpl 解析四(关闭通道等):http://donald-draper.iteye.com/blog/2372717
分享到:
评论

相关推荐

    Ice-3.7.4.msi for windows版

    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:574) at IceInternal.Network.doFinishConnect(Network.java:393) ... 6 more 这种报错是ICE服务端没有起来,telnet服务端ICE的端口不通...

    防雷及短路计算软件.zip

    防雷及短路计算软件

    电线穿管选用小软件.zip

    电线穿管选用小软件

    【小白python数据分析入门4Pandas可视化-板块8案例 2018幸福大数据】

    小白python数据分析入门4Pandas可视化——板块8案例 2018幸福大数据,辅助8.1读取数据

    电气照明照度计算软件.zip

    电气照明照度计算软件

    数据库模拟考试试卷试卷

    数据库模拟考试试卷试卷

    AI人工智能算法工程师mk体系课(31周)

    算法是什么 划重点:算法是计算机科学与数学的边界,是一系列解决问题的清晰指令。 1、算法首先是数学的一部分,是子集而非全集只有那些能够被计算机实现的、而且用于解决实际问题的那部分数学内容,才能被纳入“算法”这个范畴,成为计算机科学的一部分,所以对于同一个数学问题,数学更加关心理论上能不能求解,而算法通常更关心是不是可以在有限的时间和空间内使用计算机求解。 2、算法是一系列解决问题的清晰指令就像是总指挥,需要不停地发送指令,一个算法的优劣可以用空间复杂度与时间复杂度来衡量,而算法工程师就是利用算法处理事物的人,因此逻辑完备和思路清晰是最重要的素质,通过这种方式,把想法通过算法的魔法变成“产品”,解决实际工程应用场景中的问题。 算法工程师承担的是整个工程中最核心的思路演绎,如果核心出了纰漏,则整个系统就容易出错,工程问题就得不到解决,1%的差错率都是不能被接受的。

    W25Q512数据手册

    W25Q512数据手册。The W25Q512JV (512M-bit) Serial Flash memory provides a storage solution for systems with limited space, pins and power. The 25Q series offers flexibility and performance well beyond ordinary Serial Flash devices. They are ideal for code shadowing to RAM, executing code directly from Dual/Quad SPI (XIP) and storing voice, text and data. The device operates on a single 2.7V to 3.6V power supply with current consumption as low as 1µA for power-down. All devices are offered in space-

    电学计算软件.zip

    电学计算软件

    五子棋wuziqi2.zip

    五子棋游戏想必大家都非常熟悉,游戏规则十分简单。游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【技术】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    单片机C语言Proteus仿真实例数码管显示的频率计

    单片机C语言Proteus仿真实例数码管显示的频率计提取方式是百度网盘分享地址

    计算机设计大赛微课类别国赛一等奖作品时光修复-图像的平滑操作视频

    计算机设计大赛微课类别国赛一等奖作品时光修复-图像的平滑操作视频提取方式是百度网盘分享地址

    setuptools-63.1.0.tar.gz

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    Python源码-数学美之太阳花.py

    Python源码-数学美之太阳花

    五子棋对战小游戏appgobang.zip

    五子棋游戏想必大家都非常熟悉,游戏规则十分简单。游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【技术】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    Java五子棋项目gobang.zip

    五子棋游戏想必大家都非常熟悉,游戏规则十分简单。游戏开始后,玩家在游戏设置中选择人机对战,则系统执黑棋,玩家自己执白棋。双方轮流下一棋,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。 【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【技术】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    setuptools-0.9.5.zip

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    Theano-0.5.0.tar.gz

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、Pandas和Requests,极大地丰富了Python的应用领域,从数据科学到Web开发。Python库的丰富性是Python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,Matplotlib和Seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

    数据库选择题库数据库选择

    数据库选择题库数据库选择

    单片机C语言Proteus仿真实例数码管显示4×4键盘矩阵按键

    单片机C语言Proteus仿真实例数码管显示4×4键盘矩阵按键提取方式是百度网盘分享地址

Global site tag (gtag.js) - Google Analytics