最近涉及到流的获取与转化,终于要还流的债了。
百度了一下,看到这样的两条回复,于是好奇心,决定看看两种写法的源码差异。
先来看看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, 0, 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 == 0) { 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 <= 0) { throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); } if (((!useRights && ((access & FileAccess.Write) == 0)) || (useRights && ((rights2 & FileSystemRights.Write) == 0))) && (((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) ? -2147483648 : ((access == FileAccess.Write) ? 0x40000000 : -1073741824); } else { num = rights; } int maxPathLength = useLongPath ? 0x7fff : (AppContextSwitches.BlockLongPaths ? 260 : 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) != 0)) || (useRights && ((rights2 & FileSystemRights.ReadAndExecute) != 0))) { 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) != 0)) || (useRights && ((rights2 & (FileSystemRights.TakeOwnership | FileSystemRights.ChangePermissions | FileSystemRights.Delete | FileSystemRights.Write | FileSystemRights.DeleteSubdirectoriesAndFiles)) != 0))) || ((useRights && ((rights2 & FileSystemRights.Synchronize) != 0)) && (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(1); 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 == 3) && fullPath.Equals(Directory.InternalGetDirectoryRoot(fullPath))) { errorCode = 5; } 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) != 1) { 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) > 0; this._canWrite = (access & FileAccess.Write) > 0; } else { this._canRead = (rights2 & FileSystemRights.ListDirectory) > 0; this._canWrite = ((rights2 & FileSystemRights.CreateFiles) != 0) || ((rights2 & FileSystemRights.AppendData) > 0); } this._canSeek = true; this._isPipe = false; this._pos = 0L; this._bufferSize = bufferSize; this._readPos = 0; this._readLen = 0; this._writePos = 0; 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 = 0; 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 > 0) { int num4 = stream.Read(buffer, offset, count); if (num4 == 0) { __Error.EndOfFile(); } offset += num4; count -= num4; } } return buffer; }
第二种写法会导致在读取文件超过2G后抛出异常,所以使用时要确定文件的大小,否则就用OpenRead()。
所以图中两种写法都可能存在隐患的BUG。
当然,在确定了不可能超过2G的情况下,还是可以*使用的。