JAVA IO專題二:java NIO讀取文件并通過socket發(fā)送画饥,最少拷貝了幾次玩荠?堆外內(nèi)存和所謂的零拷貝到底是什么關(guān)系

相關(guān)IO專題

JAVA IO專題一:java InputStream和OutputStream讀取文件并通過socket發(fā)送漆腌,到底涉及幾次拷貝
JAVA IO專題二:java NIO讀取文件并通過socket發(fā)送,最少拷貝了幾次阶冈?堆外內(nèi)存和所謂的零拷貝到底是什么關(guān)系
JAVA IO專題三:java的內(nèi)存映射和應(yīng)用場(chǎng)景
JAVA IO專題四:java順序IO原理以及對(duì)應(yīng)的應(yīng)用場(chǎng)景

內(nèi)核的零拷貝

內(nèi)核的零拷貝闷尿,指的是不需要消耗CPU資源,完全交給DMA來處理女坑,內(nèi)核空間的數(shù)據(jù)沒有多余的拷貝填具。主要經(jīng)歷了這么幾個(gè)發(fā)展歷程:

一、傳統(tǒng)的read + send

1、先調(diào)用操作系統(tǒng)的read函數(shù)劳景,由DMA將文件拷貝到內(nèi)核誉简,然后CPU把內(nèi)核數(shù)據(jù)拷貝到用戶緩沖區(qū)(堆外內(nèi)存)
2、調(diào)用操作系統(tǒng)的send函數(shù)盟广,由CPU把用戶緩沖區(qū)的數(shù)據(jù)拷貝到socket緩沖區(qū)闷串,最后DMA把socket緩沖區(qū)數(shù)據(jù)拷貝到網(wǎng)卡進(jìn)行發(fā)送。

這個(gè)過程中內(nèi)核數(shù)據(jù)拷貝到用戶空間筋量,用戶空間又拷貝回內(nèi)存烹吵,有兩次多余的拷貝。

二桨武、sendfile初始版本

直接調(diào)用sendfile來發(fā)送文件肋拔,流程如下:
1、首先通過 DMA將數(shù)據(jù)從磁盤讀取到內(nèi)核
2呀酸、然后通過 CPU將數(shù)據(jù)從內(nèi)核拷貝到socket緩沖區(qū)
3凉蜂、最終通過 DMA將socket緩沖區(qū)數(shù)據(jù)拷貝到網(wǎng)卡發(fā)送

sendfile 與 read + send 方式相比,少了一次 CPU的拷貝七咧。但是從上述過程中也可以發(fā)現(xiàn)從內(nèi)核緩沖區(qū)拷貝到socket緩沖區(qū)是沒必要的跃惫。

三叮叹、sendfile改進(jìn)版本艾栋,真正的零拷貝

內(nèi)核為2.4或者以上版本的linux系統(tǒng)上,改進(jìn)后的處理過程如下:
1蛉顽、DMA 將磁盤數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)蝗砾,向socket緩沖區(qū)中追加當(dāng)前要發(fā)送的數(shù)據(jù)在內(nèi)核緩沖區(qū)中的位置和偏移量
2、DMA gather copy 根據(jù) socket緩沖區(qū)中的位置和偏移量携冤,直接將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到網(wǎng)卡上悼粮。
經(jīng)過上述過程,數(shù)據(jù)只經(jīng)過了 2 次 copy 就從磁盤傳送出去了曾棕。并且沒有CPU的參與扣猫。

java的零拷貝

一、利用directBuffer

在上一篇文章JAVA IO專題一:java InputStream和OutputStream讀取文件并通過socket發(fā)送翘地,到底涉及幾次拷貝中申尤,我們提到了基于BIO讀取文件發(fā)送消息,一共涉及六次拷貝衙耕,其中堆外和堆內(nèi)內(nèi)存的拷貝是多余的昧穿,我們可以利用directBuffer來減少這兩次拷貝:

//打開文件通道
FileChannel fileChannel = FileChannel.open(Paths.get("/test.txt"));
//申請(qǐng)堆外內(nèi)存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//讀取到堆外內(nèi)存
fileChannel.read(byteBuffer);
//打開socket通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9099));
//堆外內(nèi)存寫入socket通道
socketChannel.write(byteBuffer);

每一行代碼都有清楚的注釋,我們主要來看一下fileChannel.read橙喘、socketChannel.write做了什么:

  • fileChannel.read 分析
//FileChannelImpl
public int read(ByteBuffer dst) throws IOException {
    ... 忽略了一堆不重要代碼
        synchronized (positionLock) {
            int n = 0;
            int ti = -1;
            try {
                do {
                   // 調(diào)用IOUtil时鸵,根據(jù)文件描述符fd讀取數(shù)據(jù)到直接緩沖區(qū)dst中
                    n = IOUtil.read(fd, dst, -1, nd);
                } while ((n == IOStatus.INTERRUPTED) && isOpen());
                return IOStatus.normalize(n);
            } finally {
                threads.remove(ti);
                end(n > 0);
                assert IOStatus.check(n);
            }
        }
    }
//IOUtil
static int read(FileDescriptor fd, ByteBuffer dst, long position,
                    NativeDispatcher nd)
        throws IOException
    {
        ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
        try {
            int n = readIntoNativeBuffer(fd, bb, position, nd);
            bb.flip();
            if (n > 0)
                dst.put(bb);
            return n;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
    }

private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
                                            long position, NativeDispatcher nd)
        throws IOException
    {
        int pos = bb.position();
        int lim = bb.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);

        if (rem == 0)
            return 0;
        int n = 0;
        if (position != -1) {
            n = nd.pread(fd, ((DirectBuffer)bb).address() + pos,
                         rem, position);
        } else {
           //第一次讀取會(huì)走到這里,否則走上面的分支
            n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem);
        }
        if (n > 0)
            bb.position(pos + n);
        return n;
    }
//FileDispatcherImpl
 int read(FileDescriptor fd, long address, int len) throws IOException {
        return read0(fd, address, len);
    }

這里的調(diào)用鏈比較深厅瞎,我們一步一步梳理:

  1. 調(diào)用fileChannel.read實(shí)際是走到了FileChannelImpl.read方法饰潜,然后走到n = IOUtil.read(fd, dst, -1, nd);調(diào)用IOUtil的read初坠,傳入了文件描述符、directBuffer
  2. IOUtil 調(diào)用自己的readIntoNativeBuffer方法彭雾,字面意思是講數(shù)據(jù)讀取到native緩存某筐,即堆外內(nèi)存
  3. IOUtil 的 readIntoNativeBuffer 方法調(diào)用n = nd.read(fd, ((DirectBuffer)bb).address() + pos, rem);,即NativeDispatcher 的read方法冠跷,傳入文件描述符南誊,堆外內(nèi)存地址以及要讀取的長度
  4. 這里的 NativeDispatcher 實(shí)現(xiàn)類為 FileDispatcherImpl,實(shí)際調(diào)用的是native方法read0蜜托,并傳入了文件描述符抄囚、堆外內(nèi)存地址和讀取長度

我們簡單看一下native的read0方法做了什么:

// 以下內(nèi)容來自于 jdk/src/solairs/native/sun/nio/ch/FileDispatcherImpl.c

JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_read0(JNIEnv *env, jclass clazz,
                             jobject fdo, jlong address, jint len)
{
    //拿到文件描述符
    jint fd = fdval(env, fdo);
    //根據(jù)地址拿到堆外內(nèi)存的指針
    void *buf = (void *)jlong_to_ptr(address);
  //直接調(diào)用系統(tǒng)函數(shù)read把文件描述符讀取到buf中
    return convertReturnVal(env, read(fd, buf, len), JNI_TRUE);
}

可以看到native的read0方法是直接調(diào)用系統(tǒng)函數(shù)read,根據(jù)jvm傳過來的堆外內(nèi)存地址橄务,將文件數(shù)據(jù)讀取到堆外內(nèi)存中(read方法的作用在內(nèi)核零拷貝小節(jié)里已經(jīng)提到了)幔托。即直接操作堆外內(nèi)存,而不使用DirectByteBuffer的時(shí)候蜂挪,還需要將堆外內(nèi)存拷貝到堆內(nèi)進(jìn)行讀寫JAVA IO專題一:java InputStream和OutputStream讀取文件并通過socket發(fā)送重挑,到底涉及幾次拷貝),因此使用堆外內(nèi)存+channel的方式棠涮,可以避免堆內(nèi)外內(nèi)存拷貝谬哀,一定程度上也能提高效率。

  • socketChannel.write 分析
//SocketChannelImpl.java
  public int write(ByteBuffer buf) throws IOException {
        synchronized (writeLock) {
              ... 忽略不重要代碼
            int n = 0;
            try {
                for (;;) {
                    //調(diào)用IOUtil.write寫數(shù)據(jù)
                    n = IOUtil.write(fd, buf, -1, nd);
                    if ((n == IOStatus.INTERRUPTED) && isOpen())
                        continue;
                    return IOStatus.normalize(n);
                }
            } finally {
                writerCleanup();
            }
        }
    }
//IOUtil.java
static int write(FileDescriptor fd, ByteBuffer src, long position,
                     NativeDispatcher nd)
        throws IOException
    {
        if (src instanceof DirectBuffer)
             //directBuffer直接走這里
            return writeFromNativeBuffer(fd, src, position, nd);
    }

  private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb,
                                             long position, NativeDispatcher nd) throws IOException
    {
        int pos = bb.position();
        int lim = bb.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);

        int written = 0;
        if (rem == 0)
            return 0;
        if (position != -1) {
            written = nd.pwrite(fd,
                                ((DirectBuffer)bb).address() + pos,
                                rem, position);
        } else {
            //調(diào)用SocketDispatcher寫數(shù)據(jù)
            written = nd.write(fd, ((DirectBuffer)bb).address() + pos, rem);
        }
        if (written > 0)
            bb.position(pos + written);
        return written;
    }

//SocketDispatcher.java
int write(FileDescriptor fd, long address, int len) throws IOException {
        //直接調(diào)用了FileDispatcherImpl的native方法write0
        return FileDispatcherImpl.write0(fd, address, len);
    }

在看native方法之前還是先做簡單的梳理:

  1. socketChannel.write 實(shí)際調(diào)用了SocketChannelImpl.write严肪,然后調(diào)用IOUtil.write(fd, buf, -1, nd); 傳入文件描述符和堆外內(nèi)存引用
  2. IOUtil.write調(diào)用自己的私有方法 writeFromNativeBuffer史煎,內(nèi)部調(diào)用了written = nd.write(fd, ((DirectBuffer)bb).address() + pos, rem);,將文件描述符驳糯、堆外內(nèi)存地址交給了NativeDispatcher
  3. 此處的NativeDispatcher實(shí)際是 SocketDispatcher篇梭,里面直接調(diào)用了FileDispatcherImpl.write0(fd, address, len);native方法

接著跟蹤FileDispatcherImpl.write0(fd, address, len);這個(gè)native方法:

// 以下內(nèi)容來自于 jdk/src/solairs/native/sun/nio/ch/FileDispatcherImpl.c

JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_write0(JNIEnv *env, jclass clazz,
                              jobject fdo, jlong address, jint len)
{
    //轉(zhuǎn)換文件描述符
    jint fd = fdval(env, fdo);
    //轉(zhuǎn)換為堆外內(nèi)存指針
    void *buf = (void *)jlong_to_ptr(address);
    //直接調(diào)用系統(tǒng)函數(shù)write將堆外內(nèi)存數(shù)據(jù)發(fā)送出去
    return convertReturnVal(env, write(fd, buf, len), JNI_FALSE);
}

可以看到native的write0方法是直接調(diào)用系統(tǒng)函數(shù)write將堆外內(nèi)存數(shù)據(jù)發(fā)送出去(write方法的作用在內(nèi)核零拷貝小節(jié)里已經(jīng)提到了)。

  • 小結(jié)
    fileChannel和socketChannel配合directBuffer酝枢,本質(zhì)上區(qū)別不大恬偷,都是配合系統(tǒng)函數(shù)write和read對(duì)文件描述符,直接操作堆外內(nèi)存帘睦。因此相比較于BIO可以省去兩次拷貝袍患。
二、channel.transferTo

java中的零拷貝就是依賴操作系統(tǒng)的sendfile函數(shù)來實(shí)現(xiàn)的官脓,提供了channel.transferTo方法协怒,允許將一個(gè)channel的數(shù)據(jù)直接發(fā)送到另一個(gè)channel,接下來我們通過示例代碼和具體的源碼來分析和驗(yàn)證前面的說法卑笨。
示例代碼如下:

//打開socketChannel
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9099));
//
FileChannel fileChannel = FileChannel.open(Paths.get("/test.txt"));
fileChannel.transferTo(0, fileChannel.size(), socketChannel);

只用了一行代碼fileChannel.transferTo(0, fileChannel.size(), socketChannel);就把文件數(shù)據(jù)寫到了socket孕暇,繼續(xù)看源碼:

//FileChannelImpl.java
public long transferTo(long position, long count,
                           WritableByteChannel target)
        throws IOException
    {
         ... 忽略不重要代碼
        long sz = size();
        if (position > sz)
            return 0;
        int icount = (int)Math.min(count, Integer.MAX_VALUE);
        if ((sz - position) < icount)
            icount = (int)(sz - position);

        long n;
          //先嘗試直接tranfer,如果內(nèi)核支持的話
        if ((n = transferToDirectly(position, icount, target)) >= 0)
            return n;
        //嘗試mappedTransfer,只適用于受信任的channel類型
        if ((n = transferToTrustedChannel(position, icount, target)) >= 0)
            return n;
          //channel不受信任的話妖滔,會(huì)走最慢的方式
        return transferToArbitraryChannel(position, icount, target);
    }

// FileChannelimpl.java
private long transferToDirectly(long position, int icount,
                                    WritableByteChannel target)
        throws IOException
    {
        if (!transferSupported)
            //系統(tǒng)不支持就直接返回
            return IOStatus.UNSUPPORTED;

        FileDescriptor targetFD = null;
        if (target instanceof FileChannelImpl) { //如果目標(biāo)是fileChannel則走這里
            if (!fileSupported)
                return IOStatus.UNSUPPORTED_CASE;
            targetFD = ((FileChannelImpl)target).fd;
        } else if (target instanceof SelChImpl) { 
            //SocketChannel實(shí)現(xiàn)了SelChImpl接口隧哮,因此會(huì)走這里
            if ((target instanceof SinkChannelImpl) && !pipeSupported)
                return IOStatus.UNSUPPORTED_CASE;
            //給targetFD賦值
            targetFD = ((SelChImpl)target).getFD();
        }
        if (targetFD == null)
            return IOStatus.UNSUPPORTED;
        //將fileChannel和socketChannel對(duì)應(yīng)的fd轉(zhuǎn)換為具體的值
        int thisFDVal = IOUtil.fdVal(fd);
        int targetFDVal = IOUtil.fdVal(targetFD);
        //不支持自己給自己傳輸
        if (thisFDVal == targetFDVal) 
            return IOStatus.UNSUPPORTED;

        long n = -1;
        int ti = -1;
        try {
            begin();
            ti = threads.add();
            if (!isOpen())
                return -1;
            do { 
                //調(diào)用native方法transferTo0
                n = transferTo0(thisFDVal, position, icount, targetFDVal);
            } while ((n == IOStatus.INTERRUPTED) && isOpen());
            if (n == IOStatus.UNSUPPORTED_CASE) {
                if (target instanceof SinkChannelImpl)
                    pipeSupported = false;
                if (target instanceof FileChannelImpl)
                    fileSupported = false;
                return IOStatus.UNSUPPORTED_CASE;
            }
            if (n == IOStatus.UNSUPPORTED) {
                // Don't bother trying again
                transferSupported = false;
                return IOStatus.UNSUPPORTED;
            }
            return IOStatus.normalize(n);
        } finally {
            threads.remove(ti);
            end (n > -1);
        }
    }

代碼有點(diǎn)長:

  1. 調(diào)用FileChannelImpl的transferTo,會(huì)嘗試三種情況座舍,如果系統(tǒng)支持零拷貝沮翔,則走 transferToDirectly
  2. transferToDirectly 方法前面做了各種判斷,其實(shí)可以理解為直接調(diào)用了n = transferTo0(thisFDVal, position, icount, targetFDVal);native方法

再來跟蹤transferTo0:

// 以下內(nèi)容來自于 jdk/src/solairs/native/sun/nio/ch/FileChannelImpl.c
JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this,
                                            jint srcFD,
                                            jlong position, jlong count,
                                            jint dstFD)
{
#if defined(__linux__)
    off64_t offset = (off64_t)position;
    //直接調(diào)用sendfile
    jlong n = sendfile64(dstFD, srcFD, &offset, (size_t)count);
    if (n < 0) {
        if (errno == EAGAIN)
            return IOS_UNAVAILABLE;
        if ((errno == EINVAL) && ((ssize_t)count >= 0))
            return IOS_UNSUPPORTED_CASE;
        if (errno == EINTR) {
            return IOS_INTERRUPTED;
        }
        JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");
        return IOS_THROWN;
    }
    return n;
}

這個(gè)方法里其實(shí)有l(wèi)inux曲秉、solaris采蚀、APPLE等多個(gè)平臺(tái)的實(shí)現(xiàn),這里只截取linux下的實(shí)現(xiàn)承二,可以看到是直接調(diào)用了系統(tǒng)函數(shù)sendfile來實(shí)現(xiàn)的數(shù)據(jù)發(fā)送榆鼠,具體的拷貝次數(shù)則要看linux內(nèi)核的版本了。

總結(jié)

  • NIO讀取文件并通過socket發(fā)送亥鸠,最少拷貝幾次妆够?
    直接調(diào)用channel.transferTo,同時(shí)linux內(nèi)核版本大于等于2.4负蚊,則可以將拷貝次數(shù)降低到2次神妹,并且CPU不參與拷貝。
  • 堆外內(nèi)存和所謂的零拷貝到底是什么關(guān)系
    筆者理解網(wǎng)上說的零拷貝家妆,可以理解為內(nèi)核層面的零拷貝和java層面的零拷貝鸵荠,所謂的0并不是一次拷貝都沒有,而是在不同的場(chǎng)景下盡可能減少拷貝次數(shù)揩徊。

參考文章

Java 堆外內(nèi)存腰鬼、零拷貝嵌赠、直接內(nèi)存以及針對(duì)于NIO中的FileChannel的思考
JavaIO原理剖析之 網(wǎng)絡(luò)IO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載塑荒,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末姜挺,一起剝皮案震驚了整個(gè)濱河市齿税,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炊豪,老刑警劉巖凌箕,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異词渤,居然都是意外死亡牵舱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門缺虐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芜壁,“玉大人,你說我怎么就攤上這事』弁” “怎么了顷牌?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塞淹。 經(jīng)常有香客問我窟蓝,道長,這世上最難降的妖魔是什么饱普? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任运挫,我火速辦了婚禮,結(jié)果婚禮上套耕,老公的妹妹穿的比我還像新娘滑臊。我一直安慰自己,他們只是感情好箍铲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布雇卷。 她就那樣靜靜地躺著,像睡著了一般颠猴。 火紅的嫁衣襯著肌膚如雪关划。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天翘瓮,我揣著相機(jī)與錄音贮折,去河邊找鬼。 笑死资盅,一個(gè)胖子當(dāng)著我的面吹牛调榄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呵扛,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼每庆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了今穿?” 一聲冷哼從身側(cè)響起缤灵,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蓝晒,沒想到半個(gè)月后腮出,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芝薇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年胚嘲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洛二。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馋劈,死狀恐怖立倍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侣滩,我是刑警寧澤口注,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站君珠,受9級(jí)特大地震影響寝志,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜策添,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一材部、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唯竹,春花似錦乐导、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至产上,卻和暖如春棵磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晋涣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工仪媒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谢鹊。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓算吩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佃扼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偎巢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1、什么是零拷貝 維基上是這么描述零拷貝的:零拷貝描述的是CPU不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域的任務(wù)...
    愛學(xué)習(xí)的蹭蹭閱讀 4,963評(píng)論 0 20
  • 1. NIO 閱讀本節(jié)前,請(qǐng)先閱讀我的NIO基礎(chǔ)文章:http://www.reibang.com/nb/878...
    伊凡的一天閱讀 1,773評(píng)論 0 21
  • 久違的晴天遵倦,家長會(huì)尽超。 家長大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了梧躺。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)乐尊。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,494評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友壁熄。感恩相遇睬罗!感恩不離不棄。 中午開了第一次的黨會(huì)庞瘸,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,551評(píng)論 0 11
  • 可愛進(jìn)取,孤獨(dú)成精。努力飛翔菠净,天堂翱翔。戰(zhàn)爭(zhēng)美好彪杉,孤獨(dú)進(jìn)取毅往。膽大飛翔,成就輝煌派近。努力進(jìn)取攀唯,遙望,和諧家園渴丸『钹郑可愛游走...
    趙原野閱讀 2,716評(píng)論 1 1