public int uncommittedChanges() {
   Set<Map.Entry<String, Storeable>> indexedSet = tableIndexedData.entrySet();
   Set<Map.Entry<String, Storeable>> diskSet = tableOnDisk.entrySet();
   int count = 0;
   Iterator<Map.Entry<String, Storeable>> iter1 = indexedSet.iterator();
   Iterator<Map.Entry<String, Storeable>> iter2 = diskSet.iterator();
   while (iter1.hasNext()) {
     Map.Entry<String, Storeable> next = iter1.next();
     Storeable entryOnDisk = tableOnDisk.get(next.getKey());
     if (entryOnDisk == null) {
       // записи на диске нет, то она изменение
       ++count;
     } else if (!entryOnDisk.equals(next.getValue())) {
       // запись на диске есть, но она другая, тоже изменение
       ++count;
     }
   }
   while (iter2.hasNext()) {
     Map.Entry<String, Storeable> next = iter2.next();
     Storeable entryIndexed = tableIndexedData.get(next.getKey());
     if (entryIndexed == null) {
       // на диске есть, индексированной нет, изменение
       ++count;
     }
   }
   return count;
 }
 /**
  * Выполняет откат изменений с момента последней фиксации.
  *
  * @return Число откаченных изменений.
  */
 @Override
 public int rollback() {
   int numberOfRolledChanges = uncommittedChanges();
   tableIndexedData.clear();
   tableIndexedData.putAll(tableOnDisk);
   for (int nDir = 0; nDir < 16; ++nDir) {
     for (int nFile = 0; nFile < 16; ++nFile) {
       tableFileModified[nDir][nFile] = false;
     }
   }
   return numberOfRolledChanges;
 }
 /**
  * Устанавливает значение по указанному ключу.
  *
  * @param key Ключ для нового значения. Не может быть null.
  * @param value Новое значение. Не может быть null.
  * @return Значение, которое было записано по этому ключу ранее. Если ранее значения не было
  *     записано, возвращает null.
  * @throws IllegalArgumentException Если значение параметров key или value является null.
  * @throws ru.fizteh.fivt.storage.structured.ColumnFormatException - при попытке передать
  *     Storeable с колонками другого типа.
  */
 @Override
 public Storeable put(String key, Storeable value) throws ColumnFormatException {
   checkKey(key);
   checkValue(value);
   Storeable oldValue = tableIndexedData.get(key);
   if (oldValue == null || !oldValue.equals(value)) {
     HashcodeDestination dest = new HashcodeDestination(key);
     tableFileModified[dest.getDir()][dest.getFile()] = true;
     tableIndexedData.put(key, value);
   }
   return oldValue;
 }
 /**
  * Удаляет значение по указанному ключу.
  *
  * @param key Ключ для поиска значения. Не может быть null.
  * @return Предыдущее значение. Если не найдено, возвращает null.
  * @throws IllegalArgumentException Если значение параметра key является null.
  */
 @Override
 public Storeable remove(String key) {
   checkKey(key);
   Storeable oldValue = tableIndexedData.remove(key);
   if (oldValue != null) {
     HashcodeDestination dest = new HashcodeDestination(key);
     tableFileModified[dest.getDir()][dest.getFile()] = true;
   }
   return oldValue;
 }
 /**
  * Выполняет фиксацию изменений.
  *
  * @return Число записанных изменений.
  * @throws java.io.IOException если произошла ошибка ввода/вывода. Целостность таблицы не
  *     гарантируется.
  */
 @Override
 public int commit() throws IOException {
   int numberOfCommittedChanges = uncommittedChanges();
   Set<Map.Entry<String, Storeable>> dbSet = tableIndexedData.entrySet();
   for (int nDir = 0; nDir < 16; ++nDir) {
     for (int nFile = 0; nFile < 16; ++nFile) {
       if (tableFileModified[nDir][nFile]) {
         if (tableFiles[nDir][nFile] == null) {
           File subDir = new File(tableRootDir, Integer.toString(nDir) + ".dir");
           File subFile = new File(subDir, Integer.toString(nFile) + ".dat");
           if (!subDir.exists()) {
             if (!subDir.mkdir()) {
               throw new IllegalStateException("Sub dir was not created");
             }
           }
           tableFiles[nDir][nFile] = new TableFile(subFile);
         }
         List<TableFile.Entry> fileData = new ArrayList<>();
         Iterator<Map.Entry<String, Storeable>> iter = dbSet.iterator();
         while (iter.hasNext()) {
           Map.Entry<String, Storeable> tempMapEntry = iter.next();
           HashcodeDestination dest = new HashcodeDestination(tempMapEntry.getKey());
           if (dest.getDir() == nDir && dest.getFile() == nFile) {
             fileData.add(
                 new TableFile.Entry(
                     tempMapEntry.getKey(),
                     tableProvider.serialize(this, tempMapEntry.getValue())));
           }
         }
         tableFiles[nDir][nFile].writeEntries(fileData);
         tableFileModified[nDir][nFile] = false;
       }
     }
   }
   tableOnDisk.clear();
   tableOnDisk.putAll(tableIndexedData);
   return numberOfCommittedChanges;
 }
 private void index() {
   File[] subDirsList = tableRootDir.listFiles();
   if (subDirsList != null) {
     for (File subDir : subDirsList) {
       int numberOfSubDir;
       if (subDir.getName().equals(SignatureFile.signatureFileName)) {
         continue;
       }
       if (!subDir.isDirectory()) {
         throw new IllegalStateException("In table root dir found object is not a directory");
       }
       String[] tableSubDirName = subDir.getName().split("[.]");
       try {
         numberOfSubDir = Integer.parseInt(tableSubDirName[0]);
       } catch (NumberFormatException e) {
         throw new IllegalStateException("Table root directory contains not 0.dir ... 15.dir");
       }
       if (numberOfSubDir < 0
           || numberOfSubDir > 15
           || !tableSubDirName[1].equals("dir")
           || tableSubDirName.length != 2) {
         throw new IllegalStateException("Table root directory contains not 0.dir ... 15.dir");
       }
       File[] subFilesList = subDir.listFiles();
       if (subFilesList != null && subFilesList.length == 0) {
         throw new IllegalStateException("data base contains empty dir");
       }
       if (subFilesList != null) {
         for (File subFile : subFilesList) {
           int numberOfSubFile;
           if (!subFile.isFile()) {
             throw new IllegalStateException("In table sub dir found object is not a file");
           }
           String[] dbFileName = subFile.getName().split("[.]");
           try {
             numberOfSubFile = Integer.parseInt(dbFileName[0]);
           } catch (NumberFormatException e) {
             throw new IllegalStateException("Table sub directory contains not 0.dat ... 15.dat");
           }
           if (numberOfSubFile < 0
               || numberOfSubFile > 15
               || !dbFileName[1].equals("dat")
               || dbFileName.length != 2) {
             throw new IllegalStateException("Table sub directory contains not 0.dat ... 15.dat");
           } else if (subFile.length() == 0) {
             throw new IllegalStateException("Empty file in sub dir");
           } else {
             tableFiles[numberOfSubDir][numberOfSubFile] = new TableFile(subFile);
             List<TableFile.Entry> fileData =
                 tableFiles[numberOfSubDir][numberOfSubFile].readEntries();
             for (TableFile.Entry i : fileData) {
               HashcodeDestination dest = new HashcodeDestination(i.getKey());
               if (dest.getFile() != numberOfSubFile || dest.getDir() != numberOfSubDir) {
                 throw new IllegalStateException("Wrong key placement");
               }
               try {
                 tableIndexedData.put(i.getKey(), tableProvider.deserialize(this, i.getValue()));
               } catch (ParseException e) {
                 throw new IllegalStateException("Can't deserialize", e);
               }
             }
           }
         }
       }
     }
   }
   tableOnDisk = new HashMap<>(tableIndexedData);
 }
 /**
  * Возвращает количество ключей в таблице. Возвращает размер текущей версии, с учётом
  * незафиксированных изменений.
  *
  * @return Количество ключей в таблице.
  */
 @Override
 public int size() {
   return tableIndexedData.size();
 }
 /**
  * Получает значение по указанному ключу.
  *
  * @param key Ключ для поиска значения. Не может быть null.
  * @return Значение. Если не найдено, возвращает null.
  * @throws IllegalArgumentException Если значение параметра key является null.
  */
 @Override
 public Storeable get(String key) {
   checkKey(key);
   return tableIndexedData.get(key);
 }