.NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)

.NET 中文件打开的 API File.Open 提供了多种不同的文件打开方式,这些方式大多数与 Windows 文件 API 中的模式是对应的,但也有一些 .NET 层面的判断以及名称的变化。在 .NET 层你可以选择适合你业务场景需要的文件打开方式。


文件打开方式

文件打开的多个重载方法中,除了封装好的 OpenRead / OpenWrite 之外,其他都是需要指定 FileMode 参数的。

public static FileStream Open(string path, FileMode mode);

FileMode

FileMode 枚举有 6 种不同的值:

public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

为了方便查阅,我先将大家可能关心的内容做一个表格:

FileMode 如果文件存在 如果文件不存在
CreateNew IOException 新建
Create 截断 新建
Open 打开 FileNotFoundException
OpenOrCreate 打开 新建
Truncate 截断 FileNotFoundException
Append 追加 新建

所有这些打开模式都不会修改到文件的属性(Attribute),包括创建时间、针对用户的权限设置。所以如果你希望连这些属性都不需要,而是完完全全创建新的文件,那么请先将原来的文件删除。

注意,在 File.Open 方法中传入以下这些参数的含义描述中可能有一些包含过程和判断的语句,但实际上这些真正的判断和过程发生在 Windows 内核(虽然 .NET 也有一些判断,但是一些参数预判断和参数转换),所以实际拿到文件流(对应 Win32 中拿到句柄)是一个原子操作,不会因为中间加了判断导致与其他线程发生竞争。

CreateNew

如果文件不存在,则创建一个新的文件并返回新文件的文件流。如果文件已经存在,则抛出 IOException

Create

如果文件不存在,则创建一个新的文件并返回新文件的文件流。如果文件已经存在,则打开文件并返回此文件的文件流。

基于此文件流的修改会完全复写文件。也就是说,如果原文件内容是 walterlv,通过此文件流写入 111,那么最终文件内容是 111

Open

如果文件存在,则打开文件并返回此文件的文件流。如果文件不存在,则抛出 FileNotFoundException

基于此文件流的修改不会截断文件。也就是说,如果原文件内容是 walterlv,通过此文件流写入 111,那么最终文件内容是 111terlv

OpenOrCreate

如果文件存在,则打开文件并返回此文件的文件流。如果文件不存在,则创建一个文件并返回新文件的文件流。

基于此文件流的修改不会截断文件。也就是说,如果原文件内容是 walterlv,通过此文件流写入 111,那么最终文件内容是 111terlv

Truncate

如果文件存在,则打开后文件的长度直接变为 0,随后返回此文件的文件流。如果文件不存在,则会抛出 FileNotFoundException

由于在打开文件时就已经将文件设置为 0 字节,所以对应到上面截断的描述是一定会截断的。写入任何新内容到文件候,文件中都不会存在旧文件中的内容。

Append

如果文件不存在,则创建一个新的文件并返回新文件的文件流。如果文件已经存在,则创建一个可以往文件的结尾处开始写的文件流。

如果试图从文件流中往前倒推找到此前的文件内容,会抛出 IOException

配合文件打开权限

在以上这些 FileMode 中,CreateNewCreateTruncateAppend 都是需要写文件的权限的,OpenOrCreate 是否需要写权限则取决于文件是否存在。

附源码

以下是 FileStream 中的 Open 方法最终调用处。可以发现,此方法将传入的 FileMode 转换成了 Win32 中的值,并且最终调用了 Windows API CreateFile

你可以阅读我的另一篇博客了解 Win32 API 中的 CreateFile

private unsafe SafeFileHandle CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
{
    Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);

    int fAccess =
        ((_access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) |
        ((_access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0);

    // Our Inheritable bit was stolen from Windows, but should be set in
    // the security attributes class.  Don't leave this bit set.
    share &= ~FileShare.Inheritable;

    // Must use a valid Win32 constant here...
    if (mode == FileMode.Append)
        mode = FileMode.OpenOrCreate;

    int flagsAndAttributes = (int)options;

    // For mitigating local elevation of privilege attack through named pipes
    // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
    // named pipe server can't impersonate a high privileged client security context
    // (note that this is the effective default on CreateFile2)
    flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);

    using (DisableMediaInsertionPrompt.Create())
    {
        Debug.Assert(_path != null);
        return ValidateFileHandle(
            Interop.Kernel32.CreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero));
    }
}
// Contains constants for specifying how the OS should open a file.
// These will control whether you overwrite a file, open an existing
// file, or some combination thereof.
//
// To append to a file, use Append (which maps to OpenOrCreate then we seek
// to the end of the file).  To truncate a file or create it if it doesn't
// exist, use Create.
//
public enum FileMode
{
    // Creates a new file. An exception is raised if the file already exists.
    CreateNew = 1,

    // Creates a new file. If the file already exists, it is overwritten.
    Create = 2,

    // Opens an existing file. An exception is raised if the file does not exist.
    Open = 3,

    // Opens the file if it exists. Otherwise, creates a new file.
    OpenOrCreate = 4,

    // Opens an existing file. Once opened, the file is truncated so that its
    // size is zero bytes. The calling process must open the file with at least
    // WRITE access. An exception is raised if the file does not exist.
    Truncate = 5,

    // Opens the file if it exists and seeks to the end.  Otherwise,
    // creates a new file.
    Append = 6,
}

参考资料

blog footer

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/dotnet-file-open-modes.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,或者前往 CSDN 关注我的主页

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)