web-dev-qa-db-fra.com

Pourquoi la classe de string de Java ne met pas en œuvre une index plus efficace de ()?

Suivre la question ci-dessous sur le débordement de la pile

https://stackoverflow.com/questions/5564610/fast-alernative-for-stringindexsstring-strong

Je dois me demander pourquoi c'est que Java (6 au moins) ne pas utiliser une implémentation plus efficace?

Voici le code:

java.lang.string # indexof (string str)

1762    static int indexOf(char[] source, int sourceOffset, int sourceCount,
1763                       char[] target, int targetOffset, int targetCount,
1764                       int fromIndex) {
1765        if (fromIndex >= sourceCount) {
1766            return (targetCount == 0 ? sourceCount : -1);
1767        }
1768        if (fromIndex < 0) {
1769            fromIndex = 0;
1770        }
1771        if (targetCount == 0) {
1772            return fromIndex;
1773        }
1774
1775        char first  = target[targetOffset];
1776        int max = sourceOffset + (sourceCount - targetCount);
1777
1778        for (int i = sourceOffset + fromIndex; i <= max; i++) {
1779            /* Look for first character. */
1780            if (source[i] != first) {
1781                while (++i <= max && source[i] != first);
1782            }
1783
1784            /* Found first character, now look at the rest of v2 */
1785            if (i <= max) {
1786                int j = i + 1;
1787                int end = j + targetCount - 1;
1788                for (int k = targetOffset + 1; j < end && source[j] ==
1789                         target[k]; j++, k++);
1790
1791                if (j == end) {
1792                    /* Found whole string. */
1793                    return i - sourceOffset;
1794                }
1795            }
1796        }
1797        return -1;
1798    }
8
Yaneeve

"Efficacité" est tout sur les compromis et le "meilleur" algorithme dépendra de nombreux facteurs. Dans le cas de indexOf(), l'un de ces facteurs est la taille attendue des cordes.

L'algorithme de JDK est basé sur une référence indexée simple dans les tableaux de caractères existants. Le Knuth-Morris-Pratt que vous référence doit créer une nouvelle int[] c'est la même taille que la chaîne d'entrée. Pour BOYER-MOORE , vous avez besoin de plusieurs tables externes, au moins une d'entre elles est bidimensionnelle (je pense; je n'ai jamais mis en œuvre B-M).

La question devient donc: alloue les objets supplémentaires et les tables de recherche de bâtiments offset par la performance accrue de l'algorithme? Rappelez-vous, nous ne parlons pas d'un changement de O (N2) à O (n), mais simplement une réduction du nombre de mesures prises pour chaque N.

Et je m'attendrais à ce que les concepteurs JDK ont déclaré quelque chose comme "pour les chaînes inférieures à X, l'approche simple est plus rapide, nous ne nous attendons pas à une utilisation régulière de cordes plus longtemps que cela, et les personnes qui utilisent des chaînes plus longues sauront à optimiser leurs recherches. "

25
kdgregory

L'algorithme de recherche de chaîne efficace standard que tout le monde sait est Boyer-Moore . Entre autres choses, il nécessite de construire un table de transition qui a la même taille que votre jeu de caractères. Dans le cas de l'ASCII, c'est un tableau avec 256 entrées, qui est une surcharge constante qui pars sur de longues chaînes et ne ralentit pas les petites chaînes par suffisamment pour que quiconque soit pris en charge. Mais Java utilise des caractères de 2 octets qui rend cette table 64k de taille 64k. Dans l'utilisation normale, ces frais généraux dépassent la vitesse attendue de Boyer-Moore, Boyer-Moore ne vaut donc pas la peine.

Bien sûr, la plupart de cette table deviendront la même entrée, vous pourriez donc penser que vous pouvez simplement stocker des exceptions de manière efficace, puis fournir des valeurs par défaut pour tout ce qui n'est pas à vos exceptions. Malheureusement, les façons de faire cela viennent avec une surcharge de recherche qui les rend trop coûteux pour être efficaces. (Pour un problème, rappelez-vous qu'un si cela prend une succursale inattendue provoque un stand de pipeline et ceux-ci ont tendance à être coûteux.)

Veuillez noter qu'avec Unicode, cette question dépend fortement de votre codage. Quand Java= a été écrit, Unicode ajuster dans 64 K, SO Java vient d'utiliser 2 octets par caractère et la longueur de la chaîne était simplement le nombre d'octets divisés par 2. (Ce codage a été appelé UCS-2.) Cela a rendu rapidement de passer à un caractère particulier ou d'extraire une sous-chaîne particulière, et l'inefficacité de indexOf() était un non-problème. Malheureusement, unicode a depuis cultivé, donc un caractère unicode ne correspond pas toujours dans un Java caractère. Cela a obtenu Java dans les problèmes de taille qu'ils essayaient d'éviter. (Leur codage est maintenant UTF-16.) Pour la compatibilité à l'envers, ils ne pouvaient pas modifier la taille d'un Java caractère, mais maintenant il y a un meme que les caractères Unicode et Java = les personnages sont la même chose. Ils ne sont pas, mais peu Java Les programmeurs le savent, et même moins risquent de le rencontrer dans la vie quotidienne. (Notez que Windows et .Net suivaient la même chose chemin, pour les mêmes raisons.)

Dans certaines autres langues et environnements UTF-8 est utilisé à la place. Il a les belles propriétés que ASCII est valide Unicode et Boyer-Moore est efficace. Le compromis est que le fait de ne pas faire attention aux problèmes d'octets variables vous frappe beaucoup plus évidemment qu'il ne le fait UTF-16.

11
btilly

Cela revient surtout à cela: l'amélioration la plus évidente est de Boyer-Moore, ou d'une variante. B-M et Variant, cependant, veulent vraiment une interface complètement différente.

En particulier, Boyer-Moore et les dérivés fonctionnent vraiment en deux étapes: vous faites d'abord une initialisation. Ceci construit une table basée uniquement sur la chaîne que vous recherchez pour. Cela crée une table que vous pouvez ensuite utiliser pour rechercher cette chaîne aussi souvent que vous le souhaitez.

Vous êtes certainement pourrait-être correspond à cela dans l'interface existante en maintenant la mémoire de la table et en l'utilisant pour des recherches ultérieures de la même chaîne cible. Je ne pense pas que cela conviendrait très bien avec l'intention originale de Sun pour cette fonction: que ce soit un bloc de construction de bas niveau qui ne dépendrait pas d'une grande partie d'autre. En ce qui concerne une fonction de niveau supérieur qui dépend d'une autre infrastructure d'autres signifierait (entre autres) que vous devriez vous assurer que rien de l'infrastructure de mémoialisation qu'elle ne puisse utiliser ne pouvant jamais utiliser la recherche de sous-chaîne.

Je pense que le résultat le plus probable de ce qui serait simplement de rééchaler quelque chose comme celui-ci (c'est-à-dire une routine de recherche autonome) sous un nom différent, avec une routine de niveau supérieur sous le nom existant. Toutes les choses considérées, je pense que cela aurait probablement plus de sens de simplement écrire une nouvelle routine de niveau supérieur avec un nouveau nom.

L'alternative évidente à celle-ci serait d'utiliser une sorte de version dépouillée de mémotrice, que (par exemple) ne stocké qu'un seul tableau statiquement et réutilisé IFF la chaîne cible était identique à celle utilisée pour créer la table. . C'est certainement possible, mais il serait loin d'être optimal pour beaucoup de cas d'utilisation. La fabrication de thread-coffre-fort serait également non triviale.

Une autre possibilité serait d'exposer la nature en deux étapes de la recherche de B-M sur explicitement. Je doute que tout le monde aimerait vraiment cette idée, cela porte un coût assez élevé (maladresse, manque de familiarité) et peu ou pas d'avantage pour beaucoup d'affaires (la plupart des études sur le sujet indiquent que la longueur moyenne de la chaîne est quelque chose comme 20 caractères).

1
Jerry Coffin