Обновлять данные только по разнице между файлами (дельта для java)

ОБНОВЛЕНИЕ. Я решил проблему с помощью отличной внешней библиотеки — https://code.google.com/p/xdeltaencoder/. То, как я это сделал, опубликовано ниже как принятый ответ

Представьте, что у меня есть два отдельных компьютера, каждый из которых имеет одинаковый byte[] A.

Один из компьютеров создает byte[] B, который почти идентичен byte[] A, но является «более новой» версией.

Чтобы второй компьютер обновил свою копию байта [] A до последней версии (байт [] B), мне нужно передать весь байт [] B второму компьютеру. Если byte[] B имеет размер в несколько ГБ, это займет слишком много времени.

Можно ли создать байт [] C, который является «разницей» между байтом [] A и байтом [] B? Требования к byte[] C заключаются в том, что, зная byte[] A, можно создать byte[] B.

Таким образом, мне нужно будет передать только byte[] C на второй ПК, что теоретически будет лишь частью размера byte[] B.

Я ищу решение этой проблемы в Java.

Большое спасибо за любую помощь, которую вы можете предоставить :)

РЕДАКТИРОВАТЬ: характер обновлений данных в большинстве случаев заключается в том, что дополнительные байты вставляются в части массива. Конечно, возможно, что некоторые байты будут изменены или некоторые байты удалены. сам byte[] представляет собой дерево имен всех файлов/папок на целевом компьютере. byte[] изначально создается путем создания дерева пользовательских объектов, их упорядочения с помощью JSON и последующего сжатия этих данных с помощью алгоритма zip. Я изо всех сил пытаюсь создать алгоритм, который может разумно создавать объект c.

РЕДАКТИРОВАТЬ 2: Большое спасибо за всю помощь, которую все здесь оказали, и я прошу прощения за то, что не был активен в течение такого долгого времени. Скорее всего, я попытаюсь получить внешнюю библиотеку для выполнения дельта-кодирования за меня. Самое замечательное в этой теме то, что теперь я знаю, чего я хочу добиться, называется! Я считаю, что когда я найду подходящее решение, я опубликую его и приму, чтобы другие могли увидеть, как я решил свою проблему. Еще раз большое спасибо за вашу помощь.


person SuperMrBlob    schedule 24.01.2014    source источник
comment
Похоже, вам просто нужно передать набор пар (index,newValue), который будет меньше, чем полный список, предполагающий небольшое количество изменений. Должна ли передача быть байтом [], основная проблема заключается в том, что байт не может содержать числа, достаточно большие для хранения старших индексов. Не могли бы вы использовать сериализованную коллекцию для передачи?   -  person Richard Tingle    schedule 24.01.2014
comment
Итак, вы хотите создать дельту между A и B. Проблема заключается в том, чтобы выяснить, как сопоставить позицию x в C с A. То есть дельта (C) будет только длиной числовых байтов, которые изменились, но как вы затем сопоставите это обратно с правильной позицией в A. Вы можете ввести информацию о позиции в C, так что x будет позицией, а x+1 будет фактическими данными... например. Затем вам нужно будет рассмотреть кодирование длины прогона (так что у вас будет x = позиция, x+1 - длина, затем x+2+length - данные... Или вы можете запустить все это через ZipStream   -  person MadProgrammer    schedule 24.01.2014
comment
@RichardTingle Это очень хорошее замечание о том, что byte недостаточно велико, не думал об этом ... вы можете сделать некоторый битовый сдвиг (это правильный термин) и использовать 4/8 байтов на позицию или что-то в этом роде ... примерно здесь я думаю, что должен быть более простой способ сделать это...   -  person MadProgrammer    schedule 24.01.2014
comment
Хорошо написанный вопрос.   -  person Lopsided    schedule 25.09.2016


Ответы (4)


Использование набора «событий изменения» вместо отправки всего массива

Решением этого может быть повторная отправка сериализованного объекта, описывающего изменение, а не фактического массива.

public class ChangePair implements Serializable{
    //glorified struct
    public final int index;
    public final  byte newValue;

    public ChangePair(int index, byte newValue) {
        this.index = index;
        this.newValue = newValue;
    }

    public static void main(String[] args){

        Collection<ChangePair> changes=new HashSet<ChangePair>();

        changes.add(new ChangePair(12,(byte)2));
        changes.add(new ChangePair(1206,(byte)3));

    }
}

Генерация «событий изменения»

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

public static Collection<ChangePair> generateChangeCollection(byte[] oldValues, byte[] newValues){
    //validation
    if (oldValues.length!=newValues.length){
        throw new RuntimeException("new and old arrays are differing lengths");
    }

    Collection<ChangePair> changes=new HashSet<ChangePair>();

    for(int i=0;i<oldValues.length;i++){
        if (oldValues[i]!=newValues[i]){
            //generate a change event
            changes.add(new ChangePair(i,newValues[i]));
        }
    }

    return changes;
}

Отправка и получение этих событий изменений

В соответствии с этим ответом относительно отправки сериализованных объектов через Интернет вы можете отправить свой объект, используя следующий код

Collection<ChangePair> changes=generateChangeCollection(oldValues,newValues);

Socket s = new Socket("yourhostname", 1234);
ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
out.writeObject(objectToSend);
out.flush();

На другом конце вы получите объект

ServerSocket server = new ServerSocket(1234);
Socket s = server.accept();
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
Collection<ChangePair> objectReceived = (Collection<ChangePair>) in.readObject();
//use Collection<ChangePair> to apply changes

Использование этих событий изменения

Затем эту коллекцию можно просто использовать для изменения массива байтов на другом конце.

public static void useChangeCollection(byte[] oldValues, Collection<ChangePair> changeEvents){
    for(ChangePair changePair:changeEvents){
        oldValues[changePair.index]=changePair.newValue;
    }
}   
person Richard Tingle    schedule 24.01.2014
comment
Любые идеи об алгоритме, который мог бы определять объекты парных изменений? - person SuperMrBlob; 24.01.2014
comment
@SuperMrBlob при условии, что вы не можете их сгенерировать, когда вносите изменения в первый массив; грубое прохождение через массив в поисках изменений между ними, вероятно, будет быстрее, чем вы думаете - person Richard Tingle; 24.01.2014
comment
Однако я не знаю, как создать алгоритм, который мог бы перебирать массив в поисках изменений. Вот почему я ищу помощи :D - person SuperMrBlob; 26.01.2014
comment
@SuperMrBlob Ах, честно говоря, сейчас 2 часа ночи, но я посмотрю, что я могу сделать завтра - person Richard Tingle; 26.01.2014
comment
@SuperMrBlob добавил пару разделов: Генерация «событий изменений» и Использование этих событий изменений, которые должны охватывать эти проблемы. - person Richard Tingle; 26.01.2014

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

Если вы не можете регистрировать изменения, вам нужно локально удвоить массив или (что не так на 100% безопасно) использовать массив контрольных сумм для блоков.

person Joop Eggen    schedule 24.01.2014
comment
К сожалению, в моем случае новый byte[] создается с нуля. D: VCS звучит очень красиво, хотя я не уверен, как реализовать это на Java. Есть ли библиотеки, которые могут помочь? - person SuperMrBlob; 24.01.2014
comment
Некоторые любят JGit (git) и SVNKit. Вопрос в том, подходит ли VCS: у вас, похоже, один огромный бинарный файл. - person Joop Eggen; 24.01.2014
comment
Да, это правильно, у меня есть один огромный двоичный файл, который мне нужно периодически обновлять. Хотя мне не нужно отслеживать изменения. Не будет ли VCS немного излишним для моих целей? - person SuperMrBlob; 26.01.2014
comment
Да, у VCS есть балласт. Команды Unix diff для создания файла различий и patch для применения файла различий были бы интересны для текста. В зависимости от вида изменений может быть интересна бинарная версия. - person Joop Eggen; 27.01.2014

Основная проблема здесь — сжатие данных.

Kamikaze предлагает хорошие алгоритмы сжатия массивов данных. Он использует кодировку Simple16 и PForDelta. Simple16 — хороший и (как следует из названия) простой вариант сжатия списка. Или вы можете использовать Выполнить Длина Кодировка< /а>. Или вы можете поэкспериментировать с любым доступным алгоритмом сжатия в Java...

В любом случае, любой используемый вами метод будет оптимизирован, если вы предварительно обработаете данные.

Вы можете уменьшить различия в вычислении данных или, как указал @RichardTingle, создать пары разных местоположений данных.

Вы можете рассчитать C как B - A. A должен быть массивом int, так как разница между двумя значениями byte может быть больше, чем 255. Затем вы можете восстановить B как A + C.

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

Например. если вы используете метод разницы с A = { 1, 2, 3, 4, 5, 6, 7 } и B = { 1, 2, 3, 5, 6, 7, 7 }. Разностный массив C будет { 0, 0, 0, 1, 1, 1, 0 }. RLE может очень эффективно сжимать C, так как он хорош для сжатия данных, когда у вас есть много повторяющихся чисел в последовательности.

Использование разностного метода с Simple16 будет хорошо, если ваши данные меняются почти в каждой позиции, но разница между значениями невелика. Он может сжимать массив из 28 однобитовых значений (0 или 1) или массив из 14 двухбитных значений в одно 32-байтовое целое число.

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


РЕДАКТИРОВАТЬ: вам нужно будет предварительно обработать данные перед сжатием JSON и zip.

Создайте два набора old и now. Последний содержит все файлы, которые существуют сейчас. Для первых, старых файлов, у вас есть как минимум два варианта:

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

  • Содержит все файлы с момента последней проверки изменений. Вы можете сохранить локальную историю изменений и присвоить каждой версии «id». Затем при синхронизации вы отправляете «идентификатор версии» вместе с измененными данными на другой компьютер. В следующий раз другой ПК сначала отправляет свой «идентификатор версии» (или вы храните «идентификатор версии» каждого ПК локально), затем вы можете отправить другому ПК все новые изменения (все версии, которые идут после той, которую ПК было).

Изменения могут быть представлены двумя другими наборами: newFiles и deleted файлов. (А как насчет файлов с измененным содержимым? Их тоже не нужно синхронизировать?) newFiles содержит файлы, которые существуют только в наборе now (и не существуют в old). Набор deleted содержит файлы, которые существуют только в наборе old (и не существуют в наборе now).

Если вы представляете каждый файл как String с полным именем пути, вы безопасно будете иметь уникальные представления каждого файла. Или вы можете использовать java.io.File.

После того, как вы сократили свои изменения до набора файлов newFiles и deleted, вы можете преобразовать их в JSON, заархивировать и сделать что-нибудь еще для сериализации и сжатия данных.

person ericbn    schedule 24.01.2014
comment
Хорошая идея, но представьте себе это. А = [10,5,7,9,26]. В = [10,2,5,7,9,26]. Вставка всего одного байта раньше в массив испортит все последующие данные. С этим набором данных C = [0,-3,-2,-2,-17,26], что совершенно бесполезно. Более интеллектуальный алгоритм мог бы определить, что данные просто переместились на одну позицию вправо (что-то вроде строк [вставьте 2 в индексе 1]. Однако у меня возникли проблемы с написанием такого алгоритма. Есть идеи? - person SuperMrBlob; 24.01.2014
comment
@SuperMrBlob, могут ли значения повторяться в списке или нет (как упорядоченный набор)? Было бы полезно, если бы вы описали все операции, которые можно выполнить для изменения списка (например, вставка элемента в любую позицию). - person ericbn; 25.01.2014
comment
@SuperMrBlob, можем ли мы узнать, что именно представляют эти байты? - person ericbn; 25.01.2014
comment
Подробности смотрите в отредактированном первом сообщении. Байт[] просто представляет структуру файла/каталога на целевом компьютере. Это похоже на то, когда вы заходите в cmd и вводите dir /s. Таким образом, в основном ожидается, что он не сильно изменится, но такие операции, как добавление нового файла, удаление файла или изменение имени файла, будут происходить почти всегда. Значения определенно могут повторяться - если папка имеет такое же имя, то вполне вероятно, что они будут повторяться. - person SuperMrBlob; 26.01.2014
comment
@SuperMrBlob, я отредактировал свой ответ на основе новой информации, которую вы предоставили. На самом деле вам не нужно заботиться о byte[], потому что это уже сжатые данные, и их нельзя изменять напрямую (они могут стать недействительными сжатыми данными). Вы должны позаботиться о том, что вы можете сделать до сжатия! - person ericbn; 27.01.2014

Итак, в итоге я использовал это:

https://code.google.com/p/xdeltaencoder/

Судя по моему тесту, работает очень хорошо. Тем не менее, вам нужно убедиться, что контрольная сумма исходного кода (в моем случае это fileAJson), так как он не делает это автоматически!

В любом случае, код ниже:

//Create delta
String[] deltaArgs = new String[]{fileAJson.getAbsolutePath(), fileBJson.getAbsolutePath(), fileDelta.getAbsolutePath()};
XDeltaEncoder.main(deltaArgs);

//Apply delta
deltaArgs = new String[]{"-d", fileAJson.getAbsolutePath(), fileDelta.getAbsolutePath(), fileBTarget.getAbsolutePath()};
XDeltaEncoder.main(deltaArgs);

//Trivia, Surpisingly this also works
deltaArgs = new String[]{"-d", fileBJson.getAbsolutePath(), fileDelta.getAbsolutePath(), fileBTarget.getAbsolutePath()};
XDeltaEncoder.main(deltaArgs);
person SuperMrBlob    schedule 09.02.2014