2011-01-22 53 views
10

Chúng tôi có một thư mục trên Windows ... rất lớn. Tôi chạy "dir> list.txt". Lệnh bị mất phản hồi sau 1,5 giờ. Các tập tin đầu ra là khoảng 200 MB. Nó cho thấy có ít nhất 2,8 triệu tập tin. Tôi biết tình hình là ngu ngốc nhưng chúng ta hãy tập trung vấn đề chính nó. Nếu tôi có một thư mục như vậy, làm thế nào tôi có thể chia nó thành một số thư mục con "có thể quản lý"? Đáng ngạc nhiên là tất cả các giải pháp tôi đã đưa ra với tất cả liên quan đến việc nhận được tất cả các tập tin trong thư mục tại một số điểm, đó là một không-không có trong trường hợp của tôi. Bất kỳ đề xuất?Cách tách một thư mục lớn?

Cảm ơn Keith Hill và Mehrdad. Tôi chấp nhận câu trả lời của Keith vì đó là chính xác những gì tôi muốn làm nhưng tôi không thể làm cho PS hoạt động một cách nhanh chóng.

Với mẹo của Mehrdad, tôi đã viết chương trình nhỏ này. Mất hơn 7 giờ để di chuyển 2,8 triệu tệp. Vì vậy, lệnh dir ban đầu đã kết thúc. Nhưng bằng cách nào đó nó đã không trở lại giao diện điều khiển.

namespace SplitHugeFolder 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var destination = args[1]; 

      if (!Directory.Exists(destination)) 
       Directory.CreateDirectory(destination); 

      var di = new DirectoryInfo(args[0]); 

      var batchCount = int.Parse(args[2]); 
      int currentBatch = 0; 

      string targetFolder = GetNewSubfolder(destination); 

      foreach (var fileInfo in di.EnumerateFiles()) 
      { 
       if (currentBatch == batchCount) 
       { 
        Console.WriteLine("New Batch..."); 
        currentBatch = 0; 
        targetFolder = GetNewSubfolder(destination); 
       } 

       var source = fileInfo.FullName; 
       var target = Path.Combine(targetFolder, fileInfo.Name); 
       File.Move(source, target); 
       currentBatch++; 
      } 
     } 

     private static string GetNewSubfolder(string parent) 
     { 
      string newFolder; 
      do 
      { 
       newFolder = Path.Combine(parent, Path.GetRandomFileName()); 
      } while (Directory.Exists(newFolder)); 
      Directory.CreateDirectory(newFolder); 
      return newFolder; 
     } 
    } 
} 
+0

Uh ... hãy viết bản triển khai NTFS của riêng bạn và chia nó thành cây tìm kiếm nhị phân '$ INDEX_ALLOCATION'? Mặc dù vui vẻ ... – Mehrdad

+0

Nhân tiện, tại sao bạn không thể có danh sách tất cả các tệp? Hàm 'FindNextFile' có tốn quá nhiều thời gian/tài nguyên hay chỉ là' dir' thực hiện điều đó? – Mehrdad

+0

@Mehrdad, bởi vì nó quá chậm. FindNextFile có vẻ đầy hứa hẹn. Sẽ thử điều đó. –

Trả lời

8

Tôi sử dụng Get-ChildItem để lập chỉ mục toàn bộ C: lái xe mỗi đêm vào c: \ filelist.txt. Đó là khoảng 580.000 tệp và kích thước tệp kết quả là ~ 60MB. Phải thừa nhận rằng tôi đang ở trên Win7 x64 với 8 GB RAM. Điều đó nói rằng, bạn có thể thử một cái gì đó như thế này:

md c:\newdir 
Get-ChildItem C:\hugedir -r | 
    Foreach -Begin {$i = $j = 0} -Process { 
     if ($i++ % 100000 -eq 0) { 
      $dest = "C:\newdir\dir$j" 
      md $dest 
      $j++ 
     } 
     Move-Item $_ $dest 
    } 

Điều quan trọng là phải di chuyển theo cách trực tuyến. Tức là, không thu thập tất cả các kết quả Get-ChildItem thành một biến duy nhất và sau đó tiến hành. Điều đó đòi hỏi tất cả 2,8 triệu FileInfos phải ở trong bộ nhớ cùng một lúc. Ngoài ra, nếu bạn sử dụng tham số Name trên Get-ChildItem, nó sẽ xuất ra một chuỗi chứa đường dẫn của tệp tương ứng với thư mục cơ sở. Thậm chí sau đó, có lẽ kích thước này sẽ chỉ áp đảo bộ nhớ có sẵn cho bạn. Và không nghi ngờ gì nữa, nó sẽ mất khá nhiều thời gian để thực hiện. IIRC chính xác, kịch bản lập chỉ mục của tôi mất vài giờ.

Nếu nó hoạt động, bạn nên gió lên với c:\newdir\dir0 qua dir28 nhưng sau đó một lần nữa, tôi chưa thử nghiệm tập lệnh này vì vậy số dặm của bạn có thể thay đổi. BTW cách tiếp cận này giả định rằng bạn đang rất lớn dir là một dir khá phẳng.

Cập nhật: Sử dụng tham số Name gần như gấp hai lần vì vậy đừng sử dụng thông số đó.

+0

Đây là những gì tôi muốn làm đầu tiên với PS-ống Get-ChildItem đầu ra. Một lý do khác để bắt đầu học PS. Cảm ơn! –

+0

Và có, thư mục khổng lồ bằng phẳng. Đó là nguyên nhân gây ra vấn đề ngay từ đầu. –

0

Làm thế nào về việc bắt đầu với điều này: cmd/c dir/b> list.txt

Điều đó sẽ giúp bạn có được một danh sách tất cả các tên tập tin.

Nếu bạn đang thực hiện "dir> list.txt" từ lời nhắc powerhell, get-childitem được đặt bí danh là "dir". Get-childitem có các vấn đề đã biết liệt kê các thư mục lớn và các bộ sưu tập đối tượng mà nó trả về có thể rất lớn.

+0

Tôi không chạy từ PS. Đó là một thư mục DOS đơn giản. Nó đã chết sau khi nhận được các tệp 2.8M. Tôi đã không cố gắng nhưng tôi đoán là dir/b hoạt động tương tự. –

+0

Nó sẽ chỉ trả về tên tệp.
19.0795682 – mjolinor

+0

(lệnh đo {cmd/c dir c: \ windows /s}).totalseconds 3.6437911 (lệnh đo {cmd/c dir c: \ windows/b /s}).totalseconds 2.6323411 Nhanh hơn, nhưng không phải bởi nhiều. – mjolinor

2

Tôi phát hiện ra GetChildItem là tùy chọn chậm nhất khi làm việc với nhiều mục trong thư mục.

Nhìn vào kết quả:

Measure-Command { Get-ChildItem C:\Windows -rec | Out-Null } 
TotalSeconds  : 77,3730275 
Measure-Command { listdir C:\Windows | Out-Null } 
TotalSeconds  : 20,4077132 
measure-command { cmd /c dir c:\windows /s /b | out-null } 
TotalSeconds  : 13,8357157 

(với chức năng listdir định nghĩa như thế này:

function listdir($dir) { 
    $dir 
    [system.io.directory]::GetFiles($dir) 
    foreach ($d in [system.io.directory]::GetDirectories($dir)) { 
     listdir $d 
    } 
} 

)

Với điều này trong tâm trí, những gì tôi sẽ làm gì: Tôi sẽ ở lại PowerShell nhưng sử dụng cách tiếp cận lowlevel hơn với.NET phương pháp:

function DoForFirst($directory, $max, $action) { 
    function go($dir, $options) 
    { 
     foreach ($f in [system.io.Directory]::EnumerateFiles($dir)) 
     { 
      if ($options.Remaining -le 0) { return } 
      & $action $f 
      $options.Remaining-- 
     } 
     foreach ($d in [system.io.directory]::EnumerateDirectories($dir)) 
     { 
      if ($options.Remaining -le 0) { return } 
      go $d $options 
     } 
    } 
    go $directory (New-Object PsObject -Property @{Remaining=$max }) 
} 
doForFirst c:\windows 100 {write-host File: $args } 
# I use PsObject to avoid global variables and ref parameters. 

Để sử dụng mã bạn phải chuyển sang .NET 4.0 runtime - Phương pháp liệt kê là người mới trong .NET 4.0.

Bạn có thể chỉ định bất kỳ tập lệnh chặn nào là thông số -action, vì vậy trong trường hợp của bạn, nó sẽ giống như {Move-item -literalPath $args -dest c:\dir }.

Chỉ cần cố gắng liệt kê 1.000 mặt hàng đầu tiên, tôi hy vọng nó sẽ kết thúc rất nhanh chóng:

doForFirst c:\yourdirectory 1000 {write-host '.' -nonew } 

Và tất nhiên bạn có thể xử lý tất cả các mặt hàng cùng một lúc, chỉ cần sử dụng

doForFirst c:\yourdirectory ([long]::MaxValue) {move-item ... } 

và mỗi mục nên được xử lý ngay sau khi nó được trả về. Vì vậy, toàn bộ danh sách không được đọc cùng một lúc và sau đó được xử lý, nhưng nó được xử lý trong khi đọc.

+0

+1 để so sánh hiệu suất! –

+1

Nó trở nên tồi tệ hơn. Tại khoảng 300.000 tệp, biểu đồ thời gian phản hồi chuyển thành thanh khúc côn cầu http://blogs.msdn.com/b/powershell/archive/2009/11/04/why-is-get-childitem-so-slow.aspx – mjolinor

+0

Keep lưu ý rằng EnumerateFiles là một phương thức mới trong .NET 4.0 và thường không có sẵn cho PowerShell. Bạn phải sửa đổi cấu hình hoặc đăng ký PowerShell của bạn để liên kết PowerShell với .NET 4.0. –

Các vấn đề liên quan