多線程io分離與合併(大文件壓縮包解密)

需求:用戶上傳了一個大文件壓縮包,壓縮包是加密的,需要在後臺進行解密操作,文件大小(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


發佈了37 篇原創文章 · 獲贊 16 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章