磁盘文件的正常读写与异步读写

转自:http://222.30.226.10/hhcmc/study/teach_vc/teach_sp_52.htm

在Win32系统下文件可以支持平常的同步读写和异步读写(但在Win9X下,Win32系统不支持磁盘文件的异步读写)。本节在后面部分将会介绍文件的异步读写,最后一段内容将向大家讲解一下文件的区域加锁。

在Win32系统中支持64位长度的文件,所以在很多文件操作函数中需要两个DWORD参数来表示文件长度,一个DWORD用来表示低32位,另一个用来表示高32位。

文件的读写进行在文件被正确打开后,但请确认在打开文件时设置了正确的读写标记。在Win32的文件操作中没有了以前类似与以前ANSI C中的fputs fgets fprintf fscanf等函数,只有类似于fread和fwrite的ReadFile和WriteFile函数。

ReadFile用于文件读,函数原型为:

BOOL ReadFile(HANDLE hFile,                // handle to fileLPVOID lpBuffer,             // data bufferDWORD nNumberOfBytesToRead,  // number of bytes to readLPDWORD lpNumberOfBytesRead, // number of bytes readLPOVERLAPPED lpOverlapped    // overlapped buffer
);

其中各项参数的含义为:

  • hFile:文件句柄,为CreateFile时返回的句柄
  • lpBuffer:保存读入的数据的指针
  • nNumberOfBytesToRead:指定需要读入的字节数
  • lpNumberOfBytesRead:返回实际读入的字节数
  • lpOverlapped:在文件异步读写时使用的数据,在同步读写中全部都设置为NULL,在Win9X中只支持对串口的异步操作。

如果返回值为FALSE并且读入的字节数也返回为0,则表示文件到达了末尾。

WriteFile用于文件写,函数原型为:
BOOL WriteFile(HANDLE hFile,                    // handle to fileLPCVOID lpBuffer,                // data bufferDWORD nNumberOfBytesToWrite,     // number of bytes to writeLPDWORD lpNumberOfBytesWritten,  // number of bytes writtenLPOVERLAPPED lpOverlapped        // overlapped buffer
);

参数的含义和ReadFile类似。

如果需要移动文件指针到相关位置(和文件读写不同,这个函数没有异步版本),使用

DWORD SetFilePointer(HANDLE hFile,                // handle to fileLONG lDistanceToMove,        // bytes to move pointerPLONG lpDistanceToMoveHigh,  // bytes to move pointerDWORD dwMoveMethod           // starting point
);

其中各项参数的含义为:

  • hFile:文件句柄,为CreateFile时返回的句柄
  • lpBuffer:保存读入的数据的指针
  • lDistanceToMove:移动的字节数低DWORD
  • lpDistanceToMoveHigh:移动的字节数高DWORD,为了支持64位(2的64次方字节)长度的大文件,而用来指定64字节的高32位,如果文件大小只需要32位就可以表示,则设置为NULL
  • ldwMoveMethod:移动方法,可以选择下面的值。
    FILE_BEGIN 从文件开始处开始移动
    FILE_CURRENT 从文件开始除开始移动
    FILE_END 从文件末尾开始移动

函数返回值和参数lpDistanceToMoveHigh(当该参数不为NULL时)表明文件指针当前的位置(从文件头到当前的字节数),但当参数lpDistanceToMoveHigh为NULL时如果返回INVALID_SET_FILE_POINTER表明执行失败,当参数 lpDistanceToMoveHigh不为NULL时如果返回INVALID_SET_FILE_POINTER还需要判断GetLastError 的返回值是否不为NO_ERROR。下面是两种情况下判断错误。

//第一种情况
DWORD dwPtr = SetFilePointer (hFile, lDistance, NULL, FILE_BEGIN) ; if (dwPtr == INVALID_SET_FILE_POINTER) // Test for failure
{ // Obtain the error code. dwError = GetLastError() ; // 处理错误// . . . } // End of error handler //第二种情况
dwPtrLow = SetFilePointer (hFile, lDistLow, & lDistHigh, FILE_BEGIN) ; // Test for failure
if (dwPtrLow == INVALID_SET_FILE_POINTER && (dwError = GetLastError()) != NO_ERROR )
{ // 处理错误 // . . . } // End of error handler

在Win32中没有提供得到文件当前指针位置的函数,但通过SetFilePointer也可以确定文件指针当前的位置。在MSDN中提供了两个宏来得到当前文件的指针位置:

//对32位长度的文件#define GetFilePointer(hFile) SetFilePointer(hFile, 0, NULL, FILE_CURRENT)
//对超过32位长度的文件#define GetVLFilePointer(hFile, lpPositionHigh) /(*lpPositionHigh = 0, /SetFilePointer(hFile, 0, lpPositionHigh, FILE_CURRENT))

对了可以通过SetFilePointer来得到文件长度,方法就是从文件位结束处移动0字节,返回值就是文件的长度。

HANDLE hFile = CreateFile(...);//打开文件进行读
DWORD dwLen;
dwLen = SetFilePointer (hFile, 0, NULL, FILE_END) ;
CloseHandle( hFile ) ;

当然Win32中也提供了专门的函数来得到文件的大小

BOOL GetFileSizeEx(HANDLE hFile,              // handle to filePLARGE_INTEGER lpFileSize  // file size
);
typedef union _LARGE_INTEGER { struct {DWORD LowPart; LONG  HighPart; };LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

其中lpFileSize是一个联合数据,用来表明文件的大小。

文件的异步读写主要是用在大文件的读写上,当使用异步读写时,ReadFile和WriteFile会马上返回,并在读写完成时通知应用程序。

要使用异步功能首先需要在打开文件时指定FILE_FLAG_OVERLAPPED作为标记(dwFlagsAndAttributes),在读写文件时可以使用ReadFile/WriteFile或者ReadFileEx /WriteFileEx来进行读写,当调用不同的函数时读写完成后通知应用程序的方法有所不同的,。下面分别介绍这两种方法:

第一种方法,利用ReadFile/WriteFile,这对函数使用事件信号来进行读写完成的通知,由于磁盘读写是一个比较耗费时间的操作,而且现在的磁盘子系统可以在磁盘读写时脱离CPU而单独进行,例如使用DMA方式,所以在磁盘读写时可以进行其他一些操作以充分利用CPU。关于事件信号相关内容请参考4.4节 进程/线程间同步中事件部分内容。并且在文件读写时提供OVERLAPPED数据。

结构定义如下:
typedef struct _OVERLAPPED { ULONG_PTR  Internal; //系统使用ULONG_PTR  InternalHigh; //系统使用DWORD  Offset; // 文件读或写的开始位置低32位,对于命名管道和其他通信设备必须设置为0DWORD  OffsetHigh; // 文件读或写的开始位置高32位,对于命名管道和其他通信设备必须设置为0HANDLE hEvent; // 事件量,当操作完成时,这个时间会变为有信号状态
} OVERLAPPED; //下面的代码演示了文件异步读取
//并且比较了同步和异步之间的性能差异
void DoDataDeal(BYTE *pbData,int iLen)
{//对字节进行操作Sleep(3*1000);//假设每次计算需要2秒钟
}
//下面是使用异步读的示范代码,假设c:/temp/large_file.dat文件有130MB大小()
//每次读入10MB字节,并且对文件中每个字节进行操作,由于可以使用异步操作所以可以在下一次读入数据的同时进行计算
void ReadM1(void)
{HANDLE hFile = CreateFile("c://temp//large_file.dat",GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,NULL);if( INVALID_HANDLE_VALUE != hFile ){HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,"read_event");BYTE *pbRead = new BYTE[1024*1024*10];//10MB字节BYTE *pbBuf = new BYTE[1024*1024*10];DWORD dwRead,dwCount=0;OVERLAPPED overlap;overlap.Offset = 0;overlap.OffsetHigh =0;overlap.hEvent = hEvent;DWORD dwBegin= GetTickCount();//记录开始时间ReadFile(hFile,pbRead,1024*1024*10,&dwRead,&overlap);{//开始计算for(int i=1;i<13;i++){printf("M1 i=%d/n",i);WaitForSingleObject(hEvent,INFINITE);//等待上一次完成memcpy(pbBuf,pbRead,1024*1024*10);overlap.Offset = i * (1024*1024*10);overlap.OffsetHigh =0;overlap.hEvent = hEvent;ReadFile(hFile,pbRead,1024*1024*10,&dwRead,&overlap);//在文件进行读的同时进行计算DoDataDeal(pbBuf,1024*1024*10);}WaitForSingleObject(hEvent,INFINITE);//等待最后一次完成memcpy(pbBuf,pbRead,1024*1024*10);//数据处理DoDataDeal(pbBuf,1024*1024*10);}//结束计算printf("耗时 %d/n",GetTickCount()-dwBegin);//操作完成CloseHandle(hEvent);CloseHandle(hFile);delete pbRead;delete pbBuf;}
}
//下面是上面功能的文件同步读版本
void ReadM2(void)
{HANDLE hFile = CreateFile("c://temp//large_file.dat",GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);if( INVALID_HANDLE_VALUE != hFile ){DWORD dwRead,dwCount=0;BYTE *pbRead = new BYTE[1024*1024*10];//10MB字节BYTE *pbBuf = new BYTE[1024*1024*10];//10MB字节DWORD dwBegin= GetTickCount();//记录开始时间for(int i=0;i<13;i++){printf("M2 i=%d/n",i);if(!ReadFile(hFile,pbRead,1024*1024*10,&dwRead,NULL)){printf("error read");break;}memcpy(pbBuf,pbRead,1024*1024*10);//计算DoDataDeal(pbBuf,1024*1024*10);}printf("耗时 %d/n",GetTickCount()-dwBegin);//操作完成CloseHandle(hFile);delete pbRead;delete pbBuf;}
}

在文件的异步读写中,如果ReadFile/WriteFile返回FALSE,则需要通过LastError来进一步确认是否发生错误,例如下面的错误检测代码:

bResult = ReadFile(hFile, &inBuffer, nBytesToRead, &nBytesRead, &gOverlapped) ;
if (!bResult) switch (dwError = GetLastError()) { case ERROR_HANDLE_EOF: { //到达文件尾} case ERROR_IO_PENDING: { //正在进行异步操作} // end case } // end switch
} // end if
此外可以通过GetOverlappedResult函数来得到异步函数的执行情况
BOOL GetOverlappedResult(HANDLE hFile,                       // handle to file, pipe, or deviceLPOVERLAPPED lpOverlapped,          // overlapped structureLPDWORD lpNumberOfBytesTransferred, // bytes transferredBOOL bWait                          // wait option
);

如果函数调用返回FALSE则可以用GetLastError来得到错误,如果返回成功则可以通过lpNumberOfBytesTransferred 参数来确定当前有多少数据已经被读或写。lpOverlapped参数必须与调用ReadFile或WriteFile时使用同一个数据区。最后一个参数 bWait表明是否等待异步操作结束时才返回,如果设置为TRUE就可以等待文件读写完成时返回,否则就会马上返回,利用这个特点可以利用它来等待异步文件操作的结束(就如同等待事件变为有信号状态一样起到相同的作用)。

在上面的例子中没有过多的进行错误检查,如果大家有兴趣可以自己运行一下这个例子,看看异步文件读写对性能的影响。在我自己的计算机PII 450 IBM 30GB硬盘上同步读比异步读慢了大约10%左右,这主要时因为数据处理时间我设置为两秒钟,如果设置得足够长,会显示出异步和同步处理时的差异极限。此外由于磁盘缓存得作用也会影响结果,所以如果读入的数据量更大将会产生更明显的差异,这是因为虽然异步读写会在调用等待函数上也会耗费一些时间,所以如果数据量小就无法分辨出差异。

请记住OVERLAPPED参数在文件操作执行完以前不要手工修改结构内的值,因为系统会使用结构中的数据。

对于WriteFile操作也可以用相同方法,在WriteFile中磁盘操作将耗费更多的时间,所以使用异步写更能体现优势,你可以将这个例子改为磁盘写后自己测试一下。

下载利用ReadFile进行异步文件读的示范代码

第二种方法,利用ReadFileEx/WriteFileEx,这对函数使用回调函数来进行读写完成的通知。

BOOL ReadFileEx(HANDLE hFile,                                       // handle to fileLPVOID lpBuffer,                                    // data bufferDWORD nNumberOfBytesToRead,                         // number of bytes to readLPOVERLAPPED lpOverlapped,                          // offsetLPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine
);
  • hFile为文件句柄。
  • lpBuffer指明了写入数据的内存区指针。
  • nNumberOfBytesToRead为要求读入的数据字节数。
  • lpOverlapped为一个OVERLAPPED的结构,这个结构hEvent字段将被系统忽略,但是通过Offset和OffsetHigh字段来表明开始读文件的位置。
  • lpCompletionRoutine为一个通知用的回调函数。

函数的最后一个参数指明了一个回调函数,这个回调函数称为一个告警函数。函数必须具有这样的原型:

VOID CALLBACK FileIOCompletionRoutine(DWORD dwErrorCode,                // completion codeDWORD dwNumberOfBytesTransfered,  // number of bytes transferredLPOVERLAPPED lpOverlapped         // I/O information buffer
);
  • dwErrorCode为错误代码,如果为0表示正确,为ERROR_HANDLE_EOF表示到达文件的末尾。
  • dwNumberOfBytesTransfered为成功传送的字节数,如果发生错误,这个值为0。
  • lpOverlapped为一个OVERLAPPED的结构,这个结构必须与调用ReadFileEx时指向相同的数据区,并且在调用ReadFileEx后不能手工更改这个结构中的字段。

那么如何检测回调函数已经被调用了(文件操作已经完成),你可以设置一个全局的同步量来进行通知,但是系统提供了其他简便的方法供开发者使用,这就是SleepEx WaitForMultipleObjectsEx 和WaitForSingleObjectEx。

当线程调用ReadFileEx或WriteFileEx时,提供了一个告警函数,这个告警函数会被放入一个队列,当操作完成时操作系统会从队列中取出这些函数进行调用。所以对于属于本进程内部所产生的需要等待被调用的告警函数来说可以直接使用SleepEx对当前线程进行休眠并等待当前提交所有的告警函数被执行。如果希望等待指定文件操作上的告警函数被调用你可以使用WaitForMultipleObjectsEx或 WaitForSingleObjectEx。这三个函数的原型为:

DWORD SleepEx(DWORD dwMilliseconds,  // time-out intervalBOOL bAlertable        // early completion option
);
DWORD WaitForSingleObjectEx(HANDLE hHandle,        // handle to objectDWORD dwMilliseconds,  // time-out intervalBOOL bAlertable        // alertable option
);
DWORD WaitForMultipleObjectsEx(DWORD nCount,             // number of handles in arrayCONST HANDLE *lpHandles,  // object-handle arrayBOOL fWaitAll,            // wait optionDWORD dwMilliseconds,     // time-out intervalBOOL bAlertable           // alertable option
);

这三个函数和Sleep WaitForSingleObject WaitForMultipleObjects的差别就在于多了最后一个参数bAlertable,这个参数需要设置为TRUE表明等待文件异步操作的完成。通过检测函数返回值可以得知文件操作是否完成,例如下面的代码:

  ReadFileEx(hFile,pbRead,1024*1024*50,&overlap,MyIORoutine);while(WAIT_IO_COMPLETION != SleepEx(1,TRUE) )//检测文件操作是否完成//while (WaitForSingleObjectEx(hFile,1,TRUE) != WAIT_OBJECT_0 ) //在这里WaitForSingleObjectEx和SleepEx具有相同作用{DoSomething();}

对于SleepEx来说如果返回WAIT_IO_COMPLETION则表示异步操作完成,而对于文件对象来说如果异步操作完成文件对象就会变为有信号状态。下面的例子是一个利用告警回调函数实现的文件异步读写。

VOID CALLBACK MyIORoutine(DWORD dwErrorCode,                // completion codeDWORD dwNumberOfBytesTransfered,  // number of bytes transferredLPOVERLAPPED lpOverlapped         // I/O information buffer
)
{//定义一个简单的回调函数printf("文件读完成/n");
}void DoSomething(void)
{printf("current time %d/n",GetTickCount());Sleep(2000);//假设耗时的操作需要两秒钟
}//下面是使用异步读的示范代码,假设c:/temp/large_file.dat文件有130MB大小()
//一次性读入50MB字节,在读入的过程中进行一些其他操作
void ReadM(void)
{HANDLE hFile = CreateFile("c://temp//large_file.dat",GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,NULL);if( INVALID_HANDLE_VALUE != hFile ){BYTE *pbRead = new BYTE[1024*1024*50];//50MB字节OVERLAPPED overlap;overlap.Offset = 0;overlap.OffsetHigh =0;overlap.hEvent = NULL; //使用告警函数时无需要使用事件DWORD dwBegin= GetTickCount();//记录开始时间printf("begin time %d/n",dwBegin);ReadFileEx(hFile,pbRead,1024*1024*50,&overlap,MyIORoutine);while(WAIT_IO_COMPLETION != SleepEx(1,TRUE) )//检测文件操作是否完成//while (WaitForSingleObjectEx(hFile,1,TRUE) != WAIT_OBJECT_0 ) //在这里WaitForSingleObjectEx和SleepEx具有相同作用{DoSomething();//在文件读的执行过程中进行其他操作}printf("耗时 %d/n",GetTickCount()-dwBegin);//操作完成CloseHandle(hFile);delete pbRead;}
}

WriteFileEx的用法与ReadFileEx的用法是类似的。

下载利用ReadFileEx进行异步文件读的示范代码

在磁盘操作中磁盘写比读需要花更多的时间,并且大文件的异步写可以更加有效的提高CPU利用率。但是异步操作会给开发和调试带来一些麻烦,所以我建议除非在非常必要(性能要求非常高,文件非常大)的情况下才使用异步的磁盘读写。再提一点,对于磁盘文件的异步操作的方式同样可以用于上章所讲的命名管道,或者是串口的异步操作。

文件加锁时在打开文件后对文件的某个区域加锁,加锁后可以防止其他进程对该区域数据进行读取。相关的函数为:

BOOL LockFile(HANDLE hFile,                   // 文件句柄DWORD dwFileOffsetLow,          // 文件加锁开始位置低32位DWORD dwFileOffsetHigh,         // 文件加锁开始位置高32位DWORD nNumberOfBytesToLockLow,  // 区域长度低32位DWORD nNumberOfBytesToLockHigh  // 区域长度高32位
);
BOOL UnlockFile(HANDLE hFile,                    // 文件句柄DWORD dwFileOffsetLow,          // 文件解锁开始位置低32位DWORD dwFileOffsetHigh,         // 文件解锁开始位置高32位DWORD nNumberOfBytesToLockLow,  // 区域长度低32位DWORD nNumberOfBytesToLockHigh  // 区域长度高32位
);

在文件加锁和解锁上需要有对应关系,这种对应关系就是对A区域加锁后必须对A区域解锁后才可以对其他区域解锁,而且必须是一对一的关系,也就是说调用一次对A区域的加锁函数就必须调用一次对A区域的解锁函数,而不能对一个区域加锁后分次对该区域的不同部分解锁。

在MFC中对文件操作进行了封装,CFile中封装了各种文件操作。在CFile中常用的成员函数有以下这些:

CFile( LPCTSTR lpszFileName, UINT nOpenFlags ); //打开文件virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL ); //打开文件
uOpenFlags为打开文件时的参数,可以取的以下值的组合:
CFile::modeRead / CFile::modeReadWrite / CFile::modeWrite 读写模式
CFile::modeCreate 创建文件
CFile::shareDenyNone / CFile::shareDenyRead / CFile::shareDenyWrite 共享设置
CFile::typeText / CFile::typeBinary 以文本形式还时二进制形式打开文件virtual void Close( ); //关闭文件virtual UINT Read( void* lpBuf, UINT nCount ); //读文件virtual void Write( const void* lpBuf, UINT nCount ); // 写文件virtual LONG Seek( LONG lOff, UINT nFrom ); //设置文件指针void SeekToBegin( );//移动文件指针到文件头DWORD SeekToEnd( );//移动文件指针到文件尾virtual void LockRange( DWORD dwPos, DWORD dwCount ); //锁定文件virtual void UnlockRange( DWORD dwPos, DWORD dwCount ); //解锁文件

CStdioFile是CFile的派生类,主要是完成对文本文件的操作,它只有两个成员函数:

BOOL ReadString(CString& rString); //读入文件中一行void WriteString( LPCTSTR lpsz );//将字符串作为一行写入文件

转载于:https://www.cnblogs.com/k1988/archive/2009/12/01/2165697.html

磁盘文件的正常读写与异步读写相关推荐

  1. 【FPGA】双端口RAM的设计(异步读写)

    上篇写了双端口RAM设计(同步读写):https://blog.csdn.net/Reborn_Lee/article/details/90647784 关于异步读写和同步读写,在单端口RAM设计中也 ...

  2. linux下aio异步读写详解与实例

    1.为什么会有异步I/O aio异步读写是在linux内核2.6之后才正式纳入其标准.之所以会增加此模块,是因为众所周知我们计算机CPU的执行速度远大于I/O读写的执行速度,如果我们用传统的阻塞式或非 ...

  3. python mongodb 异步_Python异步读写Mongodb(motor+asyncio)

    使用Python做大型计算任务时,并且用mongodb做数据储存时,常常面临大量读写数据库的情况.尤其是大量更新任务,由于不能批量操作,使用pymongo同步操作的话,相当耗时. 使用多线程.多进程确 ...

  4. 用API实现串口异步读写

    VB的MSCOMM控件虽然很好用,但是在没有装VB的机器上用该控件总觉得有些累赘,网上的VB API代码大部分都基于是同步方式,处理复杂的通信模式不是太理想,所以用了一些时间,把VC项目中的异步串口读 ...

  5. boost::asio向socket中异步读写数据

    内容代码参考自: Boost.Asio C++ Network Programming Cookbook 异步写入数据的核心是异步回调函数. 在此之前, 必须弄明白异步IO的基本概念和回调函数触发的时 ...

  6. SpiFlash同步/异步读写单片机裸机实例

    单片机裸机开发中会经常遇到外设速度过慢,长时间读忙等待,但CPU又不能长时间阻塞的问题. 这种问题可以通过实现一个状态机来异步处理. 异步状态机代码结构示例: enum {eIDLE = 0,eSTA ...

  7. Java中mysql的读写分离_mysql读写分离

    MySQL读写分离原理 MySQL的主从复制和MySQL的读写分离两者有着紧密联系,首先部署主从复制,只有复制完了,才能在此基础上进行数据的读写分离. 读写分离就是只在主服务器上写,只在从服务器上读, ...

  8. python读写二进制文件(读写字节数据)

    python读写二进制文件(读写字节数据) 你想读写二进制文件,比如图片,声音文件等就是常见的二进制文件. 使用模式为 rb 或 wb 的 open() 函数来读取或写入二进制数据.比如: # Rea ...

  9. 随机顺序_SSD固态硬盘的顺序读写和随机读写区别,谁更重要?

    众所周知,固态硬盘性能主要是取决于顺序读写和随机读写,但是对于普通用户,肯定不了解顺序读写和随机读写的区别,其实两者无论是概念还是作用都存在着较大的差异,下面装机之家分享一下SSD固态硬盘的顺序读写和 ...

最新文章

  1. python利器怎么编程-python等自动化脚本编程利器 Script.NET
  2. linux compress参数,compress命令_Linux compress 命令用法详解:使用Lempress-Ziv编码压缩数据文件...
  3. javascript删除数组里的对象
  4. Jquery 多行拖拽图片排序 jq优化
  5. 理解 C++ 的 Memory Order 以及 atomic 与并发程序的关系
  6. 4K屏幕+5500万像素摄像头,以成未来手机的一大趋势
  7. JavaScript总结(四)
  8. 揭秘富人见不得光的第一桶金都是怎么来的
  9. 枪火游侠服务器停机维护,腾讯《枪火游侠》公布国服停运公告 11月30日正式关服...
  10. Linux Shell脚本 删除一个字符串中的部分字符
  11. ConceptDraw Office for mac(跨平台图表办公程序)
  12. mycelipse中关于编码的配置
  13. 谷歌归期未定,但敏感词库已经建起来了
  14. Unity获取系统信息SystemInfo(CPU、显卡、操作系统等信息)
  15. android 智能电视 电视盒子 安卓嵌入式硬件LAN压力测试
  16. 因安装搜狗输入法而引发的一系列惨案
  17. JACK——TeamsMaual6 Team Formation
  18. 前端复习之HTML5
  19. 如何在Google上下载高清原图
  20. 【PTA】谷歌的招聘(C语言)

热门文章

  1. 教你如何制作饼干icon教程
  2. 几秒之后自动关闭广告
  3. Javaoop_继承
  4. 今天早上买的这个猪蹄和花生辣条不是味道
  5. 6个特征,判断你的领导值不值得追随
  6. Simulink之器件换流式电压型无源逆变电路
  7. jquery 逗号分割截取字符串_Python中常用的8种字符串操作方法
  8. c++ sleep函数_《PHP扩展开发》-hook-(hook原来的sleep)
  9. 修改Yarn的全局安装和缓存位置
  10. 【升级包】jeecg_online 支持主子表列表展示风格模板升级包,简易升级