La belleza de los patrones de diseño 55-Patrón Flyweight (Parte 2): Análisis de la aplicación del patrón Flyweight en JavaInteger y String

55 | Modo Flyweight (Parte 2): Análisis de la aplicación del modo Flyweight en Java Integer y String

En la última clase, aprendimos el principio, la implementación y los escenarios de aplicación del modo Flyweight a través de dos ejemplos prácticos como el ajedrez, los juegos de cartas y los editores de texto. Para resumir en una oración, el "peso mosca" en el modo de peso mosca se refiere a la unidad compartida. El modo Flyweight ahorra memoria al reutilizar objetos.

Hoy les llevaré otra lección para analizar la aplicación del modo flyweight en Java Integer y String. Si no está familiarizado con el lenguaje de programación Java, no se preocupe por no entenderlo, porque el contenido de hoy presenta principalmente ideas de diseño y tiene poco que ver con el lenguaje en sí.

Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

Aplicación del modo Flyweight en Java Integer

Veamos primero el siguiente fragmento de código. Primero puede pensar en qué tipo de resultados generará este código.

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

Si no está familiarizado con el lenguaje Java, puede pensar que los valores de i1 e i2 son ambos 56, y los valores de i3 e i4 son ambos 129. Los valores de i1 e i2 son iguales, y los valores de i3 e i4 son iguales, por lo que el resultado de salida debería ser dos verdadero. Este tipo de análisis es incorrecto, principalmente porque no está familiarizado con la sintaxis de Java. Para analizar correctamente el código anterior, necesitamos resolver las siguientes dos preguntas:

  • ¿Cómo determinar si dos objetos Java son iguales (es decir, el significado del operador "==" en el código)?
  • ¿Qué es el Autoboxing y Unboxing?

En Add Meal 1 , mencionamos que Java proporciona tipos de contenedor correspondientes para tipos de datos básicos. Específicamente de la siguiente manera:

inserte la descripción de la imagen aquí

El llamado autoboxing consiste en convertir automáticamente el tipo de datos básico en un tipo contenedor. El llamado desempaquetado automático consiste en convertir automáticamente el tipo contenedor en un tipo de datos básico. Un ejemplo de código específico es el siguiente:

Integer i = 56; //自动装箱
int j = i; //自动拆箱

El valor 56 es el tipo de dato básico int. Cuando se asigna a una variable de tipo wrapper (Integer), se activa la operación de encuadre automático, se crea un objeto de tipo Integer y se asigna el valor a la variable i. Su capa inferior es equivalente a ejecutar la siguiente declaración:

Integer i = 59;底层执行了:Integer i = Integer.valueOf(59);

Por el contrario, cuando la variable i del tipo contenedor se asigna a la variable j del tipo de datos básico, se activa una operación de desempaquetado automático y los datos en i se extraen y se asignan a j. Su capa inferior es equivalente a ejecutar la siguiente declaración:

int j = i; 底层执行了:int j = i.intValue();

Después de descubrir el autoboxing y el autounboxing, veamos cómo determinar si dos objetos son iguales. Sin embargo, antes de eso, primero debemos averiguar cómo se almacenan los objetos Java en la memoria. Ilustremos con el siguiente ejemplo.

User a = new User(123, 23); // id=123, age=23

Para esta declaración, dibujé un diagrama de estructura de almacenamiento de memoria, como se muestra a continuación. El valor almacenado en a es la dirección de memoria del objeto Usuario, que se muestra en la figura apuntando al objeto Usuario.

inserte la descripción de la imagen aquí

Cuando usamos "==" para determinar si dos objetos son iguales, en realidad estamos juzgando si las direcciones almacenadas en las dos variables locales son las mismas, en otras palabras, estamos juzgando si las dos variables locales apuntan al mismo objeto.

Después de comprender estas gramáticas de Java, veamos el código del principio nuevamente.

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

Las primeras 4 líneas de instrucciones de asignación activarán la operación de encasillado automático, es decir, se creará un objeto Integer y se asignará a las cuatro variables i1, i2, i3 e i4. De acuerdo con la explicación de ahora, aunque i1 e i2 almacenan el mismo valor, los cuales son 56, apuntan a diferentes objetos Integer, así que pasa ""Para determinar si es lo mismo, devolverá falso. De manera similar, i3La declaración de juicio i4 también devuelve falso.

Sin embargo, el análisis anterior sigue siendo erróneo, la respuesta no es dos falsos, sino uno verdadero y uno falso. Al ver esto, usted puede estar más desconcertado. De hecho, esto se debe precisamente a que Integer usa el patrón Flyweight para reutilizar objetos, lo que conduce a tales resultados de ejecución. Cuando creamos un objeto Integer mediante autoboxing, es decir, llamando a valueOf(), si el valor del objeto Integer a crear está entre -128 y 127, será devuelto directamente desde la clase IntegerCache, de lo contrario será creado por llamando al nuevo método. Es más claro mirar el código.El código específico de la función valueOf() de la clase Integer es el siguiente:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

De hecho, IntegerCache aquí es equivalente a la clase de fábrica que genera objetos Flyweight de los que hablamos en la última lección, pero el nombre no es xxxFactory. Veamos su implementación de código específico. Esta clase es una clase interna de Integer y también puede ver el código fuente de JDK usted mismo.

/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

¿Por qué IntegerCache solo almacena en caché valores enteros entre -128 y 127?

En la implementación del código de IntegerCache, cuando se carga esta clase, los objetos flyweight almacenados en caché se crearán de una sola vez. Después de todo, hay demasiados valores enteros y es imposible para nosotros crear previamente todos los valores enteros en la clase IntegerCache. Esto no solo ocupará demasiada memoria, sino que también dificultará la carga de la clase IntegerCache. demasiado largo. Por lo tanto, solo podemos optar por almacenar en caché los valores enteros más utilizados para la mayoría de las aplicaciones, que es el tamaño de un byte (datos entre -128 y 127).

De hecho, JDK también proporciona métodos que nos permiten personalizar el valor máximo de la memoria caché, existen las siguientes dos formas. Si analiza el uso de la memoria JVM de la aplicación y encuentra que los datos entre -128 y 255 ocupan más memoria, puede usar el siguiente método para ajustar el valor máximo de la memoria caché de 127 a 255. Sin embargo, tenga en cuenta aquí que el JDK no proporciona una forma de establecer el valor mínimo.

//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255

Ahora, volvamos a la pregunta original, porque 56 está entre -128 y 127, i1 e i2 apuntarán al mismo objeto de peso mosca, entonces i1i2 devuelve verdadero. Y 129 es mayor que 127, no se almacenará en caché y se creará un nuevo objeto cada vez, es decir, i3 e i4 apuntan a diferentes objetos Integer, por lo que i3i4 devuelve falso.

De hecho, además del tipo Integer, otros tipos de contenedores, como Long, Short, Byte, etc., también usan el modo Flyweight para almacenar en caché datos entre -128 y 127. Por ejemplo, la clase de fábrica de peso mosca LongCache y el código de función valueOf() correspondiente al tipo Long son los siguientes:

private static class LongCache {
    private LongCache(){}

    static final Long cache[] = new Long[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

En nuestro desarrollo habitual, para las siguientes tres formas de crear objetos enteros, damos prioridad a las dos últimas.

Integer a = new Integer(123);
Integer a = 123;
Integer a = Integer.valueOf(123);

El primer método de creación no usa IntegerCache, y los dos últimos métodos de creación pueden usar el caché de IntegerCache para devolver objetos compartidos para ahorrar memoria. Para dar un ejemplo extremo, suponga que el programa necesita crear 10,000 objetos Integer entre -128 y 127. Usando el primer método de creación, necesitamos asignar espacio de memoria para 10,000 objetos Integer; usando los últimos dos métodos de creación, solo necesitamos asignar espacio de memoria para hasta 256 objetos Integer.

Aplicación del patrón Flyweight en Java String

Justo ahora hablamos sobre la aplicación del patrón flyweight en la clase Java Integer Ahora, veamos la aplicación del patrón flyweight en la clase Java String. Del mismo modo, echemos un vistazo primero a un fragmento de código. ¿Cuál crees que es el resultado de este código?

String s1 = "小争哥";
String s2 = "小争哥";
String s3 = new String("小争哥");

System.out.println(s1 == s2);
System.out.println(s1 == s3);

El resultado de ejecutar el código anterior es: uno verdadero, uno falso. Similar a la idea de diseño de la clase Integer, la clase String usa el patrón Flyweight para reutilizar la misma constante de cadena (es decir, el "hermano pequeño" en el código). La JVM abrirá especialmente un área de almacenamiento para almacenar constantes de cadena.Esta área de almacenamiento se denomina "grupo de constantes de cadena". La estructura de almacenamiento de memoria correspondiente al código anterior es la siguiente:

inserte la descripción de la imagen aquí

Sin embargo, el diseño del modo Flyweight de la clase String es ligeramente diferente al de la clase Integer. Los objetos que se compartirán en la clase Integer se crean en un momento en que se carga la clase. Sin embargo, para las cadenas, no podemos saber de antemano qué constantes de cadena compartir, por lo que no podemos crearlas por adelantado. Solo podemos almacenarlas en el grupo de constantes cuando se usa una determinada constante de cadena por primera vez. Cuando se vuelve a usar más tarde, es suficiente para referirse directamente a lo que ya existe en el conjunto de constantes, y no hay necesidad de recrearlo.

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos juntos, en qué necesitas concentrarte.

En la implementación de Java Integer, los objetos enteros entre -128 y 127 se crearán por adelantado y se almacenarán en caché en la clase IntegerCache. Cuando usamos autoboxing o valueOf() para crear un objeto entero de este rango de valores, se reutilizará el objeto creado previamente de la clase IntegerCache. La clase IntegerCache aquí es la clase de fábrica de peso ligero, y los objetos enteros creados previamente son objetos de peso ligero.

En la implementación de la clase Java String, la JVM abre un área de almacenamiento para almacenar constantes de cadena.Esta área de almacenamiento se denomina conjunto de constantes de cadena, similar a IntegerCache en Integer. Sin embargo, a diferencia de IntegerCache, no crea objetos que deban compartirse por adelantado, sino que crea y almacena en caché constantes de cadena según sea necesario durante la ejecución del programa.

Además, aquí agrego para enfatizar.

De hecho, el modo Flyweight no es compatible con la recolección de elementos no utilizados de JVM. Debido a que la clase de fábrica de disfrute siempre ha guardado la referencia al objeto de disfrute, lo que hace que el mecanismo de recolección de basura JVM no recicle automáticamente el objeto de disfrute sin ningún uso de código. Por lo tanto, en algunos casos, si el ciclo de vida del objeto es corto y no se utilizará de forma intensiva, el uso del patrón flyweight puede desperdiciar más memoria. Por lo tanto, a menos que se haya verificado en línea que usar el modo de peso ligero realmente puede ahorrar mucha memoria, de lo contrario, no use este modo en exceso. Introducir un modo de diseño complejo para ahorrar un poco de memoria no vale la pena.

discusión en clase

IntegerCache solo puede almacenar en caché objetos enteros preespecificados, por lo que podemos aprender de la idea de diseño de String, en lugar de especificar qué objetos enteros deben almacenarse en caché por adelantado, pero durante la ejecución del programa, cuando se usa un objeto entero Cuando se crea y se coloca en IntegerCache, cuando se usa la próxima vez, ¿se devolverá directamente desde IntegerCache?

Si puede hacer esto, vuelva a implementar la clase IntegerCache de acuerdo con esta idea, y puede hacer posible que el mecanismo de recolección de elementos no utilizados de JVM recicle un objeto cuando no haya ningún código en uso.

Bienvenido a dejar un mensaje y compartir sus pensamientos conmigo. Si gana algo, puede compartir este artículo con sus amigos.

Supongo que te gusta

Origin blog.csdn.net/fegus/article/details/130498813
Recomendado
Clasificación