Windows 平臺上長路徑名文件的解決方法

轉自 這裏

衆所周知,微軟的文件系統經歷了 fat->fat32->NTFS 的技術變革。且不論安全和文件組織方式上的革新,單就文件名而言,已經從古老的 DOS 8.3 文件格式(僅支持最長 8 個字符的文件名和 3 個字符的後綴名)轉變爲可以支持長達 255 個字符的文件名。而對於路徑長度,NTFS 也已經支持長達 32768 個字符的路徑名。

然而,Windows 操作系統並沒有完全放開路徑名長度的限制,在 windef.h 中,可以找到如下的宏:

#define MAX_PATH 260

 

事實上,所有的 Windows API 都遵循這個限制。因此,每當我們試圖更改某一文件的文件名時,當輸入的文件名長度 ( 全路徑 ) 到達一定限度時,雖然文件名本身還未達到 255 個字符的限制,但是任何輸入將不再被接受,這其實正是由於操作系統不允許 260 個字符(byte)的文件全路徑。

實際應用中,這種 260 個字符的全路徑的限制給應用開發帶來了很大的不便。試想如下應用:我們希望給應用服務器增加一個本地 cache 的功能,該功能可以把遠程服務器上的文件留下一個本地的副本。一個合理的實現可以把 url 映射爲文件名,當 url 很長時,cache 文件的長度也會很長。當文件名長度超過 255,我們可以把映射文件名的前 255 個字符作爲目錄名稱。但是,我們仍然無法解決 260 個字符的全路徑限制。另外,如果一個應用軟件的目錄結構過深,很容易出現某些文件名長度(含路徑)超過 260 個字符,並因此造成安裝或刪除的失敗。總而言之,該限制給我們的開發測試工作帶來了諸多不便。

對於一些網絡服務器,往往需要將 Java 代碼用於上層邏輯控制 / 事務處理的開發,同時將 C/C++ 用於底層核心功能的實現。爲此,我們研究了這兩種程序語言對長路徑名文件的支持情況。其中,對於 Java,比較了兩個常用版本 1.4 和 5.0 對長路徑支持的差異性;對於 C/C++ 語言的侷限性,提出了我們的解決方法。

實驗環境 :

操作系統: Windows xp

文件系統: NTFS 文件系統

Java 編譯環境: IBM JDK 1.4.2 以及 IBM JDK 5.0

C++ 編譯環境: VC.net

 




回頁首

 

在 Java 中使用長路徑名文件

Java 語言並不需要對長路徑名文件進行特殊的處理,就可以支持長路徑名文件的創建、讀寫和刪除操作等基本操作。但是,JDK 1.4.2 和 JDK 5.0 在長路徑的支持上是不同的,JDK 1.4.2 並不是完全支持所有的長路徑名文件操作,比如訪問文件屬性的操作是不支持的。我們設計瞭如下代碼來驗證 JDK 1.4.2 和 JDK 5.0 對長路徑名文件支持的區別。


清單 1. 對長路徑名文件操作的 Java 實驗代碼:

                
try {
    String fileName = "E://VerylongpathVerylongpathVerylongpath
        VerylongpathVerylongpathVerylongpathVerylongpath
        VerylongpathVerylongpathVerylongpathVerylongpath//
	VerylongpathVerylongpathVerylongpathVery
        longpathVerylongpathVerylongpathVerylongpath
	VerylongpathVerylongpathVerylongpathVerylongpa
        th.txt";
    System.out.println("Filename: " + fileName);
    System.out.println("File path length: " + fileName.length());
    String renameFileName = "E://VerylongpathVerylongpathVerylongpath
        VerylongpathVerylongpathVerylongpathVerylongpath
        VerylongpathVerylongpathVerylongpathVerylongpath//Short.txt";
	
    //Create the file.
    File file = new File(fileName);
    if (!file.exists())
        file.createNewFile();
    if (file.exists())
        System.out.println("The file exists!");
    if (file.canRead())
        System.out.println("The file can be read!");
    if (file.canWrite())
        System.out.println("The file can be written!");
    if (file.isFile())
        System.out.println("It's a file!");

    //Write to the created file.
    FileOutputStream out = new FileOutputStream(file);
    PrintStream p = new PrintStream(out);
    p.println("This is only a test!");
    p.close();

    //Read the information from that file.
    BufferedReader br = new BufferedReader(new FileReader(file));
    StringBuffer sb = new StringBuffer();
    while (true) {
        String sl = br.readLine();
        if (sl == null) {
            break;
        } else {
            sb.append(sl + "/n");
        }
    }
    br.close();
    System.out.println("The content in the file:");
    System.out.print("/t" + sb.toString());

    //File rename
    File newfile = new File(renameFileName);
    if (newfile.exists())
        System.out.println(renameFileName + "exsited");
    else {
        if (file.renameTo(newfile)){
            System.out.println("Rename sucessful!");
        } else {
            System.out.println("Rename failed!");
        }	
    }

    //delete file
    if (file.delete())
        System.out.println("The old file deleted!");
    if (newfile.delete())
        System.out.println("The renamed file deleted!");
    }  catch (IOException e) {
        //Error happened
        e.printStackTrace();
        System.out.println("Error occurs in writing to the file.");
    }
}



清單 2. 使用 ibm-java2-sdk-142 的結果

                
Filename: E:/VerylongpathVerylongpathVerylongpath
VerylongpathVerylongpathVerylongpathVerylongpathVer
ylongpathVerylongpathVerylongpathVerylongpath/
VerylongpathVerylongpathVerylongpathVerylong
pathVerylongpathVerylongpathVerylongpath
VerylongpathVerylongpathVerylongpathVerylongpath.t
xt

File path length: 272

The content in the file:

This is only a test!

Rename failed!

The old file deleted!

 

從實驗結果來看,JDK 1.4.2 得到了該長路徑名文件的內容,因此,對於該長路徑名文件的創建以及讀寫操作都是支持的。但是對比下文使用 JDK 5.0 的結果,可以看到,所有對於文件屬性的判斷都是錯誤的,同時,重命名的操作也無法實現。更爲重要的是,JDK 1.4.2 存在着一個很致命的問題,即方法 File.exists() 是失效的。通常,在刪除文件前,需要調用該方法判斷文件是否存在,對於 JDK 1.4.2,如果直接去刪除一個不知道是否存在的文件,就會存在比較大的風險。因此,JDK 1.4.2 在 Windows 平臺對長路徑名文件的操作只是有限的支持,使用的時候,一定要注意。


清單 3. 使用 ibm-java2-sdk-50 的結果

                
Filename: E:/VerylongpathVerylongpathVerylongpath
VerylongpathVerylongpathVerylongpathVerylongpathVer
ylongpathVerylongpathVerylongpathVerylongpath/
VerylongpathVerylongpathVerylongpathVerylong
pathVerylongpathVerylongpathVerylongpath
VerylongpathVerylongpathVerylongpathVerylongpath.t
xt
File path length: 272
The file exists!
The file can be read!
The file can be written!
It's a file!
The content in the file:
	This is only a test!
Rename sucessful!
The renamed file deleted!

 

從實驗中可以清楚的看到,在版本 JDK 5.0 中,所有的文件操作(新建、讀寫、屬性操作、重命名、刪除等)都能夠得到正確的處理。使用 JDK 5.0 就可以完全不用擔心長路徑名文件的使用問題。

 




回頁首

 

在 C/C++ 中使用長路徑名文件

相對於 JDK 5.0 不需要任何改動就可以支持長路徑名文件,在 C/C++ 中使用超過 260 個字符的路徑長度的文件,會複雜得多。下面介紹兩種支持長路徑名文件的方法。

方法一:使用 Unicode 版本的 API

從微軟官方網站 Path Field Limits,可以查到,使用 Unicode 版本的 API,對於使用 NTFS 文件系統的 Windows NT 4.0, Windows 2000, Windows XP Home Edition, Windows XP Professional 和 Windows Server 2003 操作系統,可以支持 32768 字節的文件路徑長度。同時,路徑名必須使用 //?/ 的前綴。依照這個思路,我們設計了實驗。


清單 4. 對長路徑名文件操作的 C 的示例代碼(Unicode API)

                
{
FILE *from, *to;
char filename[1024];
strcpy(filename,"////?//E://VerylongpathVerylongpathVerylongpathVerylongpathVerylongpathV
erylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpath//VerylongpathVeryl
ongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpat
hVerylongpathVerylongpath.txt");
int iL1=MultiByteToWideChar(CP_ACP, 0, filename, strlen(filename), NULL, 0); 
WCHAR* wfilename=new WCHAR[iL1+1]; 
wfilename[iL1] = '/0';
int iL2=MultiByteToWideChar(CP_ACP, 0, filename, strlen(filename), wfilename, iL1); 
from = _wfopen( wfilename ,L"rb");
to = fopen(".//longpath.txt", "wb");
if((from ==NULL)||(to==NULL))
    return -1;
char buffer[1024];
int count = 0;
while ( (count = fread(buffer, sizeof(char), 1024, from)) != 0)
    fwrite( buffer, sizeof(char), count, to);
delete []wfilename;
fclose (from); fclose(to);
}

 

使用如上的方法,我們可以拷貝某長路徑名的文件到當前文件夾中。從試驗結果看,該方法是有效的。但是,由於該方法要求系統使用 Unicode 的 API,同時需要更改路徑名稱以及編碼方式。因此,對於一個已經存在的系統,由於需要改變所有文件操作相關的 API,因此改動將會很大。

方法二:創建 8.3 格式的短路徑名

對於每一個長路徑名,都有一個 8.3 格式(8 個字符的文件名和 3 個字符的後綴名)的短路徑名與其相對應,任意的文件夾或者文件名都可以映射成一個 8 字符的文件名(A~B),其中 A 是文件名前綴,B 是表示字母序的順序。操作系統可以保證這樣的映射是一對一的,只要使用 GetShortPathName() 將長路徑名轉成相應的短路徑名,就可以進行對該文件進行普通的文件操作。同時,在任何時候都可以用函數 GetLongPathName() 把 8.3 格式的短路徑名恢復成初始的長路徑名。

GetShortPathName Function 敘述,我們需要一個 Unicode 版本的 API,同時在路徑名前加上 //?/ 的前綴,才能實現長短路徑名間的切換。但從實驗來看,即使不使用 Unicode 的 API,依然可以實現上述功能。


清單 4. 對長路徑名文件操作的 c 的示例代碼(ShortPath)

                
{
char pathName [1024];
strcpy(pathName,"////?//E://VerylongpathVerylongpathVerylongpathVerylongpathVerylongpathV
erylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpath//VerylongpathVeryl
ongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpathVerylongpat
hVerylongpathVerylongpath.txt");

const int MaxPathLength = 2048;
char shortPath[MaxPathLength];
	
if (strlen(pathName) >= MAX_PATH)
{
    char prePath[] = "////?//";
    if (strlen(pathName) >= MaxPathLength - strlen(pathName))
        return false;

    sprintf(shortPath, "%s%s", prePath, pathName);

    for (int iPathIndex = 0; iPathIndex < strlen(shortPath); iPathIndex++)
        if (shortPath[iPathIndex] == '/')
            shortPath[iPathIndex] = '//';

    int dwlen = GetShortPathName(shortPath, shortPath, MaxPathLength);
    if (dwlen <= 0)
        return false;
}
}

 

經過上述的代碼,超過 MAX_PATH 限制的路徑名都可以轉變成一個 8.3 格式的短路徑名,可以把這個文件名 (shortPath)作爲後續文件操作函數的參數。這種情況下,對於該文件的所有操作都可以被支持了。我們用這種縮短路徑名長度的方式解決了長路徑名文件的操作問題。

 

更多參考:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=94094

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章