metodo manejo funciones declarar caracteres cadenas java string traversal

manejo - ¿Por qué el método String.equals() de Java usa dos variables de conteo?



string en java (3)

Estaba mirando a través de la implementación de la clase String de Java y lo siguiente me pareció extraño:

public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { // Here n is being decremented... if (v1[i] != v2[i]) return false; i++; // while i is being incremented } return true; } } return false; }

Esto podría implementarse fácilmente con una sola variable de conteo, mientras que n sería efectivamente final, como esto:

while (i != n) { if (v1[i] != v2[i]) return false; i++; }

También hay esto, que se deshace de la i completo:

while (n-- != 0) { if (v1[n] != v2[n]) return false; }

¿Tiene que ver con que la comparación con 0 sea (un bit minúsculo) más barata que con otra variable o hay alguna otra razón en particular para explicar por qué se implementa de esa manera?


Creo que tiene que ver con la implementación de substring antes de JDK 7.

En ese momento, el tamaño de la matriz de caracteres subyacente no era necesariamente el tamaño de la cadena. Hubo dos campos, offset y count ( i y j respectivamente) que mostraban dónde está this cadena en la matriz subyacente, por lo que el método era:

public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }

Después del cambio mencionado anteriormente, este método también tuvo que cambiarse, por lo que simplemente arreglaron n para que sea ahora la longitud del arreglo:

int n = value.length;

y se deshizo de j (porque ya no hay compensación):

int i = 0;

Ahora, debido a que tengo que aumentar después de 2 usos, se incrementa en una declaración por separado:

if (v1[i] != v2[i]) return false; i++;

Creo que eso es todo, hay una implementación más concisa que es evidente si es para escribirlo desde cero, pero dado que fue un cambio impulsado por otro cambio ... Las personas de Oracle son personas comunes como nosotros :)

Benchmarking

En cuanto a la evaluación comparativa, String#equals Arrays#equals(char[], char[] ) Creo que tenemos que comparar manzanas con manzanas, por lo que he puesto los dos enfoques para comparar matrices de 2 caracteres juntos:

public static void main(String[] args) { final Random random = new Random(); final int arrays = 10000; final int chars = 1000; // generate the arrays char[][] array = new char[arrays][chars]; for (int i = 0; i < arrays; i++) { for (int j = 0; j < chars; j++) { array[i][j] = (char)(random.nextInt(94) + 33); } } // compare using Arrays equals long before = System.nanoTime(); for (int i = 0; i < arrays; i++) { for (int j = 0; j < chars; j++) { equals_Arrays(array[i], array[j]); } } System.out.println(System.nanoTime() - before); // compare using String equals before = System.nanoTime(); for (int i = 0; i < arrays; i++) { for (int j = 0; j < chars; j++) { equals_String(array[i], array[j]); } } System.out.println(System.nanoTime() - before); } private static boolean equals_Arrays(char[] a, char[] a2) { if (a == a2) return true; if (a == null || a2 == null) return false; int length = a.length; if (a2.length != length) return false; for (int i = 0; i < length; i++) if (a[i] != a2[i]) return false; return true; } private static boolean equals_String(char[] v1, char[] v2) { if (v1 == v2) return true; if (v1 == null || v2 == null) return false; int length = v1.length; if (length == v2.length) { int i = 0; while (length-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } return false; }

En mi caja no veo diferencia notable.


Esto NO ES UNA RESPUESTA, justa pseudo banchmark:

public class CompString { public static void main(String[] args) { DecimalFormat df = new DecimalFormat("0.#################"); int count = 1000000; int length = 20; int runs = 10; String[] strings = new String[count]; String[] copiedStrings = new String[count]; char[][] chars = new char[count][]; char[][] copiedChars = new char[count][]; for (int i = 0; i < count; i++) { String str = RandomStringUtils.random(length); strings[i] = new String(str); copiedStrings[i] = new String(str); chars[i] = Arrays.copyOf(str.toCharArray(), str.length()); copiedChars[i] = Arrays.copyOf(chars[i], chars[i].length); } System.out.println("Lets banchmark that !!"); int loop = 0; while (loop++ < runs) { System.out.println("Run #" + loop); long t = 0; long t0 = System.currentTimeMillis(); for (int i = 0; i < count; i++) { strings[i].equals(copiedStrings[i]); } long t1 = System.currentTimeMillis(); t = t1 - t0; System.out.println("Avg String.equals() duration: " + df.format(t / (double) count)); t0 = System.currentTimeMillis(); for (int i = 0; i < count; i++) { Arrays.equals(chars[i], copiedChars[i]); } t1 = System.currentTimeMillis(); t = t1 - t0; System.out.println("Avg Arrays.equals(char[] char[]) duration: " + df.format(t / (double) count)); System.out.println(); } } }

Y aquí están los resultados:

Run #1 Avg String.equals() duration: 0,000017 Avg Arrays.equals(char[] char[]) duration: 0,000013 Run #2 Avg String.equals() duration: 0,000037 Avg Arrays.equals(char[] char[]) duration: 0 Run #3 Avg String.equals() duration: 0,000002 Avg Arrays.equals(char[] char[]) duration: 0 Run #4 Avg String.equals() duration: 0,000003 Avg Arrays.equals(char[] char[]) duration: 0 Run #5 Avg String.equals() duration: 0,000002 Avg Arrays.equals(char[] char[]) duration: 0 Run #6 Avg String.equals() duration: 0,000003 Avg Arrays.equals(char[] char[]) duration: 0 Run #7 Avg String.equals() duration: 0,000003 Avg Arrays.equals(char[] char[]) duration: 0 Run #8 Avg String.equals() duration: 0,000003 Avg Arrays.equals(char[] char[]) duration: 0 Run #9 Avg String.equals() duration: 0,000002 Avg Arrays.equals(char[] char[]) duration: 0 Run #10 Avg String.equals() duration: 0,000002 Avg Arrays.equals(char[] char[]) duration: 0

Entonces, debido a que String.equals toma más tiempo que la comparación de matrices subyacentes (con una sola variable de iterador), podemos descartar algunos problemas de optimización de JVM mágicos aquí (¿pero podría estar en el pasado?).


No es un código real para la mayoría de las máquinas virtuales Java reales con HotSpot. String.equals es un método muy importante y se implementa a través de intrínseco. Tiene plataforma de implementación nativa específica. Puede encontrar la lista completa aquí src/share/vm/classfile/vmSymbols.hpp (vea do_intrinsic)