2013-11-08 2 views
15

У меня возникла ситуация, когда мне нужно знать размер пары кодирования String/в байтах, но не может использовать метод getBytes(), потому что 1) String очень большой, и дублирование String в массиве byte[] будет использовать большой объем памяти, но больше к точке 2) getBytes() выделяет массив byte[] на основе длины String * максимально возможных байтов на символ. Поэтому, если у меня есть String с символами 1.5B и кодировкой UTF-16, getBytes() попытается выделить массив 3 ГБ и сбой, так как массивы ограничены 2^32 - X байтами (X - спецификация Java).Получить размер строки с кодировкой в ​​байтах без преобразования в байты []

Итак - есть ли способ вычислить размер байта пары кодирования String/непосредственно из объекта String?

UPDATE:

Вот рабочая реализация ответа jtahlborn в:

private class CountingOutputStream extends OutputStream { 
    int total; 

    @Override 
    public void write(int i) { 
     throw new RuntimeException("don't use"); 
    } 
    @Override 
    public void write(byte[] b) { 
     total += b.length; 
    } 

    @Override public void write(byte[] b, int offset, int len) { 
     total += len; 
    } 
} 
+0

Длина в байтах зависит от вашей целевой кодировке. Например, «test» .getBytes («UTF-8») составляет 4 байта, но «test» .getBytes («UTF-16») составляет 10 байт (да, 10, попробуйте). Поэтому вам нужно немного уточнить свой вопрос. – brettw

+0

Я бы добавил, что он также зависит от кодовой точки («символов»), которую вы кодируете. Например, в UTF-16 определенная точка кода использует 1 блок кода, другие используют 2 (блок кода имеет длину 16 бит). UTF-8 может принимать от 1 до 4 байтов на символ. – Francis

+0

@brettw Извините, если я плотный, но да, ваш комментарий - это вопрос: с учетом строки и кодировки, сколько байтов требует кодировка строки? Перечитывая вопрос, это кажется мне совершенно понятным - есть ли у вас какие-либо предложения по его переписыванию? – elhefe

ответ

11

Простой, просто записать его в выходной поток фиктивного:

class CountingOutputStream extends OutputStream { 
    private int _total; 

    @Override public void write(int b) { 
    ++_total; 
    } 

    @Override public void write(byte[] b) { 
    _total += b.length; 
    } 

    @Override public void write(byte[] b, int offset, int len) { 
    _total += len; 
    } 

    public int getTotalSize(){ 
    _total; 
    } 
} 

CountingOutputStream cos = new CountingOutputStream(); 
Writer writer = new OutputStreamWriter(cos, "my_encoding"); 
//writer.write(myString); 

// UPDATE: OutputStreamWriter does a simple copy of the _entire_ input string, to avoid that use: 
for(int i = 0; i < myString.length(); i+=8096) { 
    int end = Math.min(myString.length(), i+8096); 
    writer.write(myString, i, end - i); 
} 

writer.flush(); 

System.out.println("Total bytes: " + cos.getTotalSize()); 

это не только просто, но, вероятно, так же быстро, как и другие «сложные» ответы.

+0

Класс COS не компилируется, но я добавил рабочую реализацию к исходному вопросу. – elhefe

+0

@elhefe - ваша версия может компилироваться, но это неверно. вы не хотите использовать смещение в вычислении. – jtahlborn

+0

Упс, исправлено. По-видимому, только мой метод write (byte []) использовал мои тесты. – elhefe

0

Хорошо, это очень грубо. Я признаю это, но этот материал скрыт от JVM, поэтому нам нужно немного копать. И немного пота.

Во-первых, нам нужен фактический символ char [], который поддерживает строку без копирования. Для этого мы должны использовать отражение, чтобы получить в поле «значение»:

char[] chars = null; 
for (Field field : String.class.getDeclaredFields()) { 
    if ("value".equals(field.getName())) { 
     field.setAccessible(true); 
     chars = (char[]) field.get(string); // <--- got it! 
     break; 
    } 
} 

Далее вам необходимо реализовать подкласс java.nio.ByteBuffer. Что-то вроде:

class MyByteBuffer extends ByteBuffer { 
    int length;    
    // Your implementation here 
}; 

Игнорировать все добытчиками, реализовать все ставить методы как put(byte) и putChar(char) и т.д. Внутри что-то вроде put(byte), приращение длины 1, внутри put(byte[]) приращения длины по длине массива. Возьми? Все, что ставится, вы добавляете размер того, что есть на длина. Но вы ничего не храните в своем ByteBuffer, вы просто считаете и выбрасываете прочь, поэтому места не требуется. Если вы остановите методы put, вы, вероятно, сможете выяснить, какие из них вам действительно нужны. putFloat(float), вероятно, не используется, например.

Теперь для большого финала, положить все это вместе:

MyByteBuffer bbuf = new MyByteBuffer();   // your "counting" buffer 
CharBuffer cbuf = CharBuffer.wrap(chars);  // wrap your char array 
Charset charset = Charset.forName("UTF-8");  // your charset goes here 
CharsetEncoder encoder = charset.newEncoder(); // make a new encoder 
encoder.encode(cbuf, bbuf, true);    // do it! 
System.out.printf("Length: %d\n", bbuf.length); // pay me US$1,000,000 
+3

Вы можете избежать уродливого отражения, просто называя ['CharBuffer.wrap (CharSequence)'] (http://docs.oracle.com/javase/7/docs/api/java/nio/CharBuffer.html#wrap (java.lang.CharSequence)) с самой строкой. Он * будет использовать 'char []' из 'String' без копирования (по крайней мере, в Oracle JDK 7 Update 21). –

+0

О, хорошо! Я не знал этого. – brettw

0

Вот по-видимому, работает реализация:

import java.nio.charset.Charset; 
import java.nio.charset.StandardCharsets; 

public class TestUnicode { 

    private final static int ENCODE_CHUNK = 100; 

    public static long bytesRequiredToEncode(final String s, 
      final Charset encoding) { 
     long count = 0; 
     for (int i = 0; i < s.length();) { 
      int end = i + ENCODE_CHUNK; 
      if (end >= s.length()) { 
       end = s.length(); 
      } else if (Character.isHighSurrogate(s.charAt(end))) { 
       end++; 
      } 
      count += encoding.encode(s.substring(i, end)).remaining() + 1; 
      i = end; 
     } 
     return count; 
    } 

    public static void main(String[] args) { 
     StringBuilder sb = new StringBuilder(); 
     for (int i = 0; i < 100; i++) { 
      sb.appendCodePoint(11614); 
      sb.appendCodePoint(1061122); 
      sb.appendCodePoint(2065); 
      sb.appendCodePoint(1064124); 
     } 
     Charset cs = StandardCharsets.UTF_8; 

     System.out.println(bytesRequiredToEncode(new String(sb), cs)); 
     System.out.println(new String(sb).getBytes(cs).length); 
    } 
} 

Выход:

1400 
1400 

На практике я d увеличить ENCODE_CHUNK до 10MChars или около того.

Вероятно, немного менее эффективен, чем ответ бреттв, но проще реализовать.

1

То же с помощью Apache-фонда библиотеки:

public static long stringLength(String string, Charset charset) { 

    try (NullOutputStream nul = new NullOutputStream(); 
     CountingOutputStream count = new CountingOutputStream(nul)) { 

     IOUtils.write(string, count, charset.name()); 
     count.flush(); 
     return count.getCount(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Unexpected I/O.", e); 
    } 
} 
Смежные вопросы