Windows 系统中的文件路径格式File path formats on Windows systems

System.IO 命名空间中很多类型的成员都包括 path 参数,让你可以指定指向某个文件系统资源的绝对路径或相对路径。Members of many of the types in the System.IO namespace include a path parameter that lets you specify an absolute or relative path to a file system resource. 此路径随后会传递至 Windows 文件系统 APIThis path is then passed to Windows file system APIs. 本主题讨论可在 Windows 系统上使用的文件路径格式。This topic discusses the formats for file paths that you can use on Windows systems.

传统 DOS 路径Traditional DOS paths

标准的 DOS 路径可由以下三部分组成:A standard DOS path can consist of three components:

  • 卷号或驱动器号,后跟卷分隔符 (:)。A volume or drive letter followed by the volume separator (:).
  • 目录名称。A directory name. 目录分隔符用来分隔嵌套目录层次结构中的子目录。The directory separator character separates subdirectories within the nested directory hierarchy.
  • 可选的文件名。An optional filename. 目录分隔符用来分隔文件路径和文件名。The directory separator character separates the file path and the filename.

如果以上三项都存在,则为绝对路径。If all three components are present, the path is absolute. 如未指定卷号或驱动器号,且目录名称的开头是目录分隔符,则路径属于当前驱动器根路径上的相对路径。If no volume or drive letter is specified and the directory name begins with the directory separator character, the path is relative from the root of the current drive. 否则路径相对于当前目录。Otherwise, the path is relative to the current directory. 下表显示了一些可能出现的目录和文件路径。The following table shows some possible directory and file paths.

路径Path 描述Description
C:\Documents\Newsletters\Summer2018.pdf C: 盘根路径上的绝对文件路径。An absolute file path from the root of drive C:
\Program Files\Custom Utilities\StringFinder.exe 当前驱动器根路径上的绝对路径。An absolute path from the root of the current drive.
2018\January.xlsx 指向当前目录的子目录中的文件的相对路径。A relative path to a file in a subdirectory of the current directory.
..\Publications\TravelBrochure.pdf 指向当前目录的同级目录中的文件的相对路径。A relative path to file in a directory that is a peer of the current directory.
C:\Projects\apilibrary\apilibrary.sln 指向 C: 盘根路径中的文件的绝对路径。An absolute path to a file from the root of drive C:
C:Projects\apilibrary\apilibrary.sln C: 盘当前目录上的相对路径。A relative path from the current directory of the C: drive.

重要

请注意最后两个路径之间的差异。Note the difference between the last two paths. 两者都指定了可选的卷说明符(均为“C:”),但前者以所指定卷的根开头,而后者不是。Both specify the optional volume specifier (C: in both cases), but the first begins with the root of the specified volume, whereas the second does not. 结果,前者表示 C: 盘根目录上的绝对路径,而后者表示 C: 盘当前目录上的相对路径。As result, the first is an absolute path from the root directory of drive C:, whereas the second is a relative path from the current directory of drive C:. 应使用前者时使用了后者是涉及 Windows 文件路径的 bug 的常见原因。Use of the second form when the first is intended is a common source of bugs that involve Windows file paths.

可以通过调用 IsPathFullyQualified 方法来确定文件路径是否完全限定(即是说,该路径独立于当前目录,且在当前目录更改时不发生变化)。You can determine whether a file path is fully qualified (that is, it the path is independent of the current directory and does not change when the current directory changes) by calling the IsPathFullyQualified method. 请注意,如果解析的路径始终指向同样的位置,那么此类路径可以包括相对目录段(...),而同时依然是完全限定的。Note that such a path can include relative directory segments (. and ..) and still be fully qualified if the resolved path always points to the same location.

以下示例演示绝对路径和相对路径之间的差异。The following example illustrates the difference between absolute and relative paths. 假定存在目录 D:\FY2018\,且在运行该示例之前还没有通过命令提示符为 D:\ 设置任何当前目录。It assumes that the directory D:\FY2018\ exists, and that you haven't set any current directory for D:\ from the command prompt before running the example.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

public class Example
{
   public static void Main(string[] args)
   {
      Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'");
      Console.WriteLine("Setting current directory to 'C:\\'");
      
      Directory.SetCurrentDirectory(@"C:\");
      string path = Path.GetFullPath(@"D:\FY2018");
      Console.WriteLine($"'D:\\FY2018' resolves to {path}");
      path = Path.GetFullPath(@"D:FY2018");
      Console.WriteLine($"'D:FY2018' resolves to {path}");

      Console.WriteLine("Setting current directory to 'D:\\Docs'");
      Directory.SetCurrentDirectory(@"D:\Docs");

      path = Path.GetFullPath(@"D:\FY2018");
      Console.WriteLine($"'D:\\FY2018' resolves to {path}");
      path = Path.GetFullPath(@"D:FY2018");

      // This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
      Console.WriteLine($"'D:FY2018' resolves to {path}");

      Console.WriteLine("Setting current directory to 'C:\\'");
      Directory.SetCurrentDirectory(@"C:\");

      path = Path.GetFullPath(@"D:\FY2018");
      Console.WriteLine($"'D:\\FY2018' resolves to {path}");

      // This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
      // the command prompt set the current directory before launch of our application, which
      // sets a hidden environment variable that is considered.
      path = Path.GetFullPath(@"D:FY2018");
      Console.WriteLine($"'D:FY2018' resolves to {path}");

      if (args.Length < 1)
      {
         Console.WriteLine(@"Launching again, after setting current directory to D:\FY2018");
         Uri currentExe = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute);
         string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
         ProcessStartInfo psi = new ProcessStartInfo("cmd", commandLine); ;
         Process.Start(psi).WaitForExit();

         Console.WriteLine("Sub process returned:");
         path = Path.GetFullPath(@"D:\FY2018");
         Console.WriteLine($"'D:\\FY2018' resolves to {path}");
         path = Path.GetFullPath(@"D:FY2018");
         Console.WriteLine($"'D:FY2018' resolves to {path}");
      }
      Console.WriteLine("Press any key to continue... ");
      Console.ReadKey();
   }
}
// The example displays the following output:
//      Current directory is 'C:\Programs\file-paths'
//      Setting current directory to 'C:\'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to d:\FY2018
//      Setting current directory to 'D:\Docs'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to D:\Docs\FY2018
//      Setting current directory to 'C:\'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to d:\FY2018
//      Launching again, after setting current directory to D:\FY2018
//      Sub process returned:
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to d:\FY2018
// The subprocess displays the following output:
//      Current directory is 'C:\'
//      Setting current directory to 'C:\'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to D:\FY2018\FY2018
//      Setting current directory to 'D:\Docs'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to D:\Docs\FY2018
//      Setting current directory to 'C:\'
//      'D:\FY2018' resolves to D:\FY2018
//      'D:FY2018' resolves to D:\FY2018\FY2018
Imports System.Diagnostics
Imports System.IO
Imports System.Reflection

Public Module Example

   Public Sub Main(args() As String)
      Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'")
      Console.WriteLine("Setting current directory to 'C:\'")
      Directory.SetCurrentDirectory("C:\")
 
      Dim filePath As String = Path.GetFullPath("D:\FY2018")
      Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
      filePath = Path.GetFullPath("D:FY2018")
      Console.WriteLine($"'D:FY2018' resolves to {filePath}")

      Console.WriteLine("Setting current directory to 'D:\\Docs'")
      Directory.SetCurrentDirectory("D:\Docs")

      filePath = Path.GetFullPath("D:\FY2018")
      Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
      filePath = Path.GetFullPath("D:FY2018")

      ' This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
      Console.WriteLine($"'D:FY2018' resolves to {filePath}")

      Console.WriteLine("Setting current directory to 'C:\\'")
      Directory.SetCurrentDirectory("C:\")

      filePath = Path.GetFullPath("D:\FY2018")
      Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")

      ' This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
      ' the command prompt set the current directory before launch of our application, which
      ' sets a hidden environment variable that is considered.
      filePath = Path.GetFullPath("D:FY2018")
      Console.WriteLine($"'D:FY2018' resolves to {filePath}")

      If args.Length < 1 Then
         Console.WriteLine("Launching again, after setting current directory to D:\FY2018")
         Dim currentExe As New Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute)
         Dim commandLine As String = $"/C cd D:\FY2018 & ""{currentExe.LocalPath}"" stop"
         Dim psi As New ProcessStartInfo("cmd", commandLine) 
         Process.Start(psi).WaitForExit()

         Console.WriteLine("Sub process returned:")
         filePath = Path.GetFullPath("D:\FY2018")
         Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
         filePath = Path.GetFullPath("D:FY2018")
         Console.WriteLine($"'D:FY2018' resolves to {filePath}")
      End If
      Console.WriteLine("Press any key to continue... ")
      Console.ReadKey()
   End Sub
End Module
' The example displays the following output:
'      Current directory is 'C:\Programs\file-paths'
'      Setting current directory to 'C:\'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to d:\FY2018
'      Setting current directory to 'D:\Docs'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to D:\Docs\FY2018
'      Setting current directory to 'C:\'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to d:\FY2018
'      Launching again, after setting current directory to D:\FY2018
'      Sub process returned:
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to d:\FY2018
' The subprocess displays the following output:
'      Current directory is 'C:\'
'      Setting current directory to 'C:\'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to D:\FY2018\FY2018
'      Setting current directory to 'D:\Docs'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to D:\Docs\FY2018
'      Setting current directory to 'C:\'
'      'D:\FY2018' resolves to D:\FY2018
'      'D:FY2018' resolves to D:\FY2018\FY2018

UNC 路径UNC paths

通用命名约定 (UNC) 路径,用于访问网络资源,具有以下格式:Universal naming convention (UNC) paths, which are used to access network resources, have the following format:

  • 一个以 \\ 开头的服务器名或主机名。A server or host name, which is prefaced by \\. 服务器名称可以为 NetBIOS 计算机名称或者 IP/FQDN 地址(支持 IPv4 和 IPv6)。The server name can be a NetBIOS machine name or an IP/FQDN address (IPv4 as well as v6 are supported).
  • 共享名,使用 \ 将其与主机名分隔开。A share name, which is separated from the host name by \. 服务器名和共享名共同组成了卷。Together, the server and share name make up the volume.
  • 目录名称。A directory name. 目录分隔符用来分隔嵌套目录层次结构中的子目录。The directory separator character separates subdirectories within the nested directory hierarchy.
  • 可选的文件名。An optional filename. 目录分隔符用来分隔文件路径和文件名。The directory separator character separates the file path and the filename.

以下是一些 UNC 路径的示例:The following are some examples of UNC paths:

路径Path 描述Description
\\system07\C$\ system07 上 C: 盘的根目录。The root directory of the C: drive on system07.
\\Server2\Share\Test\Foo.txt \\Server2\Share 卷的测试目录中的 Foo.txt 文件。The Foo.txt file in the Test directory of the \\Server2\Share volume.

UNC 路径必须始终是完全限定的。UNC paths must always be fully qualified. 它们可以包括相对目录段(...),但是这些目录段必须是完全限定的路径的一部分。They can include relative directory segments (. and ..), but these must be part of a fully qualified path. 只能通过将 UNC 路径映射至驱动器号来使用相对路径。You can use relative paths only by mapping a UNC path to a drive letter.

DOS 设备路径DOS device paths

Windows 操作系统有一个指向所有资源(包括文件)的统一对象模型。The Windows operating system has a unified object model that points to all resources, including files. 可从控制台窗口访问这些对象路径;并通过旧版 DOS 和 UNC 路径映射到的符号链接的特殊文件,将这些对象路径公开至 Win32 层。These object paths are accessible from the console window and are exposed to the Win32 layer through a special folder of symbolic links that legacy DOS and UNC paths are mapped to. 此特殊文件夹可通过 DOS 设备路径语法(以下任一)进行访问:This special folder is accessed via the DOS device path syntax, which is one of:

\\.\C:\Test\Foo.txt \\?\C:\Test\Foo.txt

除了通过驱动器号识别驱动器以外,还可以使用卷 GUID 来识别卷。In addition to identifying a drive by its drive letter, you can identify a volume by using its volume GUID. 它采用以下形式:This takes the form:

\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt \\?\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt

备注

从 NET Core 1.1 和 .NET Framework 4.6.2 开始,运行在 Windows 上的 .NET 实现支持 DOS 设备路径语法。DOS device path syntax is supported on .NET implementations running on Windows starting with .NET Core 1.1 and .NET Framework 4.6.2.

DOS 设备路径由以下部分组成:The DOS device path consists of the following components:

  • 设备路径说明符(\\.\\\?\),它将路径标识为 DOS 设备路径。The device path specifier (\\.\ or \\?\), which identifies the path as a DOS device path.

    备注

    .NET Core 的所有版本以及从 4.6.2 开始的 .NET Framework 版本都支持 \\?\The \\?\ is supported in all versions of .NET Core and in the .NET Framework starting with version 4.6.2.

  • “实际”设备对象的符号链接(如果是驱动器名称则为 C:,如果是卷 GUID 则为卷{b75e2c83-0000-0000-0000-602f00000000})。A symbolic link to the "real" device object (C: in the case of a drive name, or Volume{b75e2c83-0000-0000-0000-602f00000000} in the case of a volume GUID).

    设备路径说明符后的第一个 DOS 设备路径段标识了卷或驱动器。The first segment of the DOS device path after the device path specifier identifies the volume or drive. (例如,\\?\C:\\\.\BootPartition\。)(For example, \\?\C:\ and \\.\BootPartition\.)

    UNC 有个特定的链接,很自然地名为 UNCThere is a specific link for UNCs that is called, not surprisingly, UNC. 例如:For example:

    \\.\UNC\Server\Share\Test\Foo.txt \\?\UNC\Server\Share\Test\Foo.txt

    对于设备 UNC,服务器/共享部分构成了卷。For device UNCs, the server/share portion forms the volume. 例如,在 \\?\server1\e:\utilities\\filecomparer\ 中,服务器/共享部分是 server1\utilities。For example, in \\?\server1\e:\utilities\\filecomparer\, the server/share portion is server1\utilities. 使用相对目录段调用 Path.GetFullPath(String, String) 等方法时,这一点非常重要;决不可能越过卷。This is significant when calling a method such as Path.GetFullPath(String, String) with relative directory segments; it is never possible to navigate past the volume.

DOS 设备路径通过定义进行完全限定。DOS device paths are fully qualified by definition. 不允许使用相对目录段(...)。Relative directory segments (. and ..) are not allowed. 也不会包含当前目录。Current directories never enter into their usage.

示例:引用同一个文件的方法Example: Ways to refer to the same file

以下示例演示了一些方法,以此可在使用 System.IO 命名空间中的 API 时引用文件。The following example illustrates some of the ways in which you can refer to a file when using the APIs in the System.IO namespace. 该示例实例化 FileInfo 对象,并使用它的 NameLength 属性来显示文件名以及文件长度。The example instantiates a FileInfo object and uses its Name and Length properties to display the filename and the length of the file.

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string[] filenames = {
            @"c:\temp\test-file.txt",
            @"\\127.0.0.1\c$\temp\test-file.txt",
            @"\\LOCALHOST\c$\temp\test-file.txt",
            @"\\.\c:\temp\test-file.txt",
            @"\\?\c:\temp\test-file.txt",
            @"\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
            @"\\127.0.0.1\c$\temp\test-file.txt" };

        foreach (var filename in filenames)
        {
            FileInfo fi = new FileInfo(filename);
            Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes");
        }
    }
}
// The example displays output like the following:
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
//      file test-file.txt: 22 bytes
Imports System.IO

Module Program
    Sub Main()
        Dim filenames() As String = { 
                "c:\temp\test-file.txt", 
                "\\127.0.0.1\c$\temp\test-file.txt",
                "\\LOCALHOST\c$\temp\test-file.txt", 
                "\\.\c:\temp\test-file.txt",
                "\\?\c:\temp\test-file.txt",
                "\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
                "\\127.0.0.1\c$\temp\test-file.txt" }

        For Each filename In filenames 
           Dim fi As New FileInfo(filename)   
           Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes")
        Next   
    End Sub
End Module

路径规范化Path normalization

几乎所有传递至 Windows API 的路径都经过规范化。Almost all paths passed to Windows APIs are normalized. 规范化过程中,Windows 执行了以下步骤:During normalization, Windows performs the following steps:

  • 识别路径。Identifies the path.
  • 将当前目录应用于部分限定(相对)路径。Applies the current directory to partially qualified (relative) paths.
  • 规范化组件和目录分隔符。Canonicalizes component and directory separators.
  • 评估相对目录组件(当前目录是 .,父目录是 ..)。Evaluates relative directory components (. for the current directory and .. for the parent directory).
  • 剪裁特定字符。Trims certain characters.

这种规范化隐式进行,若想显式进行规范化,可以调用 Path.GetFullPath 方法,这会包装对 GetFullPathName() 函数的调用。This normalization happens implicitly, but you can do it explicitly by calling the Path.GetFullPath method, which wraps a call to the GetFullPathName() function. 还可以使用 P/Invoke 直接调用 Windows GetFullPathName() 函数You can also call the Windows GetFullPathName() function directly using P/Invoke.

识别路径Identifying the path

路径规范化的第一步就是识别路径类型。The first step in path normalization is identifying the type of path. 路径归为以下几个类别之一:Paths fall into one of a few categories:

  • 它们是设备路径;就是说,它们的开头是两个分隔符和一个问号或句点(\\?\\.)。They are device paths; that is, they begin with two separators and a question mark or period (\\? or \\.).
  • 它们是 UNC 路径;就是说,它们的开头是两个分隔符,没有问号或句点。They are UNC paths; that is, they begin with two separators without a question mark or period.
  • 它们是完全限定的 DOS 路径;就是说,它们的开头是驱动器号、卷分隔符和组件分隔符 (C:\)。They are fully qualified DOS paths; that is, they begin with a drive letter, a volume separator, and a component separator (C:\).
  • 它们指定旧设备(CONLPT1)。They designate a legacy device (CON, LPT1).
  • 它们相对于当前驱动器的根路径;就是说,它们的开头是单个组件分隔符 (\)。They are relative to the root of the current drive; that is, they begin with a single component separator (\).
  • 它们相对于指定驱动器的当前目录;就是说,它们的开头是驱动器号和卷分隔符,而没有组件分隔符 (C:)。They are relative to the current directory of a specified drive; that is, they begin with a drive letter, a volume separator, and no component separator (C:).
  • 它们相对于当前目录;就是说,它们的开头是上述情况以外的任何内容 (temp\testfile.txt)。They are relative to the current directory; that is, they begin with anything else (temp\testfile.txt).

路径的类型决定是否以某种方式应用当前目录。The type of the path determines whether or not a current directory is applied in some way. 还决定该路径的“根”是什么。It also determines what the "root" of the path is.

处理旧设备Handling legacy devices

如果路径是旧版 DOS 设备(例如 CONCOM1LPT1),则会转换为设备路径(方法是在其前面追加 \\.\)并返回。If the path is a legacy DOS device such as CON, COM1, or LPT1, it is converted into a device path by prepending \\.\ and returned.

开头为旧设备名的路径始终被 Path.GetFullPath(String) 方法解释为旧设备。A path that begins with a legacy device name is always interpreted as a legacy device by the Path.GetFullPath(String) method. 例如,CON.TXT 的 DOS 设备路径为 \\.\CON,而 COM1.TXT\file1.txt 的 DOS 设备路径为 \\.\COM1For example, the DOS device path for CON.TXT is \\.\CON, and the DOS device path for COM1.TXT\file1.txt is \\.\COM1.

应用当前目录Applying the current directory

如果路径非完全限定,Windows 会向其应用当前目录。If a path isn't fully qualified, Windows applies the current directory to it. 不会向 UNC 和设备路径应用当前目录。UNCs and device paths do not have the current directory applied. 带有分隔符 C:\ 的完整驱动器也不会。Neither does a full drive with separator C:\.

如果路径的开头是单个组件分隔符,则会应用当前目录中的驱动器。If the path starts with a single component separator, the drive from the current directory is applied. 例如,如果文件路径是 \utilities 且当前目录为 C:\temp\,规范化后路径则为 C:\utilitiesFor example, if the file path is \utilities and the current directory is C:\temp\, normalization produces C:\utilities.

如果路径开头是驱动器号和卷分隔符,而没有组件分隔符,则应用从命令行界面为指定驱动器设置的最新当前目录。If the path starts with a drive letter, volume separator, and no component separator, the last current directory set from the command shell for the specified drive is applied. 如未设置最新当前目录,则只应用驱动器。If the last current directory was not set, the drive alone is applied. 例如,如果文件路径为 D:sources,当前目录为 C:\Documents\,且 D: 盘上的最新当前目录为 D:\sources\,则结果为 D:\sources\sourcesFor example, if the file path is D:sources, the current directory is C:\Documents\, and the last current directory on drive D: was D:\sources\, the result is D:\sources\sources. 这些“驱动器相对”路径是导致程序和脚本逻辑错误的常见原因。These "drive relative" paths are a common source of program and script logic errors. 假设以字母和冒号开头的路径不是相对路径,显然是不正确的。Assuming that a path beginning with a letter and a colon isn't relative is obviously not correct.

如果路径不是以分隔符开头的,则应用当前驱动器和当前目录。If the path starts with something other than a separator, the current drive and current directory are applied. 例如,如果路径是 filecompare 且当前目录是 C:\utilities\,则结果为 C:\utilities\filecompare\For example, if the path is filecompare and the current directory is C:\utilities\, the result is C:\utilities\filecompare\.

重要

相对路径在多线程应用程序(也就是大多数应用程序)中很危险,因为当前目录是分进程的设置。Relative paths are dangerous in multithreaded applications (that is, most applications) because the current directory is a per-process setting. 任何线程都能在任何时候更改当前目录。Any thread can change the current directory at any time. 从 .NET Core 2.1 开始,可以调用 Path.GetFullPath(String, String) 方法,从想要据此解析绝对路径的相对路径和基础路径(当前目录)获取绝对路径。Starting with .NET Core 2.1, you can call the Path.GetFullPath(String, String) method to get an absolute path from a relative path and the base path (the current directory) that you want to resolve it against.

规范化分隔符Canonicalizing separators

将所有正斜杠 (/) 转换为标准的 Windows 分隔符,也就是反斜杠 (\)。All forward slashes (/) are converted into the standard Windows separator, the back slash (\). 如果存在斜杠,前两个斜杠后面的一系列斜杠都将折叠为一个斜杠。If they are present, a series of slashes that follow the first two slashes are collapsed into a single slash.

评估相对组件Evaluating relative components

处理路径时,会评估所有由一个或两个句点(...)组成的组件或分段:As the path is processed, any components or segments that are composed of a single or a double period (. or ..) are evaluated:

  • 如果是单句点,则删除当前分段,因为它表示当前目录。For a single period, the current segment is removed, since it refers to the current directory.

  • 如果是双句点,则删除当前分段和父级分段,因为双句点表示父级目录。For a double period, the current segment and the parent segment are removed, since the double period refers to the parent directory.

    仅当父级目录未越过路径的根时,才删除父级目录。Parent directories are only removed if they aren't past the root of the path. 路径的根取决于路径的类型。The root of the path depends on the type of path. 对于 DOS 路径,根是驱动器 (C:\);对于UNC,根是服务器/共享 (\\Server\Share);对于设备路径,则为设备路径前缀(\\?\\\.\)。It is the drive (C:\) for DOS paths, the server/share for UNCs (\\Server\Share), and the device path prefix for device paths (\\?\ or \\.\).

剪裁字符Trimming characters

随着分隔符的运行和相对段先遭删除,一些其他字符在规范化过程中也删除了:Along with the runs of separators and relative segments removed earlier, some additional characters are removed during normalization:

  • 如果某段以单个句点结尾,则删除此句点。If a segment ends in a single period, that period is removed. (单个或两个句点的段在之前的步骤中已规范化。(A segment of a single or double period is normalized in the previous step. 三个或更多句点的段未规范化,并且实际上是有效的文件/目录名。)A segment of three or more periods is not normalized and is actually a valid file/directory name.)

  • 如果路径的结尾不是分隔符,则删除所有尾随句点和空格 (U+0020)。If the path doesn't end in a separator, all trailing periods and spaces (U+0020) are removed. 如果最后的段只是单个或两个句点,则按上述相对组件规则处理。If the last segment is simply a single or double period, it falls under the relative components rule above.

    此规则意味着可以创建以空格结尾的目录名称,方法是在空格后添加结尾分隔符。This rule means that you can create a directory name with a trailing space by adding a trailing separator after the space.

    重要

    请勿创建以空格结尾的目录名或文件名 。You should never create a directory or filename with a trailing space. 如果以空格结尾,则可能难以或者无法访问目录,并且应用程序在尝试处理这样的目录或文件时通常会操作失败。Trailing spaces can make it difficult or impossible to access a directory, and applications commonly fail when attempting to handle directories or files whose names include trailing spaces.

跳过规范化Skipping normalization

一般来说,任何传递到 Windows API 的路径都会(有效地)传递到 GetFullPathName 函数并进行规范化。Normally, any path passed to a Windows API is (effectively) passed to the GetFullPathName function and normalized. 但是有一种很重要的例外情况:以问号(而不是句点)开头的设备路径。There is one important exception: a device path that begins with a question mark instead of a period. 除非路径确切地以 \\?\ 开头(注意使用的是规范的反斜杠),否则会对它进行规范化。Unless the path starts exactly with \\?\ (note the use of the canonical backslash), it is normalized.

为什么要跳过规范化过程?Why would you want to skip normalization? 主要有三方面的原因:There are three major reasons:

  1. 为了访问那些通常无法访问但合法的路径。To get access to paths that are normally unavailable but are legal. 例如名为 hidden. 的文件或目录,这是能访问它的唯一方式。A file or directory called hidden., for example, is impossible to access in any other way.

  2. 为了在已规范化的情况下通过跳过规范化过程来提升性能。To improve performance by skipping normalization if you've already normalized.

  3. 为了跳过路径长度的 MAX_PATH 检查以允许多于 259 个字符的路径(仅在 .NET Framework 上)。On the .NET Framework only, to skip the MAX_PATH check for path length to allow for paths that are greater than 259 characters. 大多数 API 都允许这一点,也有一些例外情况。Most APIs allow this, with some exceptions.

备注

.NET Core 显式处理长路径,且不执行 MAX_PATH 检查。.NET Core handles long paths implicitly and does not perform a MAX_PATH check. MAX_PATH 检查只适用于 .NET Framework。The MAX_PATH check applies only to the .NET Framework.

跳过规范化和路径上限检查是两种设备路径语法之间唯一的区别;除此以外它们是完全相同的。Skipping normalization and max path checks is the only difference between the two device path syntaxes; they are otherwise identical. 请谨慎地选择跳过规范化,因为很容易就会创建出“一般”应用程序难以处理的路径。Be careful with skipping normalization, since you can easily create paths that are difficult for "normal" applications to deal with.

如果将开头为 \\?\ 的路径显式地传递至 GetFullPathName 函数,则依然会对它们进行规范化。Paths that start with \\?\ are still normalized if you explicitly pass them to the GetFullPathName function.

可将超过 MAX_PATH 字符数的路径传递至 GetFullPathName,前提是该路径不含 \\?\You can pass paths of more than MAX_PATH characters to GetFullPathName without \\?\. 支持任意长度的路径,只要其字符串大小在 Windows 能处理的范围内。It supports arbitrary length paths up to the maximum string size that Windows can handle.

大小写和 Windows 文件系统Case and the Windows file system

Windows 文件系统有一个让非 Window 用户和开发人员感到困惑的特性,就是路径和目录名称不区分大小写。A peculiarity of the Windows file system that non-Windows users and developers find confusing is that path and directory names are case-insensitive. 也就是说,目录名和文件名反映的是创建它们时所使用的字符串的大小写。That is, directory and file names reflect the casing of the strings used when they are created. 例如,名为For example, the method call

Directory.Create("TeStDiReCtOrY");
Directory.Create("TeStDiReCtOrY")

的方法创建名为 TeStDiReCtOrY 的目录。creates a directory named TeStDiReCtOrY. 如果重命名目录或文件以改变大小写,则目录名或文件名反映的是重命名它们时所使用的字符串的大小写。If you rename a directory or file to change its case, the directory or file name reflects the case of the string used when you rename it. 例如,以下代码将文件 test.txt 重命名为 Test.txt:For example, the following code renames a file named test.txt to Test.txt:

using System.IO;

class Example
{
   static void Main()
   {
      var fi = new FileInfo(@".\test.txt");
      fi.MoveTo(@".\Test.txt");
   }
}
Imports System.IO

Module Example
   Public Sub Main()
      Dim fi As New FileInfo(".\test.txt")
      fi.MoveTo(".\Test.txt")
   End Sub
End Module

但是比较目录名和文件名时不区分大小写。However, directory and file name comparisons are case-insensitive. 如果搜索名为“test.txt”的文件,.NET 文件系统 API 会在比较时忽略大小写问题。If you search for a file named "test.txt", .NET file system APIs ignore case in the comparison. Test.txt、TEST.TXT、test.TXT 和其他任何大写和小写的字母组合都会成为“test.txt”的匹配项。Test.txt, TEST.TXT, test.TXT, and any other combination of upper- and lowercase letters will match "test.txt".