C++17 STL Стандартная библиотека шаблонов — страница 118 из 119

все равно совершит данное действие, но мы не хотим сами заходить в эти подкаталоги.


    const path p {entry.path()};

    const file_status fs {status(p)};

    if (is_directory(fs)) { continue; }


5. Далее извлекаем расширения из строки, представляющей запись каталога. Если у нее нет расширения, то просто опускаем ее:


    const string ext {p.extension().string()};

    if (ext.length() == 0) { continue; }


6. Подсчитаем размер текущего файла. Затем найдем агрегатный объект в ассоциативном массиве для этого расширения. Если такого объекта нет, то неявно создадим его. Просто увеличим счетчик количества файлов и добавим размер файла в переменную-аккумулятор:


    const size_t size {file_size(p)};


    auto &[size_accum, count] = m[ext];

    size_accum += size;

    count      += 1;

  }


7. После этого вернем ассоциативный массив:


  return m;

}


8. В функции

main
примем путь, предоставленный пользователем в командной строке. Конечно, нужно проверить, существует ли он, поскольку в противном случае продолжать не имеет смысла.


int main(int argc, char *argv[])

{

  path dir {argc > 1 ? argv[1] : "."};

  if (!exists(dir)) {

    cout << "Path " << dir << " does not exist.\n";

    return 1;

  }


9. Можно мгновенно проитерировать по ассоциативному массиву, предоставляемому

ext_stats
. Поскольку элементы типа
accum_size
этого массива содержат сумму всех файлов с одинаковым расширением, разделим эту сумму на общее количество таких файлов и выведем ее на экран:


  for (const auto &[ext, stats] : ext_stats(dir)) {

    const auto &[accum_size, count] = stats;

    cout << setw(15) << left << ext << ": "

<< setw(4) << right << count

<< " items, avg size "

<< setw(4) << size_string(accum_size / count)

<< '\n';

  }

}


10. Компиляция и запуск программы дадут следующий результат. Я предоставил ей в качестве аргумента командной строки каталог, содержащий офлайн-справку по С++.


$ ./file_type ~/Documents/cpp_reference/

.css   :    2 items, avg size  41K

.gif   :    7 items, avg size 902B

.html  : 4355 items, avg size  38K

.js    :    3 items, avg size   4K

.php   :    1 items, avg size 739B

.png   :   34 items, avg size   2K

.svg   :   53 items, avg size   6K

.ttf   :    2 items, avg size 421K

Инструмент для уменьшения размера папки путем замены дубликатов символьными ссылками

Существует множество инструментов, сжимающих данные разными способами. Наиболее известными примерами таких алгоритмов/форматов являются ZIP и RAR. Подобные инструменты уменьшают размер файлов, снижая их внутреннюю избыточность.

Прежде чем сжать файлы в архив, можно довольно просто снизить использование диска, просто удалив повторяющиеся файлы. В данном примере мы реализуем небольшой инструмент, который рекурсивно проходит по каталогу и в процессе ищет файлы с одинаковым содержимым. Если он найдет такие файлы, то удалит все дубликаты, кроме одного. Все удаленные файлы будут заменены символьными ссылками, которые указывают на оставшийся уникальный файл. Это позволяет сэкономить место, не выполняя сжатия и сохраняя данные.


Как это делается

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


 Убедитесь, что создали резервные копии системных данных. Мы будем работать с функциями STL, которые удаляют файлы. Неверно указанный путь для такой программы может привести к тому, что программа жадно удалит нежелательным способом слишком много файлов.


1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен

std
и
filesystem
по умолчанию:


#include 

#include 

#include 

#include 


using namespace std;

using namespace filesystem;


2. Чтобы определить, какие файлы являются дубликатами друг друга, создадим ассоциативный массив, в котором соотносятся хеши файлов и путь к первому файлу, из которого был получен этот хеш. Для получения таких хешей следует использовать популярный алгоритм, такой как MD5 или SHA. В целях сохранения данного примера чистым и простым просто считаем весь файл в строку, а затем задействуем объект хеш-функции, уже применяемый

unordered_map
для подсчета хешей строк:


static size_t hash_from_path(const path &p)

{

  ifstream is {p.c_str(),

               ios::in | ios::binary};

  if (!is) { throw errno; }

  string s;

  is.seekg(0, ios::end);

  s.reserve(is.tellg());

  is.seekg(0, ios::beg);

  s.assign(istreambuf_iterator{is}, {});

  return hash{}(s);

}


3. Затем реализуем функцию, которая создает такой ассоциативный массив, основанный на хешах, и удаляет дубликаты. Она рекурсивно итерирует по каталогу и его подкаталогам:


static size_t reduce_dupes(const path &dir)

{

  unordered_map m; size_t count {0};

  for (const auto &entry :

     recursive_directory_iterator{dir}) {


4. Для каждой записи каталога функция проверяет, является ли эта запись каталогом. Все каталоги опускаются. Для каждого файла генерируем значение хеша и пробуем вставить его в ассоциативный массив. Если последний уже содержит такой хеш, то это значит, что файл с таким хешем уже был добавлен. Соответственно, мы нашли дубликат! В случае конфликтов во время вставки второе значение в паре, которую возвращает

try_emplace
, равно
false
.


    const path p {entry.path()};

    if (is_directory(p)) { continue; }

    const auto &[it, success] =

      m.try_emplace(hash_from_path(p), p);


5. Задействуя значения, возвращаемые

try_emplace
, мы можем сказать пользователю, что мгновение назад вставили файл, поскольку встретили этот хеш в первый раз. Если мы нашли дубликат, то сообщаем пользователю о том, что второй файл является дубликатом, и удаляем его. После удаления создаем символьную ссылку, которая заменяет дубликат.


    if (!success) {

      cout << "Removed " << p.c_str()

<< " because it is a duplicate of "

<< it->second.c_str() << '\n';

      remove(p);

      create_symlink(absolute(it->second), p);

      ++count;

    }


6. После перебора в файловой системе возвращаем количество файлов, которые мы удалили и заменили файловыми ссылками.


  }

  return count;

}


7. В функции

main
убеждаемся, что пользователь передал каталог в командной строке и что этот каталог существует: