2015-04-13 2 views
1

J'ai besoin de vérifier chaque fichier sur un disque USB donné dans une application C#. Je soupçonne que le goulot d'étranglement ici est la lecture réelle du disque, donc je cherche à faire le plus vite possible.Comment obtenir l'emplacement du premier octet du fichier sur un disque?

Je suppose que ce serait beaucoup plus rapide si je pouvais lire les fichiers sur le disque de manière séquentielle, dans l'ordre réel où ils apparaissent sur le disque (en supposant que le lecteur n'est pas fragmenté).

Comment puis-je trouver cette information pour chaque fichier à partir de son chemin d'accès standard? c'est-à-dire donné un fichier à "F: \ MyFile.txt", comment puis-je trouver l'emplacement de départ de ce fichier sur le disque? Je cours une application C# dans Windows

+0

Vous pouvez http://stackoverflow.com/questions/11934550/get-file-offset-on-disk-cluster-number ...Notez que je ne pense pas que ce soit une bonne idée ... Corrige que ... Je ** fais ** pense que c'est une ** mauvaise idée ** – xanatos

+0

@xanatos pourquoi est-ce une mauvaise idée? – oleksii

+0

@oleksii Je ne sais même pas de quelles autorisations vous avez besoin ... et faire quelque chose de bas niveau avec des disques seulement parce que les méthodes de haut niveau sont lentes n'est pas quelque chose que je ferais jamais. Il y a une raison pour laquelle il existe des méthodes de haut niveau et des méthodes de bas niveau. – xanatos

Répondre

1

maintenant ... Je ne sais pas vraiment si ce sera utile pour vous:

[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(); 
    } 
} 

Notez que la méthode retourne un FileStream que vous pouvez garder ouvert et utiliser pour lire le fichier, ou vous pouvez facilement le modifier pour ne pas le renvoyer (et ne pas le créer), puis rouvrir le fichier lorsque vous voulez le hacher.

Pour utiliser:

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); 
    } 
} 

J'utilise l'API décrite ici: http://www.wd-3.com/archive/luserland.htm. Le programme est beaucoup plus facile car vous n'avez besoin que du numéro de cluster logique initial (la première version du code pourrait extraire toutes les extensions LCN, mais cela serait inutile, car vous devez hacher un fichier du premier au dernier octet). Notez que les fichiers vides (fichiers de longueur 0) n'ont aucun cluster alloué. La fonction renvoie -1 pour le cluster (ERROR_HANDLE_EOF). Vous pouvez choisir comment le gérer.

+0

Très, très utile, merci! Je viens juste de commencer à lire cet article, et encore moins de le comprendre ... – GoldieLocks

+0

La seule question que j'ai est que ça ne semble pas être des fichiers vides qui retournent -1, mais des fichiers qui sont très petits. .. Je comprends la logique autour des fichiers vides n'ayant pas de cluster, comment cela affecte-t-il les très petits fichiers? Je parle 1 ligne de texte, 96 octets, fichier txt. – GoldieLocks

+0

@GoldieLocks http://www.ntfs.com/ntfs_optimization.htm * Sur NTFS si le fichier est assez petit, il peut être stocké dans l'enregistrement MFT lui-même sans utiliser de clusters supplémentaires. * – xanatos

1

Si vos disques sont SSD ou basés sur la technologie du bâton de mémoire, oubliez-le.

Les clés USB et autres périphériques similaires sont généralement basés sur la technologie SSD (ou similaire), où le problème de l'accès aléatoire en lecture/écriture n'est en fait pas un problème. Vous pouvez donc simplement énumérer les fichiers et exécuter votre checksum.

Vous pouvez essayer cela en plusieurs threads, mais je ne suis pas sûr que cela pourrait accélérer le processus, c'est quelque chose que vous devrez peut-être tester. Il peut également varier d'un appareil à l'autre.

Bonus
@xanatos mentionné un point intéressant: «J'ai toujours remarqué que la copie des milliers de fichiers sur une clé mémoire est beaucoup plus lent que la copie d'un seul gros fichier »

Il est en effet beaucoup plus rapide à copier un gros fichier, plutôt qu'une pile de petits fichiers. Et la raison en est (habituellement) non parce que les fichiers sont situés les uns près des autres, il est donc plus facile pour le matériel de les lire séquentiellement. Le problème vient à l'OS qui a besoin de garder le suivi de chaque fichier.

Si vous exécutez un procmon sous Windows, vous observerez une énorme quantité de fichiers FileCreates, FileReads et FileWrites. Pour copier 100 fichiers, OS ouvrirait chaque fichier, lirait son contenu, écrirait dans un autre fichier, fermerait les deux fichiers + beaucoup d'opérations de mise à jour envoyées au système de fichiers, comme les attributs de mise à jour pour les deux fichiers, les deux fichiers, mettre à jour les informations de répertoire, etc. Ainsi, une opération de copie a de nombreuses opérations satellites.

+0

Vous avez probablement raison, mais d'après les tests empiriques, j'ai toujours remarqué que copier des milliers de fichiers sur une clé USB est beaucoup plus lent que de copier un seul gros fichier ... – xanatos

+0

@xanatos c'est un excellent point que je vais développer à ce sujet. – oleksii

+0

Les lecteurs qui seront utilisés pour cela sont tous les disques durs standard USB3 non-SSD 1TB 2,5 pouces, si cela fait une différence ... – GoldieLocks