Red de conocimiento informático - Material del sitio web - Cómo optimizar el código JAVA y mejorar la eficiencia de ejecución

Cómo optimizar el código JAVA y mejorar la eficiencia de ejecución

Los recursos disponibles para el programa (memoria, tiempo de CPU, ancho de banda de la red, etc.) son limitados. El propósito de la optimización es permitir que el programa complete las tareas programadas con la menor cantidad de recursos posible. La optimización generalmente incluye dos aspectos: reducir el tamaño del código y mejorar la eficiencia operativa del código. Este artículo analiza principalmente cómo mejorar la eficiencia del código.

En los programas Java, la mayoría de las causas de los problemas de rendimiento no residen en el lenguaje Java, sino en el propio programa. Es muy importante desarrollar buenos hábitos de codificación, como el uso correcto y hábil de las clases java.lang.String y java.util.Vector, lo que puede mejorar significativamente el rendimiento del programa. Analicemos este tema en detalle a continuación.

1. Intente especificar el modificador final de la clase. No se pueden derivar clases con modificadores finales. En la API central de Java, hay muchos ejemplos de aplicación de final, como java.lang.String. Especificar final para la clase String evita que las personas anulen el método length(). Además, si una clase se designa como final, todos los métodos de esa clase serán finales. El compilador de Java buscará oportunidades para alinear todos los métodos finales (esto depende de la implementación específica del compilador). Esto puede mejorar el rendimiento en una media del 50%

.

2. Intenta reutilizar objetos. Especialmente cuando se utilizan objetos String, se debe utilizar StringBuffer en su lugar cuando se produce la concatenación de cadenas. Debido a que el sistema no solo necesita tiempo para generar objetos, también puede tomar tiempo para recolectar basura y procesar estos objetos más adelante. Por tanto, generar demasiados objetos tendrá un gran impacto en el rendimiento del programa.

3. Intente utilizar variables locales. Los parámetros pasados ​​al llamar al método y las variables temporales creadas durante la llamada se guardan en la pila (Stack), que es más rápido. Otras variables, como variables estáticas, variables de instancia, etc., se crean en el montón y son más lentas. Además, dependiendo del compilador/JVM específico, las variables locales pueden optimizarse aún más. Consulte Usar variables de pila siempre que sea posible.

4. No inicialice las variables repetidamente. De forma predeterminada, al llamar al constructor de una clase,

Java inicializará las variables a un valor determinado: todos los objetos se establecen en números enteros nulos. Las variables (byte, short, int, long) se establecen en 0, las variables flotantes y dobles se establecen en 0,0 y los valores lógicos se establecen en falso. Esto debe tenerse en cuenta especialmente cuando una clase se deriva de otra clase, porque cuando se crea un objeto utilizando la nueva palabra clave, todos los constructores de la cadena de constructores se llamarán automáticamente.

5. En el desarrollo de sistemas de aplicaciones JAVA + ORACLE, las declaraciones SQL integradas en Java deben estar en mayúsculas tanto como sea posible para reducir la carga de análisis del analizador ORACLE.

6. Durante la programación Java, tenga cuidado al realizar conexiones de bases de datos y operaciones de flujo de E/S después de su uso, incluso cuando esté cerca de liberar recursos. Porque el funcionamiento de estos objetos grandes provocará una gran sobrecarga del sistema y un poco de descuido tendrá consecuencias graves.

7 Dado que la JVM tiene su propio mecanismo de GC, no requiere demasiada consideración por parte de los desarrolladores de programas, lo que reduce la carga para los desarrolladores hasta cierto punto, pero al mismo tiempo también evita peligros ocultos. y la creación excesiva de objetos consumirá una gran cantidad de memoria del sistema y, en casos graves, puede provocar pérdidas de memoria. Por lo tanto, es de gran importancia garantizar el reciclaje oportuno de los objetos caducados. La condición para que la JVM recopile basura es que ya no se haga referencia al objeto; sin embargo, el GC de la JVM no es muy inteligente e incluso si el objeto cumple con las condiciones para la recolección de basura, es posible que no se recicle inmediatamente. Por lo tanto, se recomienda que lo establezcamos manualmente en nulo después de usar el objeto.

8. Cuando utilice el mecanismo de sincronización, intente utilizar la sincronización de métodos en lugar de la sincronización de bloques de código.

9. Minimizar los cálculos repetidos de variables

Por ejemplo: for(int i = 0;i < list.size; i ++) {

}

Debe reemplazarse con:

for(int i = 0,int len ​​​​= list.size();i < len; i ++ ) {

}

10. Intenta adoptar la estrategia de carga diferida, es decir, empieza a crear cuando sea necesario.

Por ejemplo: String str = "aaa";

if(i == 1) {

list.add(str);

}

Debe reemplazarse con:

if(i == 1) {

String str = "aaa";

list.add(str);

}

11. Utilice excepciones con precaución

Las excepciones son perjudiciales para el rendimiento. Lanzar una excepción primero crea un nuevo objeto. El constructor de la interfaz Throwable llama al método nativo llamado fillInStackTrace(). El método fillInStackTrace() verifica la pila y recopila información de seguimiento de llamadas. Cada vez que se produce una excepción, la VM tiene que ajustar la pila de llamadas porque se crea un nuevo objeto durante el procesamiento. Las excepciones solo deben usarse para el manejo de errores y no deben usarse para controlar el flujo del programa.

12. No lo utilices en un bucle:

Prueba {

} catch() {

}

Se debe colocar en la capa más externa.

13. Uso de StringBuffer:

StringBuffer representa una cadena variable que se puede escribir.

Hay tres métodos de construcción:

StringBuffer (); //Asignar 16 caracteres de espacio por defecto

StringBuffer (int tamaño); Espacio de caracteres

StringBuffer (String str); //Asignar 16 caracteres + espacio de caracteres str.length()

Puede configurarlo a través del constructor de StringBuffer Inicializar la capacidad, lo que puede aumentar significativamente mejorar el rendimiento. El constructor mencionado aquí es StringBuffer (int

length), y el parámetro de longitud indica la cantidad de caracteres que puede contener el StringBuffer actual. También puede utilizar el método sureCapacity(int

minimumcapacity) para establecer la capacidad de un objeto StringBuffer después de su creación. Primero, veamos el comportamiento predeterminado de StringBuffer y luego busquemos una mejor manera de mejorar el rendimiento.

StringBuffer mantiene una matriz de caracteres internamente. Cuando usa el constructor predeterminado para crear un objeto StringBuffer, debido a que la longitud de caracteres inicial no está establecida, la capacidad de StringBuffer se inicializa a 16 caracteres, es decir. La capacidad predeterminada es de 16 caracteres. Cuando StringBuffer alcance su capacidad máxima, aumentará su capacidad a 2 veces el valor actual más 2, es decir (2*valor anterior + 2).

Si usa el valor predeterminado y luego le agrega caracteres después de la inicialización, cuando agrega el carácter 16, aumentará la capacidad a 34 (2*16+2), y cuando agrega 34 caracteres, aumentará Capacidad para 70 (2*34+2). Cada vez que StringBuffer alcanza su capacidad máxima, tiene que crear una nueva matriz de caracteres y copiar nuevamente los caracteres antiguos y nuevos, lo cual es demasiado costoso. Por lo tanto, es correcto establecer siempre un valor de capacidad inicial razonable para StringBuffer, lo que traerá ganancias de rendimiento inmediatas.

El efecto de ajustar el proceso de inicialización de StringBuffer se puede ver a partir de esto. Por lo tanto, siempre es la mejor sugerencia inicializar StringBuffer con un valor de capacidad apropiado.

14. Utilizar correctamente la clase Java java.util.Vector.

En pocas palabras, un vector es una matriz de instancias de java.lang.Object. Un Vector es similar a una Matriz en que se puede acceder a sus elementos a través de índices enteros. Sin embargo, después de crear un objeto de tipo Vector, el tamaño del objeto se puede expandir o reducir según la adición o eliminación de elementos. Considere el siguiente ejemplo de cómo agregar elementos a un vector:

Objeto obj = nuevo objeto();

Vector v = nuevo vector(100000);

for (int I=0;

I<100000; I++) { v.add(0,obj}

A menos que haya una buena razón para agregar nuevos elementos cada time Insértelo en la parte frontal del Vector; de lo contrario, el código anterior afectará negativamente al rendimiento. En el constructor predeterminado, la capacidad de almacenamiento inicial de Vector es de 10 elementos. Si la capacidad de almacenamiento es insuficiente cuando se agregan nuevos elementos, la capacidad de almacenamiento se duplicará cada vez en el futuro. La clase Vector es como la clase StringBuffer. Cada vez que se expande la capacidad de almacenamiento, todos los elementos existentes deben copiarse al nuevo espacio de almacenamiento. El siguiente fragmento de código es mucho más rápido que el ejemplo anterior:

Objeto obj = nuevo Objeto();

Vector v = nuevo Vector(100000);

for(int I=0; I<100000; I++) { v.add(obj); }

La misma regla también se aplica al método remove() de la clase Vector. Dado que no puede haber "espacios" entre elementos en Vector, eliminar cualquier otro elemento excepto el último hará que los elementos posteriores al elemento eliminado avancen. En otras palabras, eliminar el último elemento de un Vector es varias veces menos costoso que eliminar el primer elemento.

Supongamos que queremos eliminar todos los elementos del Vector anterior, podemos usar este código:

for(int I=0; I<100000; I++)

{

v.remove(0);

}

Sin embargo, en comparación con el siguiente código, el código anterior es varios órdenes de magnitud más lento:

for(int I=0; I<100000; I++)

{

v.remove(v.size()-1);

}

La mejor manera de eliminar todos los elementos de un objeto v de tipo Vector es:

v.removeAllElements();

Asumiendo tipo Vector El objeto v contiene la cadena "Hola".

Considere el siguiente código, que elimina la cadena "Hola" de este vector:

String s = "Hello";

int i = v.indexOf(s);

if(I != -1) v.remove(s);

Este código no parece incorrecto, pero también es perjudicial para el rendimiento. En este código, el método indexOf() realiza una búsqueda secuencial en v para encontrar la cadena "Hola", y el método remove(s) también realiza la misma búsqueda secuencial. La versión mejorada es:

String s = "Hello";

int i = v.indexOf(s);

if(I != - 1 ) v.remove(i);

En esta versión, damos directamente la posición de índice precisa del elemento a eliminar en el método remove(), evitando así una segunda búsqueda. Una versión mejor es:

String s = "Hello"; v.remove(s);

Finalmente, veamos un fragmento de código sobre la clase Vector:

p>

for(int I=0; I++;I < v.length)

Si v contiene 100.000 elementos, este fragmento de código llamará al método v.size() 100.000 veces. Aunque el método de tamaño es un método simple, todavía requiere la sobrecarga de una llamada al método. Al menos la JVM necesita configurar y borrar el entorno de pila. Aquí, el código dentro del bucle for no modificará el tamaño del objeto de tipo vectorial v de ninguna manera, por lo que es mejor reescribir el código anterior en la siguiente forma:

int size = v.size() ; for(int I=0; I++;I

Aunque este es un cambio simple, aún gana en rendimiento. Después de todo, cada ciclo de CPU es valioso.

15. Al copiar una gran cantidad de datos, utilice el comando System.arraycopy().

16. Refactorización de código: mejora la legibilidad del código.

Por ejemplo:

ShopCart de clase pública {

Lista de carritos privados;

Agregar vacío público ( Elemento de objeto) {

if(carts == null) {

carts = new ArrayList();

}

crts add(item);

}

public void eliminar(Objeto elemento) {

if(carts. contains(item)) {

carts.remove(item);

}

}

Lista pública getCarts() {

//Devolver lectura -solo Lista

return Collections.unmodifiableList(carts);

}

//Este método no se recomienda

//this .getCarts ().add(item);

}

17. Crear una instancia de una clase sin usar la nueva palabra clave

Cuando se usa la nueva palabra clave Para crear una instancia de una clase, se llama automáticamente a todos los constructores de la cadena de constructores. Pero si un objeto implementa la interfaz Cloneable, podemos llamar a su método clone().

El método clone() no llama a ningún constructor de clase.

Al usar Design Pattern, si usa el modo Factory para crear un objeto, es muy sencillo usar el método clone() para crear una nueva instancia de objeto. Por ejemplo, la siguiente es una implementación típica del patrón Factory:

crédito estático público getNewCredit() {

return new Credit();

}

El código mejorado utiliza el método clone() de la siguiente manera:

Crédito estático privado BaseCredit = new Credit();

Crédito estático público getNewCredit() {< / p>

return (Credit) BaseCredit.clone();

}

La idea anterior también es muy útil para el procesamiento de matrices.

18. Multiplicación y División

Considera el siguiente código:

for (val = 0; val < 100000; val +=5) {

alterX = val * 8; myResult = val * 2;

}

Reemplazar las operaciones de multiplicación con operaciones de desplazamiento puede mejorar enormemente el rendimiento. El siguiente es el código modificado:

for (val = 0; val < 100000; val += 5) {

alterX = val << 3; ;

}

El código modificado ya no realiza la operación de multiplicar por 8, sino que utiliza la operación equivalente de desplazar 3 bits hacia la izquierda. Cada desplazamiento a la izquierda de 1 bit es. equivalente a multiplicar Toma 2. En consecuencia, una operación de desplazamiento a la derecha de 1 bit equivale a dividir por 2. Cabe mencionar que aunque la operación de cambio es rápida, puede hacer que el código sea más difícil de entender, por lo que es mejor agregar algunos comentarios.

19. Cerrar sesiones inútiles en páginas JSP.

Un malentendido común es que la sesión se crea cuando un cliente accede a ella. Sin embargo, el hecho es que no se crea hasta que un programa del lado del servidor llama a una declaración como HttpServletRequest.getSession(true). Tenga en cuenta que si JSP utiliza <%@pagesession="false"%> para cerrar la sesión sin mostrarla, el archivo JSP agregará automáticamente dicha declaración HttpSession

cuando se compila en una sesión de servlet. = HttpServletRequest.getSession(true); Este es también el origen del objeto de sesión implícito en JSP. Dado que la sesión consume recursos de memoria, si no planea utilizar la sesión, debe cerrarla en todos los JSP.

Para las páginas que no necesitan realizar un seguimiento del estado de la sesión, desactivar las sesiones creadas automáticamente puede ahorrar algunos recursos. Utilice el siguiente comando de página: <%@ page session="false"%>

20 JDBC y E/S

Si la aplicación necesita acceder a un gran conjunto de datos, usted. debería considerar el uso de la extracción de bloques. De forma predeterminada, JDBC recupera 32 filas de datos a la vez. Por ejemplo, suponiendo que queremos recorrer un conjunto de registros de 5000 filas, JDBC debe llamar a la base de datos 157 veces para extraer todos los datos. Si cambia el tamaño del bloque a 512, la cantidad de llamadas a la base de datos se reducirá a 10.

[p][/p]21. Servlets y uso de la memoria

Muchos desarrolladores guardan arbitrariamente grandes cantidades de información en las sesiones de los usuarios. A veces, el mecanismo de recolección de basura no recupera los objetos almacenados en la sesión de manera oportuna.

Desde una perspectiva de rendimiento, un síntoma típico es que los usuarios sienten que el sistema se ralentiza periódicamente, pero no pueden atribuir la causa a ningún componente específico. Si monitorea el espacio de almacenamiento dinámico de la JVM, su rendimiento es que el uso de la memoria aumenta y disminuye de manera anormal.

Existen dos formas principales de solucionar este tipo de problema de memoria. El primer enfoque consiste en implementar la interfaz HttpSessionBindingListener en todos los beans con ámbito de sesión. De esta manera, siempre que se implemente el método valueUnbound (), los recursos utilizados por el Bean se pueden liberar explícitamente. Otra forma es invalidar la sesión lo antes posible. La mayoría de los servidores de aplicaciones tienen la opción de establecer el intervalo de caducidad de la sesión. Además, también puede llamar al método setMaxInactiveInterval() de la sesión mediante programación. Este método se utiliza para establecer el intervalo máximo en segundos que el contenedor de servlet permite las solicitudes del cliente antes de invalidar la sesión.

22. Utilice etiquetas de búfer

Algunos servidores de aplicaciones han agregado funciones de etiquetas de búfer para JSP. Por ejemplo, WebLogic Server de BEA admite esta función a partir de la versión 6.0, y el proyecto Open

Symphony también admite esta función. La etiqueta de búfer JSP puede almacenar en búfer tanto fragmentos de página como toda la página. Cuando se ejecuta la página JSP, si el fragmento de destino ya está en el búfer, no es necesario ejecutar el código que genera el fragmento. El almacenamiento en búfer a nivel de página captura las solicitudes de una URL específica y almacena en búfer toda la página resultante. Esta característica es extremadamente útil para carritos de compras, catálogos y páginas de inicio de portales. Para dichas aplicaciones, el almacenamiento en caché a nivel de página puede guardar los resultados de la ejecución de la página para solicitudes posteriores.

23. Elija un mecanismo de referencia apropiado

En un sistema de aplicación JSP típico, las partes del encabezado y pie de página a menudo se extraen y luego se introducen según sea necesario. Actualmente, existen dos formas principales de introducir recursos externos en páginas JSP: incluir instrucciones e incluir acciones.

directiva include: por ejemplo <%@ include file="copyright.html"

%>. Esta directiva introduce el recurso especificado en el momento de la compilación. Antes de la compilación, las páginas con directivas de inclusión y recursos específicos se combinan en un solo archivo. Los recursos externos a los que se hace referencia se determinan en tiempo de compilación, lo que es más eficiente que determinar los recursos en tiempo de ejecución.

incluir acción: por ejemplo

/>. Esta acción introduce los resultados generados después de la ejecución de la página especificada. Dado que se realiza en tiempo de ejecución, tiene un control más flexible sobre la salida. Sin embargo, solo vale la pena utilizar la acción de inclusión cuando el contenido al que se hace referencia cambia con frecuencia o cuando la página a la que se hace referencia no se puede determinar antes de que se produzca una solicitud de la página principal.

24. Borrar las sesiones que ya no son necesarias de manera oportuna

Para borrar las sesiones que ya no están activas, muchos servidores de aplicaciones tienen un tiempo de espera de sesión predeterminado, generalmente 30 minutos. . Cuando el servidor de aplicaciones necesita guardar más sesiones, si la capacidad de la memoria es insuficiente, el sistema operativo transferirá algunos datos de la memoria al disco y el servidor de aplicaciones también puede utilizar el "Usado más recientemente" (Más

Recientemente

Usado) descarga parte de la sesión inactiva al disco e incluso puede generar una excepción de "memoria insuficiente". En sistemas de gran escala, serializar sesiones es costoso. Cuando la sesión ya no es necesaria, se debe llamar inmediatamente al método HttpSession.invalidate() para borrar la sesión. El método HttpSession.invalidate() normalmente se puede llamar en la página de salida de la aplicación.

25. No declarar el array como: public static final.

26. Discusión sobre la eficiencia transversal de HashMap

A menudo encontramos operaciones transversales en pares clave y valor en HashMap. Hay dos métodos: Map paraMap. = new HashMap();

............//Primer bucle

Set appFieldDefIds = paraMap .keySet();

for (String appFieldDefId : appFieldDefIds) {

String[] valores = paraMap.get(appFieldDefId);

.. ....

}

//Segundo bucle

for(Entry entrada: paraMap.entrySet()){

String appFieldDefId = entrada.getKey();

String[] valores = entrada.getValue();

.. .....

}

La primera implementación obviamente no es tan eficiente como la segunda implementación.

El análisis es el siguiente Set appFieldDefIds = paraMap.keySet(); es obtener primero el keySet de HashMap

El código es el siguiente:

conjunto público keySet() {

Conjunto ks = keySet;

return (ks != null ? ks : (keySet = nuevo KeySet()) );

}

Clase privada KeySet extiende AbstractSet {

Iterador público iterador() {

retorno newKeyIterator();

}

public int size() {

tamaño de retorno;

}

booleano público contiene (Objeto o) {

return contieneClave(o);

}

eliminación booleana pública (Objeto o) {

return HashMap.this.removeEntryForKey (o) != null;

}

public void clear() {

HashMap.this.clear();

}

}

De hecho, devuelve un KeySet de clase privada, que hereda de AbstractSet e implementa la interfaz Set.

Veamos la sintaxis del bucle for/in nuevamente

for(declaración: expresión_r)

declaración

se traduce a Las siguientes fórmulas

for(Iterator #i = (expression_r).iterator(); #i.hashNext();){

declaración = #i.next( );

declaración

}

Entonces, en la primera declaración for (String appFieldDefId: appFieldDefIds) HashMap.keySet().iterator() y este método llama a newKeyIterator()

Iterator newKeyIterator() {

devuelve nuevo KeyIterator();

}

clase privada KeyIterator extiende HashIterator {

public K next() {

return nextEntry().getKey();

}

}

Por lo tanto, todavía se llama for

En el segundo bucle for(Entry entrada: paraMap.entrySet()) El iterador utilizado es un clase interna de la siguiente manera

clase privada EntryIterator extiende HashIterator> {

Map.Entry público siguiente () {

return nextEntry();

}

}

En este momento, el primer bucle obtiene la clave y el segundo bucle obtiene la Entrada de HashMap

La eficiencia se refleja en el bucle. Sinceramente, el segundo bucle puede obtener directamente los valores clave y de valor.

Pero el primer bucle aún tiene que reutilizar HashMap. (Clave de objeto) para obtener el valor

Ahora mire el método get(Clave de objeto) de HashMap

public V get(Clave de objeto) {

Objeto k = maskNull(key);

int hash = hash(k);

int i = indexFor(hash, table.length); //Entrada[] tabla

Entrada e = tabla;

mientras (verdadero) {

si (e == nulo)

devuelve nulo;

if (e.hash == hash && eq(k, e.key))

return e.value;

e = e.next;

}

}

De hecho, es usar el valor Hash nuevamente para sacar la Entrada correspondiente para comparar y obtener el resultado, por lo que usar el El primer ciclo equivale a ingresar dos veces al HashMap en la Entrada.

El segundo ciclo obtiene el valor de la Entrada y obtiene directamente la clave y el valor, que es más eficiente que el primer ciclo.

De hecho, según el concepto de Map, debería ser mejor usar el segundo bucle. Originalmente es un par de clave y valor. Aquí no es una buena opción operar la clave y el valor.