6
\$\begingroup\$

I wrote a C# tool that make a recursive search. I need the file size and the size on disc. I am using a function that give me the size on disc but it is very slow.

Is there any other way to do it except the following code?

public void RecursiveSearch(string path)
{
    try
    {
        foreach (string dir in Directory.GetDirectories(path))
        {
            foreach (string file in Directory.GetFiles(dir))
            {
                FileInfo fi = new FileInfo(file);

                Application.DoEvents();
                lblPfad.Text = fi.FullName; 

                long FileLength = fi.Length;
                long FileLengthOnHarddisc = GetFileSizeOnDisk(file,fi);


                if ((FileLength == FileLengthOnHarddisc && FileLength == 4096) || FileLength >= FileLengthOnHarddisc)
                {
                    myStreamWriter.WriteLine(file + " - Größe - " + FileLength.ToString() + " - Größe auf Datenträger - " + FileLengthOnHarddisc.ToString()); 
                }

            }

            RecursiveSearch(dir); 

        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message); 
    }
}

Here is the function for getting the size on disc:

  public static long GetFileSizeOnDisk(string file,FileInfo info)
        {
            uint dummy, sectorsPerCluster, bytesPerSector;

            int result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out sectorsPerCluster, out bytesPerSector, out dummy, out dummy);
            if (result == 0) throw new Win32Exception();
            uint clusterSize = sectorsPerCluster * bytesPerSector;

            uint hosize;
            uint losize = GetCompressedFileSizeW(file, out hosize);
            long size;
            size = (long)hosize << 32 | losize;
            return ((size + clusterSize - 1) / clusterSize) * clusterSize;
        }

        [DllImport("kernel32.dll")]
        static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
           [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

        [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
        static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
           out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
           out uint lpTotalNumberOfClusters);
\$\endgroup\$
3
\$\begingroup\$

One aside to mention: you should split your UI and business logic code. For the sake of tossing it into a working example quickly (I'm doing this over lunch), I removed all the UI code and replaced any print-outs with Console.WriteLine calls.

I also did a little refactoring to split method calls.

Additionally, I renamed RecursiveSearch to RecursiveSearch2 so I could run it side-by-side with the original.

Finally, I made some of the non-static method static, but only to speed up running the project. Pay no mind to these changes.

  public static void RecursiveSearch2 (string path)
  {
     try
     {
        RecursiveSearch2 (new DirectoryInfo (path));
     }
     catch (Exception ex)
     {
        Console.WriteLine (ex.Message);
     }
  }

Since I switched to use DirectoryInfo, I went ahead and made two overloads of the RecursiveSearch method. One takes in the string for the initial search, but sub-directories can call the overload which takes in DirectoryInfo directly. The first allows you to make the change without breaking your interface, while the second is more useful during processing work.

For speed, I pushed the try/catch up here and left out exception handling elsewhere, though you may want to add some at the lower level methods, if only to provide more descriptive logging/exception messages.

  public static void RecursiveSearch2 (DirectoryInfo dir)
  {
     foreach (var item in dir.GetFileSystemInfos ())
     {
        if (item is FileInfo)
           PrintFileInfo (item as FileInfo);
        else if (item is DirectoryInfo)
           RecursiveSearch2 (item as DirectoryInfo);
     }
  }

If you call the DirectoryInfo.GetFileSystemInfos method, it returns all directories and files in one call. The upside is that it reduces the calls to the filesystem, which are generally going to be your bottleneck. The downside is that you get a base type back, so some type handling may be necessary.

  private static void PrintFileInfo (FileInfo fi)
  {
     long FileLength = fi.Length;
     long FileLengthOnHarddisc = GetFileSizeOnDisk (fi.FullName, fi);


     if ((FileLength == FileLengthOnHarddisc && FileLength == 4096) || FileLength >= FileLengthOnHarddisc)
     {
        Console.WriteLine (fi.FullName + " - Größe - " + FileLength.ToString () + " - Größe auf Datenträger - " + FileLengthOnHarddisc.ToString ());
     }
  }

We are then left with handling the actual file, which I split off into a different method. The only notable differences is that I changed the input to be a FileInfo type rather than string, since I already have the FileInfo object from the calling method's loop.

Running your method versus the modified version on my c:\downloads folder cut the time consistently between 1/4 to 1/2 the time of the original, though my sample size is far too small to be conclusive. I would advise benchmarking it yourself, though. It is possible that the performance characteristics change wildly based upon the folder structure, depth, and ratio of directories to files.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.