9
votes

Cordes tronquantes par octets

Je crée ce qui suit pour tronquer une chaîne en Java à une nouvelle chaîne avec un nombre donné d'octets.

        String truncatedValue = "";
        String currentValue = string;
        int pivotIndex = (int) Math.round(((double) string.length())/2);
        while(!truncatedValue.equals(currentValue)){
            currentValue = string.substring(0,pivotIndex);
            byte[] bytes = null;
            bytes = currentValue.getBytes(encoding);
            if(bytes==null){
                return string;
            }
            int byteLength = bytes.length;
            int newIndex =  (int) Math.round(((double) pivotIndex)/2);
            if(byteLength > maxBytesLength){
                pivotIndex = newIndex;
            } else if(byteLength < maxBytesLength){
                pivotIndex = pivotIndex + 1;
            } else {
                truncatedValue = currentValue;
            }
        }
        return truncatedValue;


3 commentaires

Je voudrais reformuler votre problème. Vous essayez d'adapter une chaîne dans un tableau d'octets qui ne peut pas être plus grand que Maxutf8BytesLength. Vous souhaitez utiliser UTF-8 pour le codage. Vous voulez copier autant de caractère que possible. Correct?


Droite, je dirais que c'est correct. Je voudrais aussi le faire efficacement.


Je viens de modifier la question de ne pas référence UTF-8. Désolé pour ça, c'était trompeur.


13 Réponses :


14
votes

Pourquoi ne pas convertir en octets et avancer en avant - obéissant les limites de caractères UTF8 comme vous le faites - jusqu'à ce que vous obtenez le numéro maximal, puis convertissez ces octets dans une chaîne?

ou vous pouvez simplement couper le String d'origine Si vous gardez une trace de l'endroit où la coupe doit se produire: xxx

Remarque: édité pour corriger les bugs le 2014-08-25


3 commentaires

Je pourrais définitivement le faire. Y a-t-il une raison pour laquelle utiliser String.Substring est un pire? Il semble que cela semble le faire comme si vous décrivez devait rendre compte de tous les points de code, ce qui n'est pas beaucoup de plaisir. (Selon votre définition de plaisir :)).


@stevebot - Pour être efficace, vous devez tirer parti de la structure connue des données. Si vous ne vous souciez pas de l'efficacité et que vous voulez que ce soit facile, ou si vous souhaitez appuyer tous les codages Java possibles sans avoir à savoir ce que c'est, votre méthode semble assez raisonnable.


Ne serait-il pas encore plus efficace d'itérer sur les caractères de la chaîne et de prédire leur longueur codée, au lieu d'encoder la chaîne entière, d'itéraler sur les octets codés et de reconstituer leur association de caractères? Semblable à Ce , juste avec le support de caractères non-BMP et le comptage avant de faire sous-chaînes comme dans votre réponse…



1
votes

Vous pouvez convertir la chaîne en octets et convertir simplement ces octets à une chaîne.

public static String substring(String text, int maxBytes) {
   StringBuilder ret = new StringBuilder();
   for(int i = 0;i < text.length(); i++) {
       // works out how many bytes a character takes, 
       // and removes these from the total allowed.
       if((maxBytes -= text.substring(i, i+1).getBytes().length) < 0) break;
       ret.append(text.charAt(i));
   }
   return ret.toString();
}


3 commentaires

@nguyendat, il y a beaucoup de raisons ce n'est pas très performant. La principale serait la création d'objets pour la sous-chaîne () et les getbytes () Toutefois, vous seriez surpris de savoir combien vous pouvez faire dans un milli-seconde et que cela suffit généralement.


Cette méthode ne gère pas correctement les paires de substituts, par ex. Substrage ("\ ud800 \ udf30 \ ud800 \ udf30", 4) .getbytes ("utf-8"). La longueur reviendra 8, pas 4. une demi-paire de substitution est représentée comme un octet "?" par string.getbytes ("utf-8").


@Stefanl J'ai posté une variante de cette réponse ici qui devrait gérer correctement les paires de substitution.



3
votes

Utilisez l'UTF-8 CharseCoder et encoder jusqu'à ce que la sortie BYTEBUFFER contienne autant d'octets que vous êtes prêt à prendre, en recherchant CODERRESULT.Overflow.


0 commentaires

2
votes

0 commentaires

2
votes

Comme indiqué, Peter Lawrey Solution présente un inconvénient de performance majeur (~ 3 500 msc de 10 000 fois), Rex Kerr était bien meilleur (~ 500 msc pour 10 000 fois), mais le résultat n'était pas précis - il a coupé beaucoup plus que nécessaire (à la place des 4000 autres octets, il reste 3500 pour un exemple). Attaché ici ma solution (~ 250msc pour 10 000 fois) en supposant que la longueur maximale UTF-8 caractères de l'octets est de 4 (merci wikipedia): xxx


1 commentaires

Ne semble-t-il pas que cette solution empêche une paire de substitution de demi-culture à la traînée? Deuxièmement, dans le cas de getbytes (). La longueur serait appliquée aux deux moitiés d'une paire de substitution individuellement (pas immédiatement évidente pour moi, cela ne sera jamais sous-estimer la taille de la représentation UTF-8 de la paire Dans son ensemble, en supposant que le "tableau d'octet de remplacement" est un octet unique. Troisièmement, les points de code de 4 octets UTF-8 nécessitent toutes une paire de substitution à deux caractères en Java, de sorte que le max est de 3 3 octets par caractère Java.



4
votes
FOO

2 commentaires

Lorsque la longueur maximale interrompt le tableau d'octets au milieu d'une séquence multi-octets, la chaîne résultante se termine par un "?". Exemple: s = "ÄÄ"; max_length = 3; résultat: "Ä?" Compte tenu de la simplicité de ce code, peut-être peut-être dans certains Situations Ce pourrait être une option.


Corriger mon commentaire: max_length = 5 (Pourquoi la solution utilise-t-elle max_length-2 ?) Notez également que comme de Java 1.6, "utf-8" doit être remplacé par standardChararsets.utf_8 .



5
votes

5 commentaires

Vous devriez également attraperedencodingingException à S.Getbytes ("UTF-8")


Je ne vois pas les getbytes qui lancent quoi que ce soit. Bien que Docs .Orcle.com / Javase / 7 / Docs / API / Java / Lang / ... dit "Le comportement de cette méthode lorsque cette chaîne ne peut pas être codée dans le charert donné n'est pas spécifiée."


La page que vous avez liée montre qu'il jette une incidence non supportée: "Byte publique [] getbytes (chaîne CharsetNameName) jette une incidence non supportée"


Merci! Étrange, je ne sais pas quelle version j'ai utilisée quand j'ai posté cette solution il y a 2 ans. Mise à jour du code ci-dessus.


Au lieu de fournir le nom de codage sous forme de chaîne, vous pouvez utiliser les constantes de caractères à partir de la classe StandardCharSets, car la méthode String # GetBytes (Charset Charset) ne jette pas non-extraiteCodingException.



0
votes

Ceci est mon:

private static final int FIELD_MAX = 2000;
private static final Charset CHARSET =  Charset.forName("UTF-8"); 

public String trancStatus(String status) {

    if (status != null && (status.getBytes(CHARSET).length > FIELD_MAX)) {
        int maxLength = FIELD_MAX;

        int left = 0, right = status.length();
        int index = 0, bytes = 0, sizeNextChar = 0;

        while (bytes != maxLength && (bytes > maxLength || (bytes + sizeNextChar < maxLength))) {

            index = left + (right - left) / 2;

            bytes = status.substring(0, index).getBytes(CHARSET).length;
            sizeNextChar = String.valueOf(status.charAt(index + 1)).getBytes(CHARSET).length;

            if (bytes < maxLength) {
                left = index - 1;
            } else {
                right = index + 1;
            }
        }

        return status.substring(0, index);

    } else {
        return status;
    }
}


0 commentaires

0
votes

En utilisant une expression régulière ci-dessous, vous pouvez également éliminer l'espace blanc de début et de fin de caractère de double octet.

stringtoConvert = stringtoConvert.replaceAll("^[\\s ]*", "").replaceAll("[\\s ]*$", "");


0 commentaires

0
votes

Celui-ci n'a pas pu être la solution la plus efficace mais fonctionne xxx


0 commentaires

8
votes

La solution la plus sane utilise décodeur: xxx


4 commentaires

La découpe à un indice d'octet arbitraire peut créer des données codées non valides, car un seul caractère peut utiliser plusieurs octets (en particulier avec UTF-8). Pire, avec d'autres codages, il peut produire de mauvais caractères valides, qui ne sont pas ignorés. Vous pouvez facilement éviter cela en allouant d'abord un bytebuffer avec la taille souhaitée, puis l'utiliser avec un CharseCoder , qui ne codera automatiquement autant de caractères valides que correspondant au tampon. , puis décodez la mémoire tampon à une chaîne . Une approche similaire, mais sans le bogue et encore plus efficace, car elle ne codera pas le caractère au-delà de la limite prévue.


Voir cette réponse . Cela élimine même l'étape de décodage.


@Holger Ma solution ignore les caractères multibytes tronqués par codingErrorAction.ignore . Donc ça marche bien. Je suis intéressé à voir un exemple quand il échoue. Cependant, je suis d'accord, votre solution a l'air Natre et pourrait être plus performant.


Oui, pour UTF-8 en utilisant CODINGErrorAction.ignore fera la bonne chose. Mais l'OP a dit: "Je préférerais être capable de le faire pour différents types de stockage aswell" et pour d'autres codages, des séquences multibytes déchirantes peuvent entraîner des caractères valides (mais faux).



0
votes

J'ai amélioré la solution de Peter Lawrey pour gérer avec précision des paires de substituts. De plus, j'ai optimisé en fonction du fait que le nombre maximum d'octets par Char code> dans l'encodage UTF-8 est 3.

public static String substring(String text, int maxBytes) {
    for (int i = 0, len = text.length(); (len - i) * 3 > maxBytes;) {
        int j = text.offsetByCodePoints(i, 1);
        if ((maxBytes -= text.substring(i, j).getBytes(StandardCharsets.UTF_8).length) < 0)  
            return text.substring(0, i);
        i = j;
    }
    return text;
}


0 commentaires

0
votes

Approche de recherche binaire dans SCALA:

private def bytes(s: String) = s.getBytes("UTF-8")

def truncateToByteLength(string: String, length: Int): String =
  if (length <= 0 || string.isEmpty) ""
  else {
    @tailrec
    def loop(badLen: Int, goodLen: Int, good: String): String = {
      assert(badLen > goodLen, s"""badLen is $badLen but goodLen is $goodLen ("$good")""")
      if (badLen == goodLen + 1) good
      else {
        val mid = goodLen + (badLen - goodLen) / 2
        val midStr = string.take(mid)
        if (bytes(midStr).length > length)
          loop(mid, goodLen, good)
        else
          loop(badLen, mid, midStr)
      }
    }

    loop(string.length * 2, 0, "")
  }


0 commentaires