2015-04-13 6 views
1

Мне нужно проверять каждый файл на данном диске USB в приложении C#. Я подозреваю, что узким местом здесь является фактическое считывание с диска, поэтому я стараюсь сделать это как можно быстрее.Как получить местоположение первого байта файла на диске?

Я подозреваю, что это было бы намного быстрее, если бы я мог читать файлы на диске последовательно, в том порядке, в котором они появляются на диске (при условии, что диск не фрагментирован).

Как я могу найти эту информацию для каждого файла со своего стандартного пути? т. е. заданный файл в «F: \ MyFile.txt», как я могу найти начальное местоположение этого файла на диске?

Я запускаю приложение C# в Windows.

+0

Вы можете http://stackoverflow.com/questions/11934550/get-file-offset-on-disk-cluster-number ...Заметьте, что я не думаю, что это хорошая идея ... Правильно, что ... я ** думаю ** думаю, что это ** плохая идея ** – xanatos

+0

@xanatos, почему это плохая идея? – oleksii

+0

@oleksii Я даже не уверен, какие разрешения вам нужны ... и делать что-то низкоуровневое с дисками только потому, что методы высокого уровня медленны - это не то, что я когда-либо делал. Существует причина, по которой существуют методы высокого уровня и методы низкого уровня. – xanatos

ответ

1

сейчас ... Я не знаю, будет ли это полезно для вас:

[StructLayout(LayoutKind.Sequential)] 
public struct StartingVcnInputBuffer 
{ 
    public long StartingVcn; 
} 

public static readonly int StartingVcnInputBufferSizeOf = Marshal.SizeOf(typeof(StartingVcnInputBuffer)); 

[StructLayout(LayoutKind.Sequential)] 
public struct RetrievalPointersBuffer 
{ 
    public uint ExtentCount; 
    public long StartingVcn; 
    public long NextVcn; 
    public long Lcn; 
} 

public static readonly int RetrievalPointersBufferSizeOf = Marshal.SizeOf(typeof(RetrievalPointersBuffer)); 

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 
public static extern SafeFileHandle CreateFileW(
     [MarshalAs(UnmanagedType.LPWStr)] string filename, 
     [MarshalAs(UnmanagedType.U4)] FileAccess access, 
     [MarshalAs(UnmanagedType.U4)] FileShare share, 
     IntPtr securityAttributes, 
     [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, 
     [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, 
     IntPtr templateFile); 

[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] 
static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, 
    ref StartingVcnInputBuffer lpInBuffer, int nInBufferSize, 
    out RetrievalPointersBuffer lpOutBuffer, int nOutBufferSize, 
    out int lpBytesReturned, IntPtr lpOverlapped); 

// Returns a FileStream that can only Read 
public static void GetStartLogicalClusterNumber(string fileName, out FileStream file, out long startLogicalClusterNumber) 
{ 
    SafeFileHandle handle = CreateFileW(fileName, FileAccess.Read | (FileAccess)0x80 /* FILE_READ_ATTRIBUTES */, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero); 

    if (handle.IsInvalid) 
    { 
     throw new Win32Exception(); 
    } 

    file = new FileStream(handle, FileAccess.Read); 

    var svib = new StartingVcnInputBuffer(); 

    int error; 

    RetrievalPointersBuffer rpb; 

    int bytesReturned; 
    DeviceIoControl(handle.DangerousGetHandle(), (uint)589939 /* FSCTL_GET_RETRIEVAL_POINTERS */, ref svib, StartingVcnInputBufferSizeOf, out rpb, RetrievalPointersBufferSizeOf, out bytesReturned, IntPtr.Zero); 

    error = Marshal.GetLastWin32Error(); 

    switch (error) 
    { 
     case 38: /* ERROR_HANDLE_EOF */ 
      startLogicalClusterNumber = -1; // empty file. Choose how to handle 
      break; 

     case 0: /* NO:ERROR */ 
     case 234: /* ERROR_MORE_DATA */ 
      startLogicalClusterNumber = rpb.Lcn; 
      break; 

     default: 
      throw new Win32Exception(); 
    } 
} 

Обратите внимание, что метод возвращает FileStream, что вы можете держать открытыми и использовать для чтения файла, или вы можете легко изменить его, чтобы не возвращать его (и не создавать), а затем снова открыть файл, если хотите его хешировать.

Применение:

string[] fileNames = Directory.GetFiles(@"D:\"); 

foreach (string fileName in fileNames) 
{ 
    try 
    { 
     long startLogicalClusterNumber; 
     FileStream file; 
     GetStartLogicalClusterNumber(fileName, out file, out startLogicalClusterNumber); 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine("Skipping: {0} for {1}", fileName, e.Message); 
    } 
} 

Я использую API, описанный здесь: http://www.wd-3.com/archive/luserland.htm. Программа намного проще, потому что вам нужен только начальный логический номер кластера (первая версия кода может извлечь все экстенты LCN, но это было бы бесполезно, потому что вы должны хэш-файл с первого по последний байт). Обратите внимание, что пустые файлы (файлы с длиной 0) не имеют выделенного кластера. Функция возвращает -1 для кластера (ERROR_HANDLE_EOF). Вы можете выбрать, как его обрабатывать.

+0

Очень, очень полезно, спасибо! Я только начал читать эту статью, не говоря уже о ее понимании ... – GoldieLocks

+0

Единственный вопрос, который у меня есть, это то, что это не просто пустые файлы, которые возвращают -1, а файлы, которые очень малы. .. Я понимаю логику вокруг пустых файлов, не имеющих кластера, как это влияет на очень маленькие файлы? Я говорю 1 строка текста, 96 байт, txt-файл. – GoldieLocks

+0

@ GoldieLocks http://www.ntfs.com/ntfs_optimization.htm * В файле NTFS, если файл достаточно мал, он может быть сохранен в самой записи MFT без использования дополнительных кластеров. * – xanatos

1

Если ваши диски SSD или основаны на технологии Memory Stick - забудьте об этом.

Карты памяти и другие подобные устройства, как правило, основаны на технологии SSD (или аналогичной), где проблема случайного доступа к чтению/записи на самом деле не является проблемой. Таким образом, вы можете просто перечислить файлы и запустить контрольную сумму.

Вы можете попробовать запустить это в нескольких потоках, но я не уверен, что это ускорит процесс, это то, что вам нужно будет протестировать. Он также может варьироваться от устройства к устройству.

Бонус
@xanatos упоминается интересный пункт: «Я всегда замечал, что копирование тысяч файлов на карту памяти происходит гораздо медленнее, чем при копировании одного большого файла»

Это действительно намного быстрее, чтобы скопировать один большой файл, а не куча мелких файлов. И причина в том, что (обычно) не потому, что файлы расположены близко друг к другу, поэтому аппаратное обеспечение проще читать их последовательно. Проблема связана с ОС, которая должна отслеживать каждый файл.

Если вы когда-либо запускали procmon на Windows, вы могли наблюдать огромное количество файлов, файлов и файлов. Чтобы скопировать 100 файлов, ОС откроет каждый файл, прочитает его содержимое, напишет в другой файл, закроет оба файла + много операций обновления, которые отправляются в файловую систему, такие как атрибуты обновления для обоих файлов, обновлять дескрипторы безопасности для оба файла, обновлять информацию каталога и т. д. Таким образом, одна операция копирования имеет множество спутниковых операций.

+0

Возможно, вы правы, но из эмпирического тестирования я всегда замечал, что копирование тысяч файлов на карту памяти гораздо медленнее, чем копирование одного большого файла ... – xanatos

+0

@xanatos, который является отличным моментом, я буду расширять его. – oleksii

+0

Приводы, которые будут использоваться для этого, - это 1TB 2,5-дюймовые стандартные жесткие диски без SSD USB3, если это имеет значение ... – GoldieLocks