首頁 > 軟體

C#中獲取檔案大小問題

2023-02-27 06:00:45

C# 獲取檔案大小

直接貼程式碼吧

        /// <summary>
        /// 格式化檔案大小
        /// </summary>
        /// <param name="filesize">檔案傳入大小</param>
        /// <returns></returns>
        private static string GetFileSize(long filesize)
        {
            try
            {
                if (filesize < 0)
                {
                    return "0";
                }
                else if (filesize >= 1024 * 1024 * 1024)  //檔案大小大於或等於1024MB    
                {
                    return string.Format("{0:0.00} GB", (double)filesize / (1024 * 1024 * 1024));
                }
                else if (filesize >= 1024 * 1024) //檔案大小大於或等於1024KB    
                {
                    return string.Format("{0:0.00} MB", (double)filesize / (1024 * 1024));
                }
                else if (filesize >= 1024) //檔案大小大於等於1024bytes    
                {
                    return string.Format("{0:0.00} KB", (double)filesize / 1024);
                }
                else
                {
                    return string.Format("{0:0.00} bytes", filesize);
                }
            }
            catch (Exception ex)
            {
 
                throw ex;
            }
 
        }

上述程式碼是將檔案大小格式化為我們想要的大小。

 FileInfo t = new FileInfo(filePath);//獲取檔案
 檔案大小 = GetFileSize(t.Length);//這樣我們就獲取到了檔案的大小

C# 獲取檔案佔用空間 (絕對準確)

先說一下為什麼要用這種極其麻煩的方法來判斷檔案的佔用空間,因為找不到簡單的方法。

如果是想算資料夾的佔用空間,只需要將裡面的檔案的佔用空間加在一起就可以了。

首先說下檔案大小與佔用空間的區別

這與是硬碟分割區格式有關。

大小是檔案的實際大小,而佔用空間是佔硬碟的實際空間,以FAT32格式為例,硬碟的基本儲存單位是簇,在FAT32中一簇是4KB 那麼,也就是說即使檔案只有1個位元組,在硬碟上也要佔到4KB的空間 如果檔案是4KB零1個位元組,那就要佔用8KB的空間,以此類推 結論: 大小是檔案的實際大小,而佔用空間是佔硬碟的實際空間。

如圖(我這裡一簇是4kB)

計算思路

所以,要想獲得佔用空間,就需要先獲得檔案的大小,然後就可以通過把簇補全即可算出檔案的佔用空間。而獲取檔案大小的方法很簡單,其程式碼如下。

FileInfo fileInfo = new FileInfo(filePath);
Console.WriteLine(fileInfo.Length);

但是通過這種方法計算出的資料並不準確

為什麼會不準確呢?因為有很多不正常的檔案,那些檔案的大小是大於檔案佔用空間的,例如:

而這種情況通過上面的那一段程式碼求出的檔案大小為23677位元組,然後補全簇之後得出的結果一定是大於檔案大小的,怎麼也不可能得出8192位元組(8KB),所以,通過這種方法得出的結果是不準確的。

為什麼會出現這種情況?根據硬碟儲存空間的規則可以得出,佔用空間一定是比其檔案大小要大的。那麼,只有一種可能,那就是該大小並不是檔案的實際大小,它是假的(也有可能是檔案管理系統中的某個未知的壓縮功能導致的)。

獲取檔案的實際大小

要想獲取一個檔案的實際大小,需要呼叫底層的windows API,這些api都是通過C++來編寫的。

裡面就有一個可以用來獲取檔案的實際大小:GetCompressedFileSize()方法。

該方法的說明檔案如下:(為什麼裡面的方法名多了個A,我也不知道為什麼,反正可以拿來用)

https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getcompressedfilesizea

所以獲取檔案實際大小的方法如下:

static void Main(string[] args)
{
    string name = @"D:Documentstest.zip";
    //用來獲取高位數位(只有在讀取超過4GB的檔案才需要用到該引數)
    uint h = 0;
    //用來獲取低位資料
    uint l = GetCompressedFileSize(name, ref h);
    //將兩個int32拼接成一個int64
    ulong r = ((ulong)h << 32) + l;
    FileInfo fileInfo = new FileInfo(name);
    Console.WriteLine(fileInfo.Length);
    Console.WriteLine(h);
    Console.WriteLine(l);
    //最終結果
    Console.WriteLine(r);
    Console.ReadKey(true);
}
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetCompressedFileSize(string fileName, ref uint fileSizeHigh);

然後拿一個正常的檔案測試一下

可以看出,位元組數是正確的,然後再加上補全簇的演演算法,一切就正常了。

其程式碼如下:

static void Main(string[] args)
{
    string name = @"C:WindowsDiagTrackGetFileActionAllowedList.dat";
    //string name = @"D:Documentstest.zip";
    uint h = 0;
    uint l = GetCompressedFileSize(name, ref h);
    ulong r = ((ulong)h << 32) + l;
    FileInfo fileInfo = new FileInfo(name);
    Console.WriteLine(fileInfo.Length);
    Console.WriteLine(h);
    Console.WriteLine(l);
    Console.WriteLine(r);
    ulong size = GetClusterSize("D:\");
    if (r%size != 0)
    {
        decimal res = r / size;
        uint clu = (uint)Convert.ToInt32(Math.Ceiling(res)) + 1;
        r = size * clu;
    }
    //最終結果
    Console.WriteLine(r);
    Console.ReadKey(true);
}
//獲取每簇的位元組數
private static uint GetClusterSize(string rootPath)
{
    //提前宣告各項引數
    uint sectorsPerCluster = 0, bytesPerSector = 0, numberOfFreeClusters = 0, totalNumberOfClusters = 0;
    GetDiskFreeSpace(rootPath, ref sectorsPerCluster, ref bytesPerSector, ref numberOfFreeClusters, ref totalNumberOfClusters);
    return bytesPerSector * sectorsPerCluster;
}
//用於獲取檔案實際大小的api
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint GetCompressedFileSize(string fileName, ref uint fileSizeHigh);
//用於獲取盤資訊的api
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool GetDiskFreeSpace([MarshalAs(UnmanagedType.LPTStr)]string rootPathName, ref uint sectorsPerCluster, ref uint bytesPerSector, ref uint numberOfFreeClusters, ref uint totalNumbeOfClusters);

最後再看一下那個不正常的檔案:

結果8192位元組,計算成功。

。。。
。。。
。。。

這個C#也太坑了吧,為了弄一個獲取佔用空間,我搞了整整一整天。也不知道微軟怎麼想的,就不能直接給一個獲取佔用空間方法嗎?非地讓我們自己算,哎。

後續

有時間為上面的程式碼加了一點說明,同時也加上了錯誤處理,修改後的程式碼如下:

using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string name = @"目標資料夾路徑";
            uint h = 0;
            uint l = GetCompressedFileSize(name, ref h);
            if (l == uint.MaxValue)
                throw new IOException("檔案讀取失敗。", new Win32Exception(Marshal.GetLastWin32Error()));
            ulong r = ((ulong)h << 32) + l;
            FileInfo fileInfo = new FileInfo(name);
            Console.WriteLine("檔案大小:");
            Console.WriteLine(fileInfo.Length);
            Console.WriteLine("高位資料:");
            Console.WriteLine(h);
            Console.WriteLine("低位資料:");
            Console.WriteLine(l);
            Console.WriteLine("檔案實際大小:");
            Console.WriteLine(r);
            ulong size = GetClusterSize("D:\");
            if (r % size != 0)
            {
                decimal res = r / size;
                uint clu = (uint)Convert.ToInt32(Math.Ceiling(res)) + 1;
                r = size * clu;
            }
            //最終結果
            Console.WriteLine("檔案佔用空間:");
            Console.WriteLine(r);
            Console.ReadKey(true);
        }
        //獲取每簇的位元組數
        private static uint GetClusterSize(string rootPath)
        {
            //提前宣告各項引數
            uint sectorsPerCluster = 0, bytesPerSector = 0, numberOfFreeClusters = 0, totalNumberOfClusters = 0;
            GetDiskFreeSpace(rootPath, ref sectorsPerCluster, ref bytesPerSector, ref numberOfFreeClusters, ref totalNumberOfClusters);
            return bytesPerSector * sectorsPerCluster;
        }
        //用於獲取檔案實際大小的api
        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern uint GetCompressedFileSize(string fileName, ref uint fileSizeHigh);
        //用於獲取盤資訊的api
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool GetDiskFreeSpace([MarshalAs(UnmanagedType.LPTStr)]string rootPathName, ref uint sectorsPerCluster, ref uint bytesPerSector, ref uint numberOfFreeClusters, ref uint totalNumbeOfClusters);
    }
}

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com