๋ฐ˜์‘ํ˜•

๐Ÿš€ Problem


php โ†’ java๋กœ ์ปจ๋ฒ„ํŒ…์„ ํ•˜๊ณ  php ์„œ๋ฒ„์˜ api์™€ java ์„œ๋ฒ„์˜ api์˜ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์™„์ „ํžˆ ๋™์ผํ•˜๊ฒŒ ๋งž์ถ”๋Š” ๋ฐฉ์‹์œผ๋กœ

ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค. ๊ธฐ๋Šฅ์„ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ฝ”๋“œ ์ปจ๋ฒ„ํŒ…๋งŒ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— php์„œ๋ฒ„์˜ ์‘๋‹ต๊ณผ 100% ๋™์ผํ•ด์•ผ ํ–ˆ๋‹ค.

๋ฐœ์ƒ ๋ฌธ์ œ

๋ฌธ์ž์—ด์„ ์ตœ๋Œ€ 150๋กœ ์ž˜๋ผ์•ผ ํ–ˆ๋‹ค.

php์—์„œ๋Š” ๊ธฐ๋ณธ api๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฌธ์ž์—ด์„ ์ž๋ฅด๋ฉด ๋ฐ”์ดํŠธ ๊ธฐ์ค€ 150๊ฐœ๋กœ ์ž๋ฅธ๋‹ค. ์ด๋•Œ ํ•œ๊ธ€์€ 2๋ฐ”์ดํŠธ๋กœ ์ธ์‹ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ java๋Š” ๋ฌธ์ž์—ด ์ž์ฒด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‘๊ธฐ ๋•Œ๋ฌธ์— 2๋ฐฐ ์ ๊ฒŒ ๊ธธ์ด๊ฐ€ ์ธก์ •๋๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์–ธ์–ด ์ž๋ฅผ ๊ธธ์ด ํƒ€๊ฒŸ ๋ฌธ์ž์—ด ๊ฒฐ๊ณผ
php 4 ์•ˆ๋…•ํ•˜์„ธ์š” ์•ˆ๋…•
java 4 ์•ˆ๋…•ํ•˜์„ธ์š” ์•ˆ๋…•ํ•˜์„ธ

java์—์„œ StringUtils.left(string, limit)๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

์ด๋Š” ๋‹จ์ˆœ์ด ๋ฌธ์ž์—ด์„ ์™ผ์ชฝ๋ถ€ํ„ฐ 4์ž๋งŒํผ ์ž๋ฅด๋Š” ๊ฒƒ์ด๋‹ค.

๐Ÿš€ Trial And Error


Charset๋ณ„๋กœ ๋””์ฝ”๋”ฉ

๋จผ์ € ํ•œ๊ธ€์„ ๋ช‡ ๋ฐ”์ดํŠธ๋กœ JVM์ด ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ์•Œ์•„๋ดค๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋กœ ํ™•์ธํ•œ ๊ฒฐ๊ณผ 3๋ฐ”์ดํŠธ๋ผ๋Š” ๊ฒƒ์„ ํ™•์ผํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

"์•ˆ๋…•ํ•˜์„ธ์š”".getBytes().length // 15

์ด์ œ 3๋ฐ”์ดํŠธ๋ผ๋Š” ๊ฒƒ์„ ์•Œ์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฑธ 2๋ฐ”์ดํŠธ๋กœ ๋ฐ”๊พธ๋ฉด ๋˜๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ 2๋ฐ”์ดํŠธ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” Charset์„ ์ฐพ๊ธฐ ์œ„ํ•ด ๋ชจ๋“  Charset์„ ์‚ฌ์šฉํ•ด์„œ String์œผ๋กœ ๋””์ฝ”๋”ฉ ํ•ด๋ดค๋‹ค.

ํ•˜์ง€๋งŒ ๋ชจ๋‘ ๋ฌธ์ž๋Š” ๊นจ์กŒ๋‹ค. ํ•˜์ง€๋งŒ UTF-8๋กœ ํ•˜๋ฉด ๊นจ์ง€์ง€ ์•Š์•˜๋‹ค.

// org.apache.commons.lang3.StringUtils
StringUtils.left(new String("ํ•œ๊ธ€์–ด์ฉŒ๊ตฌ".getByte(), UTF-8) ,150).trim()

Java๋Š” OS์˜ ์‹œ์Šคํ…œ Charset์„ ๋””ํดํŠธ๋กœ ์ฑ„ํƒํ•˜๊ณ  ์žˆ๋‹ค.

JVM์€ ๋””ํดํŠธ Charset์„ OS Charset์œผ๋กœ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋กœ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋‚ด ์ปดํ“จํ„ฐ์˜ Charset์€ UTF-8์ด์—ˆ๊ณ , ๊ทธ๋ž˜์„œ ์œ„์—์„œ Charset๋ณ„๋กœ ๋””์ฝ”๋”ฉ์„ ์‹œ๋„ํ–ˆ์„ ๋•Œ UTF-8์„ ์ œ์™ธํ•˜๊ณ ๋Š” ๋ชจ๋‘ ๊นจ์กŒ๋˜ ๊ฒƒ์ด๋‹ค.

์ฆ‰, ์ธ์ฝ”๋”ฉ์ด ๋˜๋ฉด Charset์€ ๋ฐ”๊ฟ€ ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ด๋ฏธ ์•ฝ์†๋œ ํ”„๋กœํ† ์ฝœ๋กœ ์ธ์ฝ”๋”ฉ ๋๊ธฐ ๋•Œ๋ฌธ์—

๋””์ฝ”๋”ฉ์„ ๋‹ค๋ฅธ Charset์œผ๋กœ ํ•˜๋ฉด ์•ˆ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

@Test
void ์ธ์ฝ”๋”ฉ() {
  // given
  String twoByteStr = "์•ˆ";
  String encode16 = UriUtils.encode(twoByteStr, StandardCharsets.UTF_16);
  String decode8 = UriUtils.encode(twoByteStr, StandardCharsets.UTF_8);

  System.out.println(Charset.defaultCharset()); // UTF-8
  System.out.println("decode8 = " + decode8);   // decode8 = %EC%95%88
  System.out.println("encode16 = " + encode16); // encode16 = โ•†ไ”ฅไ™†โ•ƒใ•ˆ
}

์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๊ฐ€ ๋งž์•˜์„๊นŒ?

์ธ์ฝ”๋”ฉ ์ด์Šˆ๋Š” ์•„๋‹ˆ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

php ์„œ๋ฒ„์—์„œ๋Š” utf-16์ธ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ 2๋ฐ”์ดํŠธ๋กœ ํ•œ๊ธ€์„ ์ทจ๊ธ‰ํ•˜๋Š” Charset์„ ์‚ฌ์šฉํ–ˆ๋˜ ๊ฒƒ์ด๋‹ค.

๋ฌผ๋ก  ๊ทธ๋ ‡๋‹ค๊ณ  ํ•œ๋“ค ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฅด๋Š” ๊ฒƒ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ชป์ฐพ์•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๊ตญ ๋‚˜๋Š” ์ง์ ‘ 2๋ฐ”์ดํŠธ๋กœ ํ•œ๊ธ€์„ ์ž๋ฅด๋Š” ์ฝ”๋“œ๋ฅผ

์ž‘์„ฑํ–ˆ๋‹ค. ๋งŒ์•ฝ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฐพ์•˜๋Š”๋ฐ, ๊ฑฐ๊ธฐ์„œ defaultCharset()์„ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๊ฐ€ ๋์„ ์ˆ˜๋„ ์žˆ๋‹ค.

์œ„์—์„œ ํ…Œ์ŠคํŠธํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด defaultCharset() OS์˜ Charset์„ ์ฝ์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ์• ์ดˆ์— ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ์˜€๋‹ค๊ธฐ ๋ณด๋‹ค๋Š” php์—์„œ ๋ฌธ์ž์—ด์„ ๊ทธ๋Œ€๋กœ ์ž๋ฅด๋Š๋ƒ ๋ช‡ ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฅด๋Š๋ƒ์˜ ๋ฌธ์ œ์˜€๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

์ž๋ฐ”์—์„œ๋„ 3๋ฐ”์ดํŠธ๋กœ ์ธ์‹ํ•˜๋Š” ๊ฒƒ๋„ ๋ฌด๊ด€ํ•œ ๊ฒƒ ์•„๋‹Œ๊ฐ€?

ํ•œ๊ธ€์„ 2๋ฐ”์ดํŠธ๋กœ ์ธ์‹ํ•˜๋Š” Charset์€ ์—†์„๊นŒ?

UTF-8์€ 3๋ฐ”์ดํŠธ๋กœ ์ธ์‹ํ•˜๊ณ  UTF-16์€ 2 or 4๋ฐ”์ดํŠธ๋กœ ์ธ์‹ํ•œ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— defaultCharset()์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด os์˜ Charset์„ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

์•„๋‹ˆ๋ฉด JVM ์˜ต์…˜์„ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€ ์•Š์„๊นŒ?

โš ๏ธ ์™œ ๋ฌธ์ž์—ด ์ž์ฒด๊ฐ€ ์•„๋‹Œ ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ์ž˜๋ผ์„œ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ?

๋น„์ง€๋‹ˆ์Šค์  ๋ฌธ์ œ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋””๋น„์— ์ปฌ๋Ÿผ ์‚ฌ์ด์ฆˆ ๋•Œ๋ฌธ์ด๋ผ๋˜์ง€ ์•„๋‹ˆ๋ฉด ํ†ต์‹ ํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ ๊ทœ์น™์ด ๊ทธ๋ ‡๋‹ค๋˜์ง€ ๋“ฑ๋“ฑ..

๐Ÿš€ Solution


๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฅด๋„๋ก ๋ณ€๊ฒฝ

์ž๋ฐ” or ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฅด๋Š” ๊ฒƒ์„ ์ฐพ์ง€ ๋ชปํ•ด์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์„ ํƒํ–ˆ๋‹ค.

๊ฒฐ๊ตญ 2๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž˜๋ผ์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

  • code

      public class ByteString {
    
        /**
         * String์—์„œ ํ•œ๊ธ€์ด ์žˆ์„ ๊ฒฝ์šฐ ํ•œ๊ธ€์„ 2byte or 3byte๋“ฑ์œผ๋กœ ์ธ์‹ํ•˜๊ฒŒ ํ•˜์—ฌ
         * ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ๋ฌธ์ž์—ด์„ ์ž˜๋ผ์„œ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
         * @param target ์ž๋ฅผ ๋ฌธ์ž์—ด
         * @param limit ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฅผ limit
         * @param standardByteLength ํ•œ๊ธ€์„ ๋ช‡ ๋ฐ”์ดํŠธ๋กœ ์ทจ๊ธ‰ํ• ์ง€ ๊ธธ์ด
         * @return ๋ฐ”์ดํŠธ ๊ธฐ์ค€์œผ๋กœ ์ž˜๋ฆฐ ๋ฌธ์ž์—ด
         */
        public static String cutStringToByte(String target, int limit, int standardByteLength) {
          TargetString doggy = new TargetString(target);
          return doggy.leftStringToByteCuting(limit, standardByteLength);
        }
    
        private static class TargetString {
    
          private final String target;
          private int realLimit = 0;
          private int bufferSize = 0;
    
          public TargetString(String target) {
            this.target = target;
          }
    
          public String leftStringToByteCuting(int standardLimit, int standardByte) {
            for (int i = 0; i < target.length(); i++) {
              char ascii = target.charAt(i);
              boolean isFillBuffer = fillBuffer(ascii, standardLimit, standardByte);
    
              if (!isFillBuffer) {
                return target.substring(0, realLimit);
              }
            }
    
            return target.substring(0, realLimit);
          }
    
          private boolean fillBuffer(char c, int standardLimit, int standardByte) {
            if (isAsciiCode(c)) {
              return fillEnglishBuffer(standardLimit);
            }
    
            return fillKrBuffer(standardLimit, standardByte);
          }
    
          private static boolean isAsciiCode(int asciiCode) {
            return asciiCode <= 127;
          }
    
          private boolean fillEnglishBuffer(int standardLimit) {
            if (isFillEnglish(standardLimit)) {
              bufferSize++;
              realLimit++;
    
              return true;
            }
            return false;
          }
    
          private boolean fillKrBuffer(int standardLimit, int standardByte) {
            if (isFillKr(standardLimit, standardByte)) {
              bufferSize += standardByte;
              realLimit++;
    
              return true;
            }
            return false;
          }
    
          private boolean isFillEnglish(int standardLimit) {
            return standardLimit > bufferSize;
          }
    
          private boolean isFillKr(int standardLimit, int standardByte) {
            return standardLimit >= bufferSize + standardByte;
          }
        }
      }

๐Ÿš€์ฐธ๊ณ  ์ž๋ฃŒ


https://namu.wiki/w/์ธ์ฝ”๋”ฉ

https://codingpractices.tistory.com/entry/์ธ์ฝ”๋”ฉ-vs-๋””์ฝ”๋”ฉ-์ •ํ™•ํ•˜๊ฒŒ-์ดํ•ดํ•˜๊ธฐ

https://it-eldorado.tistory.com/143

https://codingpractices.tistory.com/entry/์ธ์ฝ”๋”ฉ-vs-๋””์ฝ”๋”ฉ-์ •ํ™•ํ•˜๊ฒŒ-์ดํ•ดํ•˜๊ธฐ

https://luv-n-interest.tistory.com/1369

๋ฐ˜์‘ํ˜•
๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค!