2014-01-04 2 views
4

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

Однако, это выглядит немного медленнее. Требуется около 15 секунд для хэша 8 Мбайт файла. Я не уверен, что это потому, что библиотека медленная, JavaScript медленный, или алгоритм по своей сути медленный. Он использует sha256, который, вероятно, немного переборщит за то, что мне нужно. Скорость ключевая - криптографическая безопасность и коллизии не особенно важны.

Есть ли более быстрый способ сделать это?

$(document).on('drop', function(dropEvent) { 
    dropEvent.preventDefault(); 
    _.each(dropEvent.originalEvent.dataTransfer.files, function(file) { 
     var reader = new FileReader(); 
     var pos = 0; 
     var startTime = +new Date(); 

     var hashObj = new sjcl.hash.sha256(); 

     reader.onprogress = function(progress) { 
      var chunk = new Uint8Array(reader.result).subarray(pos, progress.loaded); 
      hashObj.update(chunk); 
      pos = progress.loaded; 

      if(progress.lengthComputable) { 
       console.log((progress.loaded/progress.total*100).toFixed(1)+'%'); 
      } 
     }; 

     reader.onload = function() { 
      var endTime = +new Date(); 
      console.log('hashed',file.name,'in',endTime-startTime,'ms'); 
      var chunk = new Uint8Array(reader.result, pos); 
      if(chunk.length > 0) hashObj.update(chunk); 
      console.log(sjcl.codec.hex.fromBits(hashObj.finalize())); 
     }; 

     reader.readAsArrayBuffer(file); 
    }); 
}); 

Edit: Просто обнаружил SparkMD5 согласно this answer. Первоначальные тесты показывают, что он работает в течение секунды для одного и того же 8 Мбайт файла, но он все еще медленнее, чем мне хотелось бы.

+1

xxHash рекламирует довольно впечатляющие скорости. –

+1

@JasonLeBrun: Сейчас я пытаюсь [xxHash] (https://github.com/pierrec/js-xxhash). Он не будет принимать «ArrayBuffer» в качестве входного сигнала, что может быть проблематичным. – mpen

ответ

1

SparkMD5 совсем немного быстрее:

var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; 
var chunkSize = 1024 * 1024 * 2; 

$(document).on('drop', function (dropEvent) { 
    dropEvent.preventDefault(); 

    _.each(dropEvent.originalEvent.dataTransfer.files, function (file) { 
     var startTime = +new Date(), elapsed; 
     var chunks = Math.ceil(file.size/chunkSize); 
     var currentChunk = 0; 
     var spark = new SparkMD5.ArrayBuffer(); 
     var fileReader = new FileReader(); 

     var readNextChunk = function() { 
      var start = currentChunk * chunkSize; 
      var end = Math.min(start + chunkSize, file.size); 

      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); 
     }; 

     fileReader.onload = function (e) { 
      console.log("read chunk nr", currentChunk + 1, "of", chunks); 
      spark.append(e.target.result);     // append array buffer 
      ++currentChunk; 

      if (currentChunk < chunks) { 
       readNextChunk(); 
      } else { 
       elapsed = +new Date() - startTime; 
       console.info("computed hash", spark.end(), 'for file', file.name, 'in', elapsed, 'ms'); // compute hash 
      } 
     }; 

     fileReader.onerror = function() { 
      console.warn("oops, something went wrong."); 
     }; 

     readNextChunk(); 
    }); 
}); 
3

xxHash дает 32-битные хэши. Это примерно на 30% быстрее, чем SparkMD5. Он, однако, не работает с HTML5 ArrayBuffer, поэтому файл должен быть прочитан как текст.

var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; 
var chunkSize = 1024 * 1024 * 2; 

$(document).on('drop', function (dropEvent) { 
    dropEvent.preventDefault(); 

    _.each(dropEvent.originalEvent.dataTransfer.files, function (file) { 
     var startTime = +new Date(), elapsed; 
     var chunks = Math.ceil(file.size/chunkSize); 
     var currentChunk = 0; 
     var xxh = XXH(); 
     var fileReader = new FileReader(); 

     var readNextChunk = function() { 
      var start = currentChunk * chunkSize; 
      var end = Math.min(start + chunkSize, file.size); 

      fileReader.readAsText(blobSlice.call(file, start, end)); 
     }; 

     fileReader.onload = function (e) { 
      console.log("read chunk nr", currentChunk + 1, "of", chunks); 
      xxh.update(e.target.result); 
      ++currentChunk; 

      if (currentChunk < chunks) { 
       readNextChunk(); 
      } else { 
       elapsed = +new Date() - startTime; 
       console.info("computed hash", xxh.digest().toString(16), 'for file', file.name, 'in', elapsed, 'ms'); 
      } 
     }; 

     fileReader.onerror = function() { 
      console.warn("oops, something went wrong."); 
     }; 

     readNextChunk(); 
    }); 
}); 

Я думаю blobSlice будет сделать копию файла, который я не очень увлечен. Мне также не нравится рассматривать двоичные данные как текст. Я создал эту альтернативную версию, которая работает с API ArrayBuffer, прорываясь через источник xxHash. Оказывается, только один метод отсутствует, чтобы сделать HTML5's Uint8Array работать как Node.js Buffer.

/** 
* Hack to make Uint8Array work like a Node.js Buffer 
* 
* @param {Buffer} targetBuffer Buffer to copy into 
* @param {Number} targetStart Optional, Default: 0 
* @param {Number} sourceStart Optional, Default: 0 
* @param {Number} sourceEnd Optional, Default: source length 
* @see http://nodejs.org/api/buffer.html#buffer_buf_copy_targetbuffer_targetstart_sourcestart_sourceend 
* @see https://developer.mozilla.org/en-US/docs/Web/API/Uint32Array 
*/ 
Uint8Array.prototype.copy = function(targetBuffer, targetStart, sourceStart, sourceEnd) { 
    targetStart = targetStart || 0; 
    sourceStart = sourceStart || 0; 
    sourceEnd = sourceEnd || this.length; 
    for(var i=sourceStart; i<sourceEnd; ++i) { 
     targetBuffer[targetStart+i] = this[i]; 
    } 
}; 

$(document).on('drop', function(dropEvent) { 
    dropEvent.preventDefault(); 
    _.each(dropEvent.originalEvent.dataTransfer.files, function(file) { 
     var reader = new FileReader(); 
     var pos = 0; 
     var startTime = +new Date(); 
     var xxh = XXH(); 

     reader.onprogress = function(progress) { 
      var length = progress.loaded - pos; 
      var arr = new Uint8Array(reader.result, pos, length); 
      pos += length; 

      xxh.update(arr); 

      if(progress.lengthComputable) { 
       console.log((progress.loaded/progress.total*100).toFixed(1)+'%'); 
      } 
     }; 

     reader.onload = function() { 
      var arr = new Uint8Array(reader.result, pos); 
      xxh.update(arr); 

      var elapsed = +new Date() - startTime; 
      console.info("computed hash", xxh.digest().toString(16), 'for file', file.name, 'in', elapsed, 'ms'); 
     }; 

     reader.readAsArrayBuffer(file); 
    }); 
}); 

К сожалению, они в значительной степени идентичны по скорости и по-прежнему делают копию. Тем не менее, это работает примерно в 270 мс на исходном 8 МБ файле, который намного лучше, чем 15 секунд.

Смежные вопросы