C#直接读取磁盘文件(类似linux的Direct IO模式)

简介:

由于项目需要测试windows下的IO性能,因此要写个小程序,按照要求读取磁盘上的文件。在读取文件的时候,测试Windows的IO性能。

主要内容:

  1. 程序的要求
  2. 一般的FileStream方式
  3. 利用kernel32.dll中的CreateFile函数

1. 程序的要求

程序的要求很简单。

(1)命令行程序

(2)有3个参数,读取的文件名,一次读取buffer size,读取的次数count

(3)如果读取次数count未到,文件已经读完,就再次从头读取文件。

使用格式如下:

C:\>****.exe “c:\****.bin” 32768 32768

读取文件“c:\****.bin”,每次读取4K,读取32768次,读取的量大概1G。

 

2. 一般的FileStream方式

利用FileStream来读取文件,非常简单,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using  System;
using  System.Collections.Generic;
using  System.Text;
using  System.IO;
using  System.Reflection;
 
namespace  DirectIO
{
     public  class  DIOReader
     {
         static  void  Main( string [] args)
         {
             long  start = DateTime.Now.Ticks;
 
             if  (args.Length < 3)
             {
                 Console.WriteLine( "parameter error!!" );
                 return ;
             }
             FileStream input = null ;
 
             try
             {
                 int  bs = Convert.ToInt32(args[1]);
                 int  count = Convert.ToInt32(args[2]);
                 input = new  FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, bs);
 
                 byte [] b = new  byte [bs];
                 for  ( int  i = 0; i < count; i++)
                 {
                     if  (input.Read(b, 0, bs) == 0)
                         input.Seek(0, SeekOrigin.Begin);
                 }
                 Console.WriteLine( "Read successed! " );
                 Console.WriteLine(DateTime.Now.Ticks - start);
             }
             catch  (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }
             finally
             {
                 if  (input != null )
                 {
                     input.Flush();
                     input.Close();
                     // 清除使用的对象
                     GC.Collect();
                     GC.Collect();
                 }
             }
         }
     }
}

编译后的exe文件可以按照既定要求执行,但是对于同一文件,第二次读取明显比第一次快很多(大家可以用个1G左右的大文件试试)。第三次读取,第四次读取……和第二次差不多,都很快。

基于上述情况,可以判断是缓存的原因,导致第二次及以后各次都比较快。

但是从代码中来看,已经执行了input.Flush();input.Close();甚至是GC.Collect();

所以可能是Windows系统或者CLR对文件读取操作进行了优化,使用了缓存。

 

3. 利用kernel32.dll中的CreateFile函数

既然上述方法行不通,就得调查新的方法。通过google的查询,大部分人都是建议用C/C++调用系统API来实现。

不过最后终于找到了用c#实现了无缓存直接读取磁盘上的文件的方法。其实也是通过DllImport利用了kernel32.dll,不完全是托管代码。(估计用纯托管代码实现不了)

参考的文章:How do I read a disk directly with .Net?

还有msdn中的CreateFile API

实现代码就是参考的How do I read a disk directly with .Net?,分为两部分

(1)利用CreateFile API构造的可直接读取磁盘的DeviceStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using  System;
using  System.Runtime.InteropServices;
using  System.IO;
using  Microsoft.Win32.SafeHandles;
 
namespace  DirectIO
{
     public  class  DeviceStream : Stream, IDisposable
     {
         public  const  short  FILE_ATTRIBUTE_NORMAL = 0x80;
         public  const  short  INVALID_HANDLE_VALUE = -1;
         public  const  uint  GENERIC_READ = 0x80000000;
         public  const  uint  NO_BUFFERING = 0x20000000;
         public  const  uint  GENERIC_WRITE = 0x40000000;
         public  const  uint  CREATE_NEW = 1;
         public  const  uint  CREATE_ALWAYS = 2;
         public  const  uint  OPEN_EXISTING = 3;
 
         // Use interop to call the CreateFile function.
         // For more information about CreateFile,
         // see the unmanaged MSDN reference library.
         [DllImport( "kernel32.dll" , SetLastError = true , CharSet = CharSet.Unicode)]
         private  static  extern  IntPtr CreateFile( string  lpFileName, uint  dwDesiredAccess,
           uint  dwShareMode, IntPtr lpSecurityAttributes, uint  dwCreationDisposition,
           uint  dwFlagsAndAttributes, IntPtr hTemplateFile);
 
         [DllImport( "kernel32.dll" , SetLastError = true )]
         private  static  extern  bool  ReadFile(
             IntPtr hFile,                        // handle to file
             byte [] lpBuffer,                // data buffer
             int  nNumberOfBytesToRead,        // number of bytes to read
             ref  int  lpNumberOfBytesRead,    // number of bytes read
             IntPtr lpOverlapped
             //
             // ref OVERLAPPED lpOverlapped        // overlapped buffer
             );
 
         private  SafeFileHandle handleValue = null ;
         private  FileStream _fs = null ;
 
         public  DeviceStream( string  device)
         {
             Load(device);
         }
 
         private  void  Load( string  Path)
         {
             if  ( string .IsNullOrEmpty(Path))
             {
                 throw  new  ArgumentNullException( "Path" );
             }
 
             // Try to open the file.
             IntPtr ptr = CreateFile(Path, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, NO_BUFFERING, IntPtr.Zero);
 
             handleValue = new  SafeFileHandle(ptr, true );
             _fs = new  FileStream(handleValue, FileAccess.Read);
 
             // If the handle is invalid,
             // get the last Win32 error
             // and throw a Win32Exception.
             if  (handleValue.IsInvalid)
             {
                 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
             }
         }
 
         public  override  bool  CanRead
         {
             get  { return  true ; }
         }
 
         public  override  bool  CanSeek
         {
             get  { return  false ; }
         }
 
         public  override  bool  CanWrite
         {
             get  { return  false ; }
         }
 
         public  override  void  Flush()
         {
             return ;
         }
 
         public  override  long  Length
         {
             get  { return  -1; }
         }
 
         public  override  long  Position
         {
             get
             {
                 throw  new  NotImplementedException();
             }
             set
             {
                 throw  new  NotImplementedException();
             }
         }
         /// <summary>
         /// </summary>
         /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and
         /// (offset + count - 1) replaced by the bytes read from the current source. </param>
         /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream. </param>
         /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
         /// <returns></returns>
         public  override  int  Read( byte [] buffer, int  offset, int  count)
         {
             int  BytesRead = 0;
             var  BufBytes = new  byte [count];
             if  (!ReadFile(handleValue.DangerousGetHandle(), BufBytes, count, ref  BytesRead, IntPtr.Zero))
             {
                 Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
             }
             for  ( int  i = 0; i < BytesRead; i++)
             {
                 buffer[offset + i] = BufBytes[i];
             }
             return  BytesRead;
         }
         public  override  int  ReadByte()
         {
             int  BytesRead = 0;
             var  lpBuffer = new  byte [1];
             if  (!ReadFile(
             handleValue.DangerousGetHandle(),                        // handle to file
             lpBuffer,                // data buffer
             1,        // number of bytes to read
             ref  BytesRead,    // number of bytes read
             IntPtr.Zero
             ))
             { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); ;}
             return  lpBuffer[0];
         }
 
         public  override  long  Seek( long  offset, SeekOrigin origin)
         {
             throw  new  NotImplementedException();
         }
 
         public  override  void  SetLength( long  value)
         {
             throw  new  NotImplementedException();
         }
 
         public  override  void  Write( byte [] buffer, int  offset, int  count)
         {
             throw  new  NotImplementedException();
         }
 
         public  override  void  Close()
         {
             handleValue.Close();
             handleValue.Dispose();
             handleValue = null ;
             base .Close();
         }
         private  bool  disposed = false ;
 
         new  void  Dispose()
         {
             Dispose( true );
             base .Dispose();
             GC.SuppressFinalize( this );
         }
 
         private  new  void  Dispose( bool  disposing)
         {
             // Check to see if Dispose has already been called.
             if  (! this .disposed)
             {
                 if  (disposing)
                 {
                     if  (handleValue != null )
                     {
                         _fs.Dispose();
                         handleValue.Close();
                         handleValue.Dispose();
                         handleValue = null ;
                     }
                 }
                 // Note disposing has been done.
                 disposed = true ;
 
             }
         }
 
     }
}

注意和原文相比,改动了一个地方。即加了个NO_BUFFERING的参数,并在调用CreateFile时使用了这个参数。

1
IntPtr ptr = CreateFile(Path, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, NO_BUFFERING, IntPtr.Zero);

 

之前没有加这个参数的时候,在xp上测试还是第二次比第一次快很多。

 

(2)完成指定要求的DIOReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using  System;
using  System.Collections.Generic;
using  System.Text;
using  System.IO;
using  System.Reflection;
 
namespace  DirectIO
{
     public  class  DIOReader
     {
         static  void  Main( string [] args)
         {
             long  start = DateTime.Now.Ticks;
 
             if  (args.Length < 3)
             {
                 Console.WriteLine( "parameter error!!" );
                 return ;
             }
             BinaryReader input = null ;
 
             try
             {
                 int  bs = Convert.ToInt32(args[1]);
                 int  count = Convert.ToInt32(args[2]);
                 input = new  BinaryReader( new  DeviceStream(args[0]));
 
                 byte [] b = new  byte [bs];
                 for  ( int  i = 0; i < count; i++)
                 {
                     if  (input.Read(b, 0, bs) == 0)
                         input.BaseStream.Seek(0, SeekOrigin.Begin);
                 }
                 Console.WriteLine( "Read successed! " );
                 Console.WriteLine( "Total cost "  + ( new  TimeSpan(DateTime.Now.Ticks - start)).TotalSeconds + " seconds" );
             }
             catch  (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }
             finally
             {
                 if  (input != null )
                 {
                     input.Close();
                 }
                 //Console.ReadKey(true);
             }
         }
     }
}

 

这样,就完成了类似linux上Direct IO模式读取文件的操作。

通过这个例子可以看出,C#不仅可以开发上层的应用,也可以结合一些非托管的dll完成更加底层的操作。



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/09/06/2168833.html,如需转载请自行联系原作者


目录
相关文章
|
28天前
|
Linux 数据安全/隐私保护 Windows
命令方式:window向linux传文件
【10月更文挑战第6天】本文介绍了如何在Linux系统中通过命令`ip a`获取IP地址,并在Windows系统下使用CMD命令行工具和SCP命令实现文件传输。示例展示了如何将D盘中的`mm.jar`文件上传至IP地址为192.168.163.122的Linux系统的/up/目录下,最后在Linux系统中确认文件传输结果。
223 65
|
16天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
115 5
|
16天前
|
Linux Shell 数据库
Linux文件查找新姿势:总有一种你没见过
【10月更文挑战第18天】文件查找是Linux用户提升工作效率的重要技能。本文介绍了几种实用的文件查找方法,包括基础的`find`命令、快速的`locate`和`mlocate`、高效的`fd`工具、以及结合`grep`和`rg`进行内容搜索。此外,还提供了编写Shell脚本和使用图形界面工具的建议,帮助你更灵活地管理文件。
56 3
|
5天前
|
网络协议 Linux
linux系统重要文件目录
本文介绍了Linux系统中的重要目录及其历史背景,包括根目录、/usr、/etc、/var/log和/proc等目录的结构和功能。其中,/etc目录下包含了许多关键配置文件,如网卡配置、DNS解析、主机名设置等。文章还详细解释了各目录和文件的作用,帮助读者更好地理解和管理Linux系统。
19 2
|
4天前
|
缓存 监控 Linux
|
27天前
|
Linux 开发工具 数据安全/隐私保护
linux异常一:feng 不在 sudoers 文件中,此事将被报告。yum提示Another app is currently holding the yum lock; waiting for
这篇文章介绍了在CentOS 7系统中安装Docker时遇到的两个常见问题及其解决方法:用户不在sudoers文件中导致权限不足,以及yum被锁定的问题。
36 2
linux异常一:feng 不在 sudoers 文件中,此事将被报告。yum提示Another app is currently holding the yum lock; waiting for
|
7天前
|
Linux Shell 数据库
文件查找是Linux用户日常工作的重要技能介绍了几种不常见的文件查找方法
文件查找是Linux用户日常工作的重要技能。本文介绍了几种不常见的文件查找方法,包括使用`find`和`column`组合、`locate`和`mlocate`快速查找、编写Shell脚本、使用现代工具`fd`、结合`grep`搜索文件内容,以及图形界面工具如`Gnome Search Tool`和`Albert`。这些方法能显著提升文件查找的效率和准确性。
28 2
|
10天前
|
Linux 数据库
linux 全局搜索文件
在 Linux 系统中,全局搜索文件常用 `find`、`locate` 和 `grep` 命令。`find` 根据文件名、类型、大小、时间戳等条件搜索;`locate` 通过预构建的数据库快速查找文件;`grep` 在文件中搜索特定文本,常与 `find` 结合使用。选择合适的命令取决于具体需求。
|
14天前
|
Linux 开发工具 Perl
Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
【10月更文挑战第20天】Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
30 4
|
13天前
|
运维 安全 Linux
Linux文件清空的五种方法总结分享
每种方法各有优势,选择最合适的一种或几种,可以极大提高您的工作效率。更多有关Linux系统管理的技巧与资源,欢迎访问,持续提升您的运维技能。
60 1