轉自 這裏
衆所周知,微軟的文件系統經歷了 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 語言並不需要對長路徑名文件進行特殊的處理,就可以支持長路徑名文件的創建、讀寫和刪除操作等基本操作。但是,JDK 1.4.2 和 JDK 5.0 在長路徑的支持上是不同的,JDK 1.4.2 並不是完全支持所有的長路徑名文件操作,比如訪問文件屬性的操作是不支持的。我們設計瞭如下代碼來驗證 JDK 1.4.2 和 JDK 5.0 對長路徑名文件支持的區別。
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 平臺對長路徑名文件的操作只是有限的支持,使用的時候,一定要注意。
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 就可以完全不用擔心長路徑名文件的使用問題。
|
相對於 JDK 5.0 不需要任何改動就可以支持長路徑名文件,在 C/C++ 中使用超過 260 個字符的路徑長度的文件,會複雜得多。下面介紹兩種支持長路徑名文件的方法。
從微軟官方網站 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 字符的文件名(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