最近涉及到流的获取与转化,终于要还流的债了。
百度了一下,看到这样的两条回复,于是好奇心,决定看看两种写法的源码差异。
先来看看OpenRead()
public static FileStream OpenRead(string path) =>
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); //构造函数又调用了其他构造函数,继续深入
[SecuritySafeCritical]
public FileStream(string path, FileMode mode, FileAccess access, FileShare share) : this(path, mode, access, share, 0x1000, FileOptions.None, Path.GetFileName(path), false)
{
}
//调用了Init 初始化
[SecurityCritical]
internal FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, string msgPath, bool bFromProxy)
{
Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
this.Init(path, mode, access, , false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, false, false);
} //读取数据到流中,过程就没什么可以看的了
[SecuritySafeCritical]
private unsafe void Init(string path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, string msgPath, bool bFromProxy, bool useLongPath, bool checkHost)
{
int num;
if (path == null)
{
throw new ArgumentNullException("path", Environment.GetResourceString("ArgumentNull_Path"));
}
if (path.Length == )
{
throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"));
}
FileSystemRights rights2 = (FileSystemRights) rights;
this._fileName = msgPath;
this._exposedHandle = false;
FileShare share2 = share & ~FileShare.Inheritable;
string paramName = null;
if ((mode < FileMode.CreateNew) || (mode > FileMode.Append))
{
paramName = "mode";
}
else if (!useRights && ((access < FileAccess.Read) || (access > FileAccess.ReadWrite)))
{
paramName = "access";
}
else if (useRights && ((rights2 < FileSystemRights.ListDirectory) || (rights2 > FileSystemRights.FullControl)))
{
paramName = "rights";
}
else if ((share2 < FileShare.None) || (share2 > (FileShare.Delete | FileShare.ReadWrite)))
{
paramName = "share";
}
if (paramName != null)
{
throw new ArgumentOutOfRangeException(paramName, Environment.GetResourceString("ArgumentOutOfRange_Enum"));
}
if ((options != FileOptions.None) && ((options & 0x3ffbfff) != FileOptions.None))
{
throw new ArgumentOutOfRangeException("options", Environment.GetResourceString("ArgumentOutOfRange_Enum"));
}
if (bufferSize <= )
{
throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
}
if (((!useRights && ((access & FileAccess.Write) == )) || (useRights && ((rights2 & FileSystemRights.Write) == ))) && (((mode == FileMode.Truncate) || (mode == FileMode.CreateNew)) || ((mode == FileMode.Create) || (mode == FileMode.Append))))
{
if (!useRights)
{
object[] objArray1 = new object[] { mode, access };
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&AccessCombo", objArray1));
}
object[] values = new object[] { mode, rights2 };
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&RightsCombo", values));
}
if (useRights && (mode == FileMode.Truncate))
{
if (rights2 != FileSystemRights.Write)
{
object[] objArray3 = new object[] { mode, rights2 };
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileModeTruncate&RightsCombo", objArray3));
}
useRights = false;
access = FileAccess.Write;
}
if (!useRights)
{
num = (access == FileAccess.Read) ? - : ((access == FileAccess.Write) ? 0x40000000 : -);
}
else
{
num = rights;
}
int maxPathLength = useLongPath ? 0x7fff : (AppContextSwitches.BlockLongPaths ? : 0x7fff);
string fullPath = Path.NormalizePath(path, true, maxPathLength);
this._fileName = fullPath;
if ((!CodeAccessSecurityEngine.QuickCheckForAllDemands() || AppContextSwitches.UseLegacyPathHandling) && fullPath.StartsWith(@"\\.\", StringComparison.Ordinal))
{
throw new ArgumentException(Environment.GetResourceString("Arg_DevicesNotSupported"));
}
bool flag = false;
if ((!useRights && ((access & FileAccess.Read) != )) || (useRights && ((rights2 & FileSystemRights.ReadAndExecute) != )))
{
if (mode == FileMode.Append)
{
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidAppendMode"));
}
flag = true;
}
if (CodeAccessSecurityEngine.QuickCheckForAllDemands())
{
FileIOPermission.EmulateFileIOPermissionChecks(fullPath);
}
else
{
FileIOPermissionAccess noAccess = FileIOPermissionAccess.NoAccess;
if (flag)
{
noAccess |= FileIOPermissionAccess.Read;
}
if (((!useRights && ((access & FileAccess.Write) != )) || (useRights && ((rights2 & (FileSystemRights.TakeOwnership | FileSystemRights.ChangePermissions | FileSystemRights.Delete | FileSystemRights.Write | FileSystemRights.DeleteSubdirectoriesAndFiles)) != ))) || ((useRights && ((rights2 & FileSystemRights.Synchronize) != )) && (mode == FileMode.OpenOrCreate)))
{
if (mode == FileMode.Append)
{
noAccess |= FileIOPermissionAccess.Append;
}
else
{
noAccess |= FileIOPermissionAccess.Write;
}
}
AccessControlActions control = ((secAttrs != null) && (secAttrs.pSecurityDescriptor != null)) ? AccessControlActions.Change : AccessControlActions.None;
string[] fullPathList = new string[] { fullPath };
FileIOPermission.QuickDemand(noAccess, control, fullPathList, false, false);
}
share &= ~FileShare.Inheritable;
bool flag2 = mode == FileMode.Append;
if (mode == FileMode.Append)
{
mode = FileMode.OpenOrCreate;
}
if ((options & FileOptions.Asynchronous) != FileOptions.None)
{
this._isAsync = true;
}
else
{
options &= ~FileOptions.Asynchronous;
}
int dwFlagsAndAttributes = (int) options;
dwFlagsAndAttributes |= 0x100000;
int newMode = Win32Native.SetErrorMode();
try
{
string str3 = fullPath;
if (useLongPath)
{
str3 = Path.AddLongPathPrefix(str3);
}
this._handle = Win32Native.SafeCreateFile(str3, num, share, secAttrs, mode, dwFlagsAndAttributes, IntPtr.Zero);
if (this._handle.IsInvalid)
{
int errorCode = Marshal.GetLastWin32Error();
if ((errorCode == ) && fullPath.Equals(Directory.InternalGetDirectoryRoot(fullPath)))
{
errorCode = ;
}
bool flag4 = false;
if (!bFromProxy)
{
try
{
FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, this._fileName, false, false);
flag4 = true;
}
catch (SecurityException)
{
}
}
if (flag4)
{
__Error.WinIOError(errorCode, this._fileName);
}
else
{
__Error.WinIOError(errorCode, msgPath);
}
}
}
finally
{
Win32Native.SetErrorMode(newMode);
}
if (Win32Native.GetFileType(this._handle) != )
{
this._handle.Close();
throw new NotSupportedException(Environment.GetResourceString("NotSupported_FileStreamOnNonFiles"));
}
if (this._isAsync)
{
bool flag5 = false;
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
try
{
flag5 = ThreadPool.BindHandle(this._handle);
}
finally
{
CodeAccessPermission.RevertAssert();
if (!flag5)
{
this._handle.Close();
}
}
if (!flag5)
{
throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed"));
}
}
if (!useRights)
{
this._canRead = (access & FileAccess.Read) > ;
this._canWrite = (access & FileAccess.Write) > ;
}
else
{
this._canRead = (rights2 & FileSystemRights.ListDirectory) > ;
this._canWrite = ((rights2 & FileSystemRights.CreateFiles) != ) || ((rights2 & FileSystemRights.AppendData) > );
}
this._canSeek = true;
this._isPipe = false;
this._pos = 0L;
this._bufferSize = bufferSize;
this._readPos = ;
this._readLen = ;
this._writePos = ;
if (flag2)
{
this._appendStart = this.SeekCore(0L, SeekOrigin.End);
}
else
{
this._appendStart = -1L;
}
}
从上面的源码可以看出,OpenRead会把无论多大的文件都读取到FileStream中,能有多大呢?FileStream.Length可是long型的,就是2的63次方了,2的40次方是1T,大约就是8MT吧,我们的常用硬盘是没这么大的了,可以说能读出天荒地老了。那么图片中的第一中写法,读取没错,但是转换int就留下了隐患,int的最大限制是2G,如果文件超过2G,那么转换就会被截取,导致读到data里的数据就不完整了。所以一定不要偷懒,由于FileStream.Read的参数是int型的,一定要判断流的长度,如果超过了int最大值,要循环read。
再来看看File.ReadAllBytes(),上源码:
[SecurityCritical]
private static byte[] InternalReadAllBytes(string path, bool checkHost)
{
byte[] buffer;
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, FileOptions.None, Path.GetFileName(path), false, false, checkHost))//这里也是和OpenRead一样,把文件全部读取进来了
{
int offset = ;
long length = stream.Length;
if (length > 0x7fffffffL)//这里才是关键,对长度做了一个判断,如果超过了2G,就抛异常了,所以决定了这个方法的文件不能超过2G
{
throw new IOException(Environment.GetResourceString("IO.IO_FileTooLong2GB"));
}
int count = (int) length;
buffer = new byte[count];
while (count > )
{
int num4 = stream.Read(buffer, offset, count);
if (num4 == )
{
__Error.EndOfFile();
}
offset += num4;
count -= num4;
}
}
return buffer;
}
第二种写法会导致在读取文件超过2G后抛出异常,所以使用时要确定文件的大小,否则就用OpenRead()。
所以图中两种写法都可能存在隐患的BUG。
当然,在确定了不可能超过2G的情况下,还是可以*使用的。