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();
}
}