Tistory View

 

byte[]를 String으로

byte[] => String

String s = new String(b, "UTF_8"); // b => byte[]

String => byte[]

String str = "한글";
byte[] bin = str.getBytes( "UTF-8" );

넘길/넘어온 데이터에 따라 'UTF-8' or 'EUC-KR'을 넣어주면 된다.

이건 검색하면 쉽게 찾을 수 있다.

 

근데... 만약에 byte[]를 String으로 변환하는 과정에서 byte[]의 크기가 무지막지하게 크다면, 위의 방법은 그래 좋은 방법이 아니다. 사실상 텍스트 데이터는 그리 크지 않기에 그냥 처리해 위와 같이 처리하면 웬만하면 다 처리할 수 있다.

다시 하지만, 큰 데이터를 안 다룰 경우가 없지 않다는 것이 문제다.

 

 

ByteStream을 String으로

파일이나 네트워크상에서 넘어오는 byte들을 한글로 변환해야 하는 경우는 다음과 같이 하면 훨씬 효율적이다.

char[] buf = new char[4096];
StringBuilder builder = new StringBuilder();
//예제에서는 file을 사용하지만, 네트워크 스트림을 사용할 수 있다.
FileInputStream in = new FileInputStream(fileName);

InputStream is = new BufferedInputStream( in );
try {

	InputStreamReader reader = new InputStreamReader(is, "UTF-8");
	
	while( true ) { 
		// 읽기
		int len = rdr.read(buf);
		if( len < 0 ) {
			break;
		}
		
		// 쓰기
		// 이 예제에서는 builder를 사용하지만 필요하면
		// 다른 파일스트림이나 네트워크 스트림으로 보내면 된다.
		builder.append(buff, 0, len);
	}
	
	is.close();
	is = null;		
}
catch( Exception e ) {
	e.printStackTrace();
}
finally {
	if( is != null ) {
		try { is.close(); } catch (Exception e) {}
   }
}

 

CharsetDecoder

 

이 정도에서 끝날 문제였으면, 이 글을 쓰지도 않았다. 스트림마저 쓸 수 없는 경우가 있다. 보통 Callback방식에서 이런 문제가 있는 데, 파라미터로 byte데이터는 지속적으로 넘어오고, 이 것을 맨 위의 방법인

[  String s = new String(b, "UTF_8");   ] 를 사용하게 되면 변환이 제대로 일어나지 않는다. 이 맨 위의 방식은 데이터가 온전해야 하기 때문이다.

 

온전하지 않게 잘려서 넘어옴

 

이런 경우에 쓰는 방법이다.

넘어온 데이터를 지속적으로 onCallback함수에 전달이 되게 되고, 데이터가 온전하지 않아도 CharsetDecoder가 알아서 버퍼링을 해가며 문제 없이 변환을 시켜주는 코드이다.

 

//               utf-8
// 디코더 byte ----------> CharBuffer

private CharsetDecoder mDecoder = Charset.forName('UTF-8').newDecoder();
private ByteBuffer mByteBuffer  = ByteBuffer.allocate( 4096 );

// 데이터가 넘어올 때마다 처리하는 함수
public void onCallback( byte[] dataBuf, int dataLen, StringBuilder out ) {

	int dataOffset = 0;        // current offset of dataBuf
	int dataRemain = dataLen;  // remain size of dataBuf
	
	try {
	
		while( dataRemain > 0 ) {
            // 입력 데이터를 mByteBuffer에 밀어 넣는다.
            int copyLen = mByteBuffer.remaining() < dataRemain ? mByteBuffer.remaining() : dataRemain;
		    if( copyLen <= 0 ) {
		        throw new Exception("mByteBuffer is not compacted or cleared");
		    }
		
		    mByteBuffer.put( dataBuf, dataOffset, copyLen );
		    dataOffset += copyLen;
		    dataRemain -= copyLen;
		
		    mByteBuffer.flip();
		
		    while (true) {
                // decode함수는 변환시킬 수 있는 최대까지 변환을 한다.
                // 마지막 파라미터 false는 입력이 종료되지 않았다는 뜻이다.
                // 입력에 있는 모든 데이터를 처리하면 underflow를 리턴한다.
		        CoderResult res = mDecoder.decode(mByteBuffer, mCharBuffer, false);
		
		        if (res.isOverflow()) {
                    // 출력 버퍼가 꽉 찼다. 데이터를 저장하고 출력 버퍼를 비운다.
		            mCharBuffer.flip();
		            out.append( mCharBuffer.array(), 0, mCharBuffer.length() );
		            mCharBuffer.clear();
		            // 버퍼를 비웠으니 다시 디코드하게 한다.
		            continue;
		            
		        } else if (res.isUnderflow()) {
		            // 인풋에 들어있는 데이터로는 변환작업이 끝났다.
                    작업을 끝낸다.
		        } else if (res.isMalformed()) {
		            Log.e(TAG, "isMalformed");
		        } else if (res.isUnmappable()) {
		            Log.e(TAG, "isUnmappable");
		        }
		        break;
		    }
		
		    mByteBuffer.compact();
		}
	}
	catch( Exception e ) {
		e.printStackTrace();
	}
}

// decoder안에는 아직 출력하지 못한 데이터가 존재할 수 있다. 
// 남은 자투리 데이터를 가지고 온다.
public void onFlush( String out ) {

	// mByteBuffer is write-mode so switch to read-mode
	mByteBuffer.flip();
	
	while( true ) {
	    // 마지막 파라미터 true가 마지막 입력 데이터임을 나타낸다.
        // 성공시 underflow가 리턴된다.
		CoderResult res = mDecoder.decode( mByteBuffer, mCharBuffer, true );
		
		if (res.isOverflow()) {
			mCharBuffer.flip();
			out.append(mCharBuffer.array(), 0, mCharBuffer.length() );
			mCharBuffer.clear();
			continue;
		}
		else if( res.isUnderflow() ) {
		
			mCharBuffer.flip();
			out.append(mCharBuffer.array(), 0, mCharBuffer.length() );
			mCharBuffer.clear();
			
			mDecoder.flush( mCharBuffer );
			mCharBuffer.flip();
			builder.append(mCharBuffer.array(), 0, mCharBuffer.length() );
		
		}
		else if (res.isMalformed()) {
			Log.e( TAG, "isMalformed");
		} else if (res.isUnmappable()) {
			Log.e( TAG, "isUnmappable");
		} else {
			Log.d( TAG, "waht?" );
		}
		
		break;
	}
}

만드는 데 꽤 시간이 들었다.. 머리를 좀 쓰느라..

 

위의 코드를 그대로 복사해서 지속적으로 byte[]로 넘겨주면 된다.

 

사용법

StringBuilder out = new StringBuilder();

byte[] buf = new byte[128];
InputStream in = null; // [your_file or network stream]
BufferedInputStream is = new BufferedInputStream( in );

try {
  while (true) {
      int nread = is.read(buf);
      if( nread < 0 ) {
          break;
      }
      onCallback( buf, nread, out );
  }
  onFlush( out );
}
catch( IOException e ) {
  e.printStackTrace();
}
finally {
  if( is != null ) {
      try { is.close(); } catch (Exception e ) {}
  }
}

Log.d( TAG, out.toString() );

 

위에서 사용된 ByteBuffer 동작방식이 궁금하면 다음의 링크를 이용하라.

jamssoft.tistory.com/221

 

Buffer(ByteBuffer, CharBuffer...) flip, compact, clear사용법

소스코드 ByteBuffer, IntBuffer, FloatBuffer, DoubleBuffer, ShortBuffer, LongBuffer, CharBuffer.. 등에는 flip, compact, clear등에 함수가 있다. 근데, 이 flip, compact, clear는 도대체가 뭐 하는 녀석인..

jamssoft.tistory.com

위 소스코드 다운로드

chardecoder.zip
0.00MB

Reference

javacan.tistory.com/tag/CharsetDecoder

Replies
Reply Write