자바 NIO 큰 파일 비교(win 7 과 mac)

테스트 설명
2G 파일 을 쓰 고 일괄 적 으로 기록 하 며 일괄 적 으로 128 MB 를 기록 합 니 다.
각각 Win 7 시스템(3G 메모리,쌍 핵,32 비트,T 시리즈 프로세서)과 MacOS 시스템(8G 메모리,4 핵,64 비트,i7 시리즈 프로세서)에서 테스트 를 실행 합 니 다.이론 적 으로 하 드 디스크 의 유형 과 배치 와 도 관계 가 있 으 므 로 여 기 는 더 이상 붙 이지 않 습 니 다.
테스트 코드

package rwbigfile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.ReadableByteChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

import util.StopWatch;

/**
 * NIO      
 * @author Will
 * 
 */
public class WriteBigFileComparison {

	// data chunk be written per time
	private static final int DATA_CHUNK = 128 * 1024 * 1024; 

	// total data size is 2G
	private static final long LEN = 2L * 1024 * 1024 * 1024L; 

	
	public static void writeWithFileChannel() throws IOException {
		File file = new File("e:/test/fc.dat");
		if (file.exists()) {
			file.delete();
		}

		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel fileChannel = raf.getChannel();

		byte[] data = null;
		long len = LEN;
		ByteBuffer buf = ByteBuffer.allocate(DATA_CHUNK);
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");

			buf.clear(); // clear for re-write
			data = new byte[DATA_CHUNK];
			for (int i = 0; i < DATA_CHUNK; i++) {
				buf.put(data[i]);
			}

			data = null;

			buf.flip(); // switches a Buffer from writing mode to reading mode
			fileChannel.write(buf);
			fileChannel.force(true);

			len -= DATA_CHUNK;
		}

		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");
			buf = ByteBuffer.allocateDirect((int) len);
			data = new byte[(int) len];
			for (int i = 0; i < len; i++) {
				buf.put(data[i]);
			}

			buf.flip(); // switches a Buffer from writing mode to reading mode, position to 0, limit not changed
			fileChannel.write(buf);
			fileChannel.force(true);
			data = null;
		}

		fileChannel.close();
		raf.close();
	}

	/**
	 * write big file with MappedByteBuffer
	 * @throws IOException
	 */
	public static void writeWithMappedByteBuffer() throws IOException {
		File file = new File("e:/test/mb.dat");
		if (file.exists()) {
			file.delete();
		}

		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel fileChannel = raf.getChannel();
		int pos = 0;
		MappedByteBuffer mbb = null;
		byte[] data = null;
		long len = LEN;
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");

			mbb = fileChannel.map(MapMode.READ_WRITE, pos, DATA_CHUNK);
			data = new byte[DATA_CHUNK];
			mbb.put(data);

			data = null;

			len -= DATA_CHUNK;
			pos += DATA_CHUNK;
		}

		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");

			mbb = fileChannel.map(MapMode.READ_WRITE, pos, len);
			data = new byte[(int) len];
			mbb.put(data);
		}

		data = null;
		unmap(mbb);  // release MappedByteBuffer
		fileChannel.close();
	}
	
	public static void writeWithTransferTo() throws IOException {
		File file = new File("e:/test/transfer.dat");
		if (file.exists()) {
			file.delete();
		}
		
		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel toFileChannel = raf.getChannel();
		
		long len = LEN;
		byte[] data = null;
		ByteArrayInputStream bais = null;
		ReadableByteChannel fromByteChannel = null;
		long position = 0;
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");
			
			data = new byte[DATA_CHUNK];
			bais = new ByteArrayInputStream(data);
			fromByteChannel = Channels.newChannel(bais);
			
			long count = DATA_CHUNK;
			toFileChannel.transferFrom(fromByteChannel, position, count);
			
			data = null;
			position += DATA_CHUNK;
			len -= DATA_CHUNK;
		}
		
		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");

			data = new byte[(int) len];
			bais = new ByteArrayInputStream(data);
			fromByteChannel = Channels.newChannel(bais);
			
			long count = len;
			toFileChannel.transferFrom(fromByteChannel, position, count);
		}
		
		data = null;
		toFileChannel.close();
		fromByteChannel.close();
	}
	
	/**
	 *  MappedByteBuffer                 jvm crash,           
	 *                ,  crash    。                 
	 *            
	 * @param mappedByteBuffer
	 */
	public static void unmap(final MappedByteBuffer mappedByteBuffer) {
		try {
			if (mappedByteBuffer == null) {
				return;
			}
			
			mappedByteBuffer.force();
			AccessController.doPrivileged(new PrivilegedAction<Object>() {
				@Override
				@SuppressWarnings("restriction")
				public Object run() {
					try {
						Method getCleanerMethod = mappedByteBuffer.getClass()
								.getMethod("cleaner", new Class[0]);
						getCleanerMethod.setAccessible(true);
						sun.misc.Cleaner cleaner = 
								(sun.misc.Cleaner) getCleanerMethod
									.invoke(mappedByteBuffer, new Object[0]);
						cleaner.clean();
						
					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println("clean MappedByteBuffer completed");
					return null;
				}
			});

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		StopWatch sw = new StopWatch();
		
		sw.startWithTaskName("write with file channel's write(ByteBuffer)");
		writeWithFileChannel();
		sw.stopAndPrint();
		
		sw.startWithTaskName("write with file channel's transferTo");
		writeWithTransferTo();
		sw.stopAndPrint();
		
		sw.startWithTaskName("write with MappedByteBuffer");
		writeWithMappedByteBuffer();
		sw.stopAndPrint();
	}

}
테스트 결과(Y 축 은 소모 시간 초)

분명히 writeWith MappedByteBuffer 방식 의 성능 이 가장 좋 고 하드웨어 설정 이 비교적 높 은 상황 에서 장점 이 더욱 뚜렷 하 다
  • 하드웨어 설정 이 낮은 상황 에서 writeWith TransferTo 는 writeWith FileChannel 보다 성능 이 약간 좋 습 니 다
  • 하드웨어 설정 이 비교적 높 은 상황 에서 writeWithTransferTo 와 writeWithFileChannel 의 성능 은 기본적으로 같다
  • 그 밖 에 writeWith MappedByteBuffer 방식 은 JVM 더 미 를 사용 하 는 것 외 에 추가 네 이 티 브 메모리(Direct Byte Buffer 메모리)를 사용 해 야 합 니 다
  • 메모리 맵 파일 사용 경험 치
    MappedByteBuffer 는'두 배'메모리(대상 JVM 더미 메모리 와 Direct Byte Buffer 메모리)를 사용 해 야 합 니 다.-XX:MaxDirectMemory Size 매개 변 수 를 통 해 후자 의 최대 크기 를 설정 할 수 있 습 니 다.
    MappedByteBuffer 의 force()방법 을 자주 호출 하지 마 십시오.이 방법 은 OS 가 메모리 에 있 는 데 이 터 를 디스크 에 새로 고침 하도록 강제 하기 때문에 약간의 성능 향상(IO 방식 에 비해)만 얻 을 수 있 습 니 다.뒤의 코드 인 스 턴 스 로 정시,정량 새로 고침 을 할 수 있 습 니 다.
    갑자기 전기 가 끊 기거 나 서버 가 갑자기 다운 되면 메모리 맵 파일 데이터 가 디스크 에 기록 되 지 않 았 을 수도 있 습 니 다.이 때 데 이 터 를 잃 어 버 립 니 다.이러한 위험 을 낮 추기 위해 MappedByteBuffer 로 초대형 파일 을 쓰 는 것 을 피하 고 큰 파일 을 몇 개의 작은 파일 로 나 눌 수 있 지만 너무 작 으 면 안 됩 니 다(그렇지 않 으 면 성능 우 위 를 잃 게 됩 니 다)
    ByteBuffer 의 rewind()방법 은 position 속성 을 0 으로 설정 하기 때문에 buffer 의 데 이 터 를 다시 읽 을 수 있 습 니 다.limit 속성 이 변 하지 않 기 때문에 읽 을 수 있 는 바이트 수 는 변 하지 않 습 니 다.
    ByteBuffer 의 flip()방법 은 Buffer 를 쓰기 모드 에서 읽 기 모드 로 전환 합 니 다.
    ByteBuffer 의 clear()와 compact()는 ByteBuffer 의 데 이 터 를 읽 은 후에 다시 쓰기 모드 를 자 를 수 있 습 니 다.다른 것 은 clear()가 position 를 0,limit 을 capacity 로 설정 한 다 는 것 입 니 다.다시 말 하면 Buffer 는 비 워 졌 지만 Buffer 내 데 이 터 는 비 워 지지 않 았 습 니 다.Buffer 에 읽 히 지 않 은 데이터 가 있다 면 clear()를 호출 하면 이 데 이 터 는'잊 혀 짐'되 고 다시 쓰 면 읽 히 지 않 은 데 이 터 를 덮어 씁 니 다.compcat()를 호출 한 후에 도 읽 지 않 은 데 이 터 는 보존 할 수 있 습 니 다.읽 지 않 은 데 이 터 를 Buffer 의 왼쪽 에 복사 한 다음 position 를 읽 지 않 은 데이터 에 이 어 limit 를 capacity 로 설정 하고 읽 지 않 은 데 이 터 는 덮어 쓰 지 않 습 니 다.
    디스크 에 메모리 맵 파일 을 정기 적 으로 새로 고 칩 니 다.
    
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class MappedFile {
    	
    	//    
    	private String fileName;
    
    	//         
    	private String fileDirPath;
    
    	//     
    	private File file;
    
    	private MappedByteBuffer mappedByteBuffer;
    	private FileChannel fileChannel;
    	private boolean boundSuccess = false;
    
    	//        50MB
    	private final static long MAX_FILE_SIZE = 1024 * 1024 * 50;
    	
    	//        512KB,           
    	private long MAX_FLUSH_DATA_SIZE = 1024 * 512;
    
    	//       ,           
    	private long MAX_FLUSH_TIME_GAP = 1000;
    
    	//       
    	private long writePosition = 0;
    
    	//           
    	private long lastFlushTime;
    
    	//          
    	private long lastFlushFilePosition = 0;
    	
    	public MappedFile(String fileName, String fileDirPath) {
    		super();
    		this.fileName = fileName;
    		this.fileDirPath = fileDirPath;
    		this.file = new File(fileDirPath + "/" + fileName);
    		if (!file.exists()) {
    			try {
    				file.createNewFile();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    	}
    
    	/**
    	 * 
    	 *         
    	 * @return
    	 */
    	public synchronized boolean boundChannelToByteBuffer() {
    		try {
    			RandomAccessFile raf = new RandomAccessFile(file, "rw");
    			this.fileChannel = raf.getChannel();
    		} catch (Exception e) {
    			e.printStackTrace();
    			this.boundSuccess = false;
    			return false;
    		}
    
    		try {
    			this.mappedByteBuffer = this.fileChannel
    					.map(FileChannel.MapMode.READ_WRITE, 0, MAX_FILE_SIZE);
    		} catch (IOException e) {
    			e.printStackTrace();
    			this.boundSuccess = false;
    			return false;
    		}
    
    		this.boundSuccess = true;
    		return true;
    	}
    	
    	/**
    	 *    :             
    	 * @param data
    	 * @return
    	 */
    	public synchronized boolean writeData(byte[] data) {
    		
    		return false;
    	}
    	
    	/**
    	 *          
    	 * @param data
    	 * @return
    	 * @throws Exception
    	 */
    	public synchronized boolean appendData(byte[] data) throws Exception {
    		if (!boundSuccess) {
    			boundChannelToByteBuffer();
    		}
    		
    		writePosition = writePosition + data.length;
    		if (writePosition >= MAX_FILE_SIZE) {  //     data         ,   
    			flush();
    			writePosition = writePosition - data.length;
    			System.out.println("File=" 
    								+ file.toURI().toString() 
    								+ " is written full.");
    			System.out.println("already write data length:" 
    								+ writePosition
    								+ ", max file size=" + MAX_FILE_SIZE);
    			return false;
    		}
    
    		this.mappedByteBuffer.put(data);
    
    		//                
    		if ( (writePosition - lastFlushFilePosition > this.MAX_FLUSH_DATA_SIZE)
    			 ||
    			 (System.currentTimeMillis() - lastFlushTime > this.MAX_FLUSH_TIME_GAP
    			 && writePosition > lastFlushFilePosition) ) {
    			flush();  //     
    		}
    		
    		return true;
    	}
    
    	public synchronized void flush() {
    		this.mappedByteBuffer.force();
    		this.lastFlushTime = System.currentTimeMillis();
    		this.lastFlushFilePosition = writePosition;
    	}
    
    	public long getLastFlushTime() {
    		return lastFlushTime;
    	}
    
    	public String getFileName() {
    		return fileName;
    	}
    
    	public String getFileDirPath() {
    		return fileDirPath;
    	}
    
    	public boolean isBundSuccess() {
    		return boundSuccess;
    	}
    
    	public File getFile() {
    		return file;
    	}
    
    	public static long getMaxFileSize() {
    		return MAX_FILE_SIZE;
    	}
    
    	public long getWritePosition() {
    		return writePosition;
    	}
    
    	public long getLastFlushFilePosition() {
    		return lastFlushFilePosition;
    	}
    
    	public long getMAX_FLUSH_DATA_SIZE() {
    		return MAX_FLUSH_DATA_SIZE;
    	}
    
    	public long getMAX_FLUSH_TIME_GAP() {
    		return MAX_FLUSH_TIME_GAP;
    	}
    
    }
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기