Java IO & NIO 성능 테스트
Open Study and Java 모임에서 세미나 했던 내용이다.
IO와 NIO를 테스트하는 간단한 예제 소스이다.
앞선 COPY 예제에서는 IO와 NIO의 차이가 극명히 나타나지는 않지만, MP3를 잘라내는 프로그램에서는 성능 차이가 크게는 5~6배까지 나기도 한다.
Watch.java
{
private long start = 0L;
private long end = 0L;
public Watch()
{}
public void click()
{
if(start != 0L)
end = System.nanoTime();
else
start = System.nanoTime();
}
public double doubleSec()
{
return (double)(end - start)/1000000000;
}
public void showTime()
{
System.out.println("구동시간 : "+doubleSec()+"sec");
}
public static void main(String[] args) throws Exception
{
Watch w = new Watch();
w.click();
Thread.sleep(1000);
w.click();
w.showTime();
}
}
COPY_IO.java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class COPY_IO
{
private FileInputStream in = null;
private FileOutputStream out = null;
private String sourcePath = null;
private String destPath = null;
private Watch watch = null;
public COPY_IO(String _sourcePath, String _destPath) throws IOException
{
sourcePath = _sourcePath;
destPath = _destPath;
in = new FileInputStream(sourcePath);
out = new FileOutputStream(destPath);
watch = new Watch();
}
public void copy() throws IOException
{
byte[] buf = new byte[4096];
int readBytes = 0x00;
watch.click(); // 복사 시작
while((readBytes = in.read(buf)) > -1)
out.write(buf, 0, readBytes);
watch.click(); // 복사 종료
if(in != null) { in.close(); in = null; }
if(out != null) { out.close(); out = null; }
}
public void showResult()
{
File source = new File(sourcePath);
File dest = new File(destPath);
System.out.println("원 본 : 파일명 - "+source.getName()+"\t파일크기 - "+dest.length());
System.out.println("복사본 : 파일명 - "+dest.getName()+"\t파일크기 - "+dest.length());
System.out.println("\n복사시간 : "+watch.doubleSec()+"sec");
}
public static void main(String[] args) throws Exception
{
COPY_IO test = new COPY_IO("E:\\edit\\source", "E:\\edit\\dest_io");
test.copy();
test.showResult();
}
}
COPY_NIO.java
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class COPY_NIO
{
private FileChannel in = null;
private FileChannel out = null;
private String sourcePath = null;
private String destPath = null;
private Watch watch = null;
public COPY_NIO(String _sourcePath, String _destPath) throws IOException
{
sourcePath = _sourcePath;
destPath = _destPath;
in = new RandomAccessFile(sourcePath, "r").getChannel();
out = new RandomAccessFile(destPath, "rw").getChannel();
watch = new Watch();
}
public void copy() throws IOException
{
watch.click(); // 복사 시작
in.transferTo(0, in.size(), out);
// out.transferFrom(in, 0, in.size());
watch.click(); // 복사 종료
if(in != null) { in.close(); in = null; }
if(out != null) { out.close(); out = null; }
}
public void showResult()
{
File source = new File(sourcePath);
File dest = new File(destPath);
System.out.println("원 본 : 파일명 - "+source.getName()+"\t파일크기 - "+dest.length());
System.out.println("복사본 : 파일명 - "+dest.getName()+"\t파일크기 - "+dest.length());
System.out.println("\n복사시간 : "+watch.doubleSec()+"sec");
}
public static void main(String[] args) throws Exception
{
COPY_NIO test = new COPY_NIO("E:\\edit\\source", "E:\\edit\\dest_nio");
test.copy();
test.showResult();
}
}
MP3Cutter_IO.java
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
public class MP3Cutter_IO
{
private final int MPEG_VERSION1 = 3;
private final int MPEG_VERSION2 = 2;
private final int MPEG_VERSION2_5 = 0;
private final int[][] BITRATES =
{
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
};
private final int[][] SAMPLERATES =
{
{11025, 12000, 8000, -1}, // Version 2.5
{-1, -1, -1, -1}, // Unknown version
{22050, 24000, 16000, -1}, // Version 2
{44100, 44800, 32000, -1}, // Version 1
};
private RandomAccessFile in = null;
private RandomAccessFile out = null;
private String sourcePath = null;
private String destPath = null;
private int startPoint = 0x00;
private int endPoint = 0x00;
private Watch watch = null;
public MP3Cutter_IO(String _sourcePath, String _destPath, int _startPoint, int _endPoint) throws IOException
{
sourcePath = _sourcePath;
destPath = _destPath;
startPoint = _startPoint;
endPoint = _endPoint;
in = new RandomAccessFile(sourcePath, "r");
out = new RandomAccessFile(destPath, "rw");
watch = new Watch();
}
// startPoint와 endPoint사이의 mp3데이타를 새로운 파일에 저장
public void cut() throws IOException
{
watch.click();
byte[] buf = null;
int readBytes = 0x00;
long mp3Frame = 0x00;
int currentTimestamp = 0;
long start = 0L;
long size = 0L;
// 잘라내기 시작할 지점보다 이전 부분이면 스킵한다.
while(currentTimestamp < startPoint)
{
mp3Frame = readUnsignedInt();
in.skipBytes(rawDataSize(mp3Frame));
currentTimestamp += 26;
}
// 여기부터 endPoint까지 잘라낼 구간이다.
start = in.getFilePointer();
while(currentTimestamp < endPoint)
{
mp3Frame = readUnsignedInt();
in.skipBytes(rawDataSize(mp3Frame));
currentTimestamp += 26;
}
size = in.getFilePointer() - start;
buf = new byte[(int)size];
in.readFully(buf);
out.write(buf);
// endPoint까지 데이타 작성완료. 스트림을 닫는다.
if(in != null) { in.close(); in = null; }
if(out != null) { out.close(); out = null; }
watch.click();
}
/* MP3 프레임 헤더를 가져오기 위해서 작성해준 메소드
* 싱크 비트가 '1111 1111 111' 으로 시작하는데 이는 첫 자리가 1인 음수 정수 값이 된다.
* 음수는 문제가 될 가능성이 있으므로 long타입의 하위 4바이트만 사용하는 방식으로
* 문제가 발생할 가능성을 방지했다.
*/
private long readUnsignedInt() throws IOException
{
long rv = 0L;
rv = rv | in.readInt();
/*
rv = in.readUnsignedByte();
rv = (rv << 8) & 0xFFFFFFFFL | in.readUnsignedByte();
rv = (rv << 8) & 0xFFFFFFFFL | in.readUnsignedByte();
rv = (rv << 8) & 0xFFFFFFFFL | in.readUnsignedByte();
*/
return rv;
}
// MP3의 프레임에 로우데이타의 크기를 계산하는 메소드
private int rawDataSize(long mp3Frame)
{
int rawDataSize = 0;
int mpegVersion = 0; // 2
int layer = 0; // 2
int bitrate = 0; // 4
int samplingRate = 0; // 2
int paddingBit = 0; // 1
int col1=0, col2=0;
int row1=0, row2=0;
// 여기부터는 MP3 Frame의 각 정보를 추출
mpegVersion = (int)(((mp3Frame << 11) & 0x00000000FFFFFFFFL) >> 30);
layer = (int)(((mp3Frame << 13) & 0x00000000FFFFFFFFL) >> 30);
bitrate = (int)(((mp3Frame << 16) & 0x00000000FFFFFFFFL) >> 28);
samplingRate = (int)(((mp3Frame << 20) & 0x00000000FFFFFFFFL) >> 30);
paddingBit = (int)(((mp3Frame << 22) & 0x00000000FFFFFFFFL) >> 31);
// 이 부분은 Bitrate 인덱스 세팅 부분
if(mpegVersion == 3 && layer == 3)
row1 = 0;
else if(mpegVersion == 3 && layer == 2)
row1 = 1;
else if(mpegVersion == 3 && layer == 1)
row1 = 2;
else if(mpegVersion == 2 && layer == 3)
row1 = 3;
else if(mpegVersion == 2 && (layer == 2 || layer == 1))
row1 = 4;
col1 = (int)bitrate;
// 이 부분은 Sampling rate 세팅 부분
if(mpegVersion == MPEG_VERSION1)
row2 = 3;
else if(mpegVersion == MPEG_VERSION2)
row2 = 2;
else if(mpegVersion == MPEG_VERSION2_5)
row2 = 0;
if(samplingRate == 0)
col2 = 0;
if(samplingRate == 1)
col2 = 1;
if(samplingRate == 2)
col2 = 2;
/** Raw Data 크기 계산 공식
* The size of the sample data is calculated like this (using integer arithmetic):
* Size = (((MpegVersion == MPEG1 ? 144 : 72) * Bitrate) / SamplingRate) + PaddingBit - 4
* For example: The size of the sample data for an MPEG1 frame with a Bitrate of 128000,
* a SamplingRate of 44100, and PaddingBit of 1 is:
* Size = (144 * 128000) / 44100 + 1 ? 4 = 414 bytes
*/
rawDataSize = (int)((((mpegVersion == MPEG_VERSION1 ? 144 : 72)
* BITRATES[row1][col1] * 1000)
/ SAMPLERATES[row2][col2])
+ paddingBit-4);
return rawDataSize;
}
// 결과 출력
public void showResult()
{
File source = new File(sourcePath);
File dest = new File(destPath);
System.out.println("원본 파일명 - "+source.getName()+"\t파일크기 - "+source.length());
System.out.println("잘라낸 파일명 - "+dest.getName()+"\t파일크기 - "+dest.length());
System.out.println("\n복사시간 : "+watch.doubleSec()+"sec");
}
public static void main(String[] args) throws Exception
{
MP3Cutter_IO test = new MP3Cutter_IO("E:\\edit\\111.mp3", "E:\\edit\\111_cut_io.mp3", 10000, 30000);
test.cut();
test.showResult();
}
}
MP3Cutter_NIO.java
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
public class MP3Cutter_NIO
{
private final int MPEG_VERSION1 = 3;
private final int MPEG_VERSION2 = 2;
private final int MPEG_VERSION2_5 = 0;
private final int[][] BITRATES =
{
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
};
private final int[][] SAMPLERATES =
{
{11025, 12000, 8000, -1}, // Version 2.5
{-1, -1, -1, -1}, // Unknown version
{22050, 24000, 16000, -1}, // Version 2
{44100, 44800, 32000, -1}, // Version 1
};
private FileChannel in = null;
private FileChannel out = null;
private MappedByteBuffer m = null;
private String sourcePath = null;
private String destPath = null;
private int startPoint = 0x00;
private int endPoint = 0x00;
private Watch watch = null;
public MP3Cutter_NIO(String _sourcePath, String _destPath, int _startPoint, int _endPoint) throws IOException
{
sourcePath = _sourcePath;
destPath = _destPath;
startPoint = _startPoint;
endPoint = _endPoint;
in = new RandomAccessFile(sourcePath, "r").getChannel();
out = new RandomAccessFile(destPath, "rw").getChannel();
m = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());;
watch = new Watch();
}
// startPoint와 endPoint사이의 mp3데이타를 새로운 파일에 저장
public void cut() throws IOException
{
watch.click();
long mp3Frame = 0x00;
int currentTimestamp = 0;
long start = 0L;
long size = 0L;
// 잘라내기 시작할 지점보다 이전 부분이면 스킵한다.
while(currentTimestamp < startPoint)
{
mp3Frame = readUnsignedInt();
skip(rawDataSize(mp3Frame));
currentTimestamp += 26;
}
// 여기부터 endPoint가 잘라낸 구간이다.
start = m.position();
while(currentTimestamp < endPoint)
{
mp3Frame = readUnsignedInt();
skip(rawDataSize(mp3Frame));
currentTimestamp += 26;
}
size = m.position() - start;
in.transferTo(start, size, out);
// endPoint까지 데이타 작성완료. 스트림을 닫는다.
if(in != null) { in.close(); in = null; }
if(out != null) { out.close(); out = null; }
watch.click();
}
/* MP3 프레임 헤더를 가져오기 위해서 작성해준 메소드
* 싱크 비트가 '1111 1111 111' 으로 시작하는데 이는 첫 자리가 1인 음수 정수 값이 된다.
* 음수는 문제가 될 가능성이 있으므로 long타입의 하위 4바이트만 사용하는 방식으로
* 문제가 발생할 가능성을 방지했다.
*/
private long readUnsignedInt() throws IOException
{
long rv = 0L;
rv = rv | m.getInt();
return rv;
}
// 지정된 size만큼 skip
private void skip(int size) throws IOException
{
m.position(m.position()+size);
}
// MP3의 프레임에 로우데이타의 크기를 계산하는 메소드
private int rawDataSize(long mp3Frame)
{
int rawDataSize = 0;
int mpegVersion = 0; // 2
int layer = 0; // 2
int bitrate = 0; // 4
int samplingRate = 0; // 2
int paddingBit = 0; // 1
int col1=0, col2=0;
int row1=0, row2=0;
// 여기부터는 MP3 Frame의 각 정보를 추출
mpegVersion = (int)(((mp3Frame << 11) & 0x00000000FFFFFFFFL) >> 30);
layer = (int)(((mp3Frame << 13) & 0x00000000FFFFFFFFL) >> 30);
bitrate = (int)(((mp3Frame << 16) & 0x00000000FFFFFFFFL) >> 28);
samplingRate = (int)(((mp3Frame << 20) & 0x00000000FFFFFFFFL) >> 30);
paddingBit = (int)(((mp3Frame << 22) & 0x00000000FFFFFFFFL) >> 31);
// 이 부분은 Bitrate 인덱스 세팅 부분
if(mpegVersion == 3 && layer == 3)
row1 = 0;
else if(mpegVersion == 3 && layer == 2)
row1 = 1;
else if(mpegVersion == 3 && layer == 1)
row1 = 2;
else if(mpegVersion == 2 && layer == 3)
row1 = 3;
else if(mpegVersion == 2 && (layer == 2 || layer == 1))
row1 = 4;
col1 = (int)bitrate;
// 이 부분은 Sampling rate 세팅 부분
if(mpegVersion == MPEG_VERSION1)
row2 = 3;
else if(mpegVersion == MPEG_VERSION2)
row2 = 2;
else if(mpegVersion == MPEG_VERSION2_5)
row2 = 0;
if(samplingRate == 0)
col2 = 0;
if(samplingRate == 1)
col2 = 1;
if(samplingRate == 2)
col2 = 2;
/** Raw Data 크기 계산 공식
* The size of the sample data is calculated like this (using integer arithmetic):
* Size = (((MpegVersion == MPEG1 ? 144 : 72) * Bitrate) / SamplingRate) + PaddingBit - 4
* For example: The size of the sample data for an MPEG1 frame with a Bitrate of 128000,
* a SamplingRate of 44100, and PaddingBit of 1 is:
* Size = (144 * 128000) / 44100 + 1 ? 4 = 414 bytes
*/
rawDataSize = (int)((((mpegVersion == MPEG_VERSION1 ? 144 : 72)
* BITRATES[row1][col1] * 1000)
/ SAMPLERATES[row2][col2])
+ paddingBit-4);
return rawDataSize;
}
// 결과 출력
public void showResult()
{
File source = new File(sourcePath);
File dest = new File(destPath);
System.out.println("원본 파일명 - "+source.getName()+"\t파일크기 - "+source.length());
System.out.println("잘라낸 파일명 - "+dest.getName()+"\t파일크기 - "+dest.length());
System.out.println("\n복사시간 : "+watch.doubleSec()+"sec");
}
public static void main(String[] args) throws Exception
{
MP3Cutter_NIO test = new MP3Cutter_NIO("E:\\edit\\111.mp3", "E:\\edit\\111_cut_nio.mp3", 10000, 30000);
test.cut();
test.showResult();
}
}