需求:用戶上傳了一個大文件壓縮包,壓縮包是加密的,需要在後臺進行解密操作,文件大小(1G)左右,解密過程需要1分鐘。。。需要提速
ok,直接用java多線程的方式來解決吧,先讀取文件,然後將io流切分,每段io開啓一個線程進行解密,最後按順序將解密後的io片段進行合併
閒話不說,直接上代碼,不需要導入其他jar包
首先是工具
ioutil,就是關閉流而已
package com.noryar.filesystem.util;
import java.io.Closeable;
import java.io.IOException;
/**
* IOUtil.
* @author Leon Lee.
*/
public class IOUtil {
/**
* close IO resource.
* @param closeables :IO resource, implements Closable
* @throws IOException :Exception
*/
public static void close(Closeable... closeables) throws IOException {
if (closeables != null) {
for (Closeable closeable : closeables) {
if (closeable != null) {
closeable.close();
}
}
}
}
/**
* close IO resource. do not throw Exception.
* @param closeables : IO resource, implements Closable
*/
public static void closeQuietly(Closeable... closeables) {
try {
close(closeables);
} catch (IOException e) {
// do nothing
}
}
}
package com.noryar.filesystem.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* SecurityFileUtil.
* @author Leon Lee.
*/
public class SecurityUtil {
/**
* init AES Cipher.
* @param keySource keySource
* @param cipherMode keyMode
* @return Cipher
*/
private static Cipher initAESCipher(final String keySource, final int cipherMode) {
KeyGenerator keyGenerator = null;
Cipher cipher = null;
try {
keyGenerator = KeyGenerator.getInstance("AES");
// init 128 key with import random source[key]
keyGenerator.init(128, new SecureRandom(keySource.getBytes()));
// create key
SecretKey secretKey = keyGenerator.generateKey();
// get key of byte[]
byte[] codeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(codeFormat, "AES");
cipher = Cipher.getInstance("AES");
cipher.init(cipherMode, key);
} catch (Exception e) {
e.printStackTrace();
}
return cipher;
}
/**
* encryptFile.<br>
* decryptFile will named [en+source file name]. And in same folder
* @param sourceFile sourceFile
* @param keySource keySource
* @return boolean
*/
public static boolean encryptFile(final File sourceFile, final String keySource) {
boolean flag = false;
String path = sourceFile.getAbsolutePath();
InputStream inputStream = null;
OutputStream outputStream = null;
CipherInputStream cipherInputStream = null;
try {
inputStream = new FileInputStream(sourceFile);
File encrypfile = new File(path.replaceFirst(sourceFile.getName(), "en"+sourceFile.getName()));
outputStream = new FileOutputStream(encrypfile);
Cipher cipher = initAESCipher(keySource, Cipher.ENCRYPT_MODE);
cipherInputStream = new CipherInputStream(inputStream, cipher);
byte[] cache = new byte[1024];
int nRead = 0;
while ((nRead = cipherInputStream.read(cache)) != -1) {
outputStream.write(cache, 0, nRead);
outputStream.flush();
}
flag = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (cipherInputStream != null) {
cipherInputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return flag;
}
/**
* decryptFile.
* @param encryptFile encryptFile
* @param decryptFile decryptFile
* @param keySource keySource
* @return decryptFile
*/
public static File decryptFile(final File encryptFile, final File decryptFile, final String keySource) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
Cipher cipher = initAESCipher(keySource, Cipher.DECRYPT_MODE);
inputStream = new FileInputStream(encryptFile);
outputStream = new FileOutputStream(decryptFile);
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
byte[] buffer = new byte[1024];
int r;
while ((r = inputStream.read(buffer)) >= 0) {
cipherOutputStream.write(buffer, 0, r);
}
cipherOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return decryptFile;
}
/**
* decryptFileAsByte[] input InputStream.
* @param sourceFile sourceFile
* @param keySource keySource
* @return byte[]
*/
public static byte[] decryptFile(final InputStream inputStream, final String keySource) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
Cipher cipher = initAESCipher(keySource, Cipher.DECRYPT_MODE);
CipherOutputStream cipherOutputStream = new CipherOutputStream(bos, cipher);
byte[] buffer = new byte[1024];
int r;
while ((r = inputStream.read(buffer)) >= 0) {
cipherOutputStream.write(buffer, 0, r);
}
cipherOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
bos.close();
} catch (IOException e) {
}
}
return bos.toByteArray();
}
}
多線程解密類
package com.noryar.filesystem.thread;
import java.io.InputStream;
import com.noryar.filesystem.util.SecurityUtil;
/**
* Parallel decrypt thread.<br>
* run every thread must create new Instance
* @author Leon Lee.
*/
public class ParallelDecryptThread implements Runnable{
private InputStream in;
private byte[] byteArray;
public byte[] getByteArray() {
return byteArray.clone();
}
private String keySource;
public ParallelDecryptThread(InputStream in, String keySource) {
this.in = in;
this.keySource = keySource;
}
public void run() {
byteArray = SecurityUtil.decryptFile(in, keySource);
}
}
OK,最後上測試類
package com.noryar.test;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.noryar.filesystem.thread.ParallelDecryptThread;
import com.noryar.filesystem.util.IOUtil;
import com.noryar.filesystem.util.SecurityUtil;
/**
* decrypt test.
* @author Leon Lee.
*/
public class Test {
/**
* do test.
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws IOException,
InterruptedException {
// encrypt file
// encryptFile();
// decrypt file with parallel
parallelDecryptTest();
System.out.println("############");
// decrypt file with single thread
singleDecryptTest();
}
/**
* encrypt file.
*/
public static void encryptFile(){
File sourceFile = new File("/Users/leon/Downloads/mysql.zip");
SecurityUtil.encryptFile(sourceFile, "123");
}
/**
* ParallelDecryptTest.
* @throws IOException
* @throws InterruptedException
*/
public static void parallelDecryptTest() throws IOException,
InterruptedException {
File encryptFile = new File("/Users/leon/Downloads/enmysql.zip");
File decryptFile = new File("/Users/leon/Downloads/demysql.zip");
System.out.println("parallel decrypt start");
long start = System.currentTimeMillis();
// init rule.
long cut = 30 * 1024 * 1024; // 30MB;
long srcSize = encryptFile.length();
int number = (int) (srcSize / cut);
number = srcSize % cut == 0 ? number : number + 1;
System.out.println("create thread num: " + number);
Thread[] allThread = new Thread[number];
ParallelDecryptThread[] pdu = new ParallelDecryptThread[number];
// IO start, with thread
InputStream is = new FileInputStream(encryptFile);
ByteArrayOutputStream baos = null;
byte[] rb = null;
for (int i = 0; i < number; i++) {
rb = new byte[1024];
baos = new ByteArrayOutputStream();
int len = -1;
int cnt = 0;
while ((len = is.read(rb)) != -1) {
baos.write(rb, 0, len);
cnt += len;
if (cnt >= cut) {
pdu[i] = new ParallelDecryptThread(
new ByteArrayInputStream(baos.toByteArray()), "123");
allThread[i] = new Thread(pdu[i]);
allThread[i].start();
break;
}
}
// last
if (i + 1 == number && cnt < cut) {
pdu[i] = new ParallelDecryptThread(new ByteArrayInputStream(
baos.toByteArray()), "123");
allThread[i] = new Thread(pdu[i]);
allThread[i].start();
}
}
IOUtil.close(is, baos);
// Thread join
for (Thread tr : allThread) {
tr.join();
}
// write
OutputStream os = new BufferedOutputStream(new FileOutputStream(
decryptFile));
rb = new byte[1024];
InputStream lastIn = null;
for (ParallelDecryptThread parallelDecryptUtil : pdu) {
lastIn = new ByteArrayInputStream(
parallelDecryptUtil.getByteArray());
while (lastIn.read(rb) != -1) {
os.write(rb);
}
}
IOUtil.close(lastIn, os);
System.out.println("parallel decrypt end, cost " + (System.currentTimeMillis() - start)
/ 1000 + " seconds");
}
/**
* SingleDecryptTest.
*/
public static void singleDecryptTest() {
File encryptFile = new File("/Users/leon/Downloads/enmysql.zip");
File decryptFile = new File("/Users/leon/Downloads/demysql.zip");
System.out.println("single decrypt start");
long start = System.currentTimeMillis();
SecurityUtil.decryptFile(encryptFile, decryptFile, "123");
System.out.println("single decrypt end, cost " + (System.currentTimeMillis() - start)
/ 1000 + " seconds");
}
}
需要注意的是,這裏的多線程解密方式是將文件分段成字節數組流進行解密,整個過程完全在內存中進行,如果文件太大,或者併發量大,或者線程切分數量太少(字節數組太大),均非常容易出現內存溢出,即使增加jvm內存大小也不能很好解決,因此可以嘗試將文件輸入流切分以後輸出成小文件,然後解密的時候讀取所有分段文件進行解密,解密完成以後刪除分段文件~
另一方面,需要合理考慮io流的切分數量,考慮服務區硬盤是否爲機械硬盤或固態,否則容易出現多線程解密反而更耗時的情況。
接下來附上本人測試結果,分別爲機械硬盤和固態硬盤結果,測試文件爲mysql安裝包,打成了zip,文件大小在 350 MB左右。
機械硬盤測試結果:
parallel decrypt start
create thread num: 12
parallel decrypt end, cost 4 seconds
############
single decrypt start
single decrypt end, cost 8 seconds
固態硬盤測試結果:
parallel decrypt start
create thread num: 12
parallel decrypt end, cost 2 seconds
############
single decrypt start
single decrypt end, cost 1 seconds