Cómo optimizar el diseño y la codificación de programas Java y mejorar el rendimiento de Java
Lo siguiente le proporciona algunos métodos y técnicas que se utilizan a menudo en el diseño y codificación de programas JAVA para mejorar el rendimiento de los programas JAVA:
1. Generación y cambio de tamaño de objetos.
Un problema común en la programación JAVA es que las funciones proporcionadas por el propio lenguaje JAVA no se utilizan en su totalidad, lo que muchas veces genera una gran cantidad de objetos (o instancias). 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.
Ejemplo 1: Acerca de String, StringBuffer, + y append
El lenguaje JAVA proporciona operaciones sobre variables de tipo String. Pero si se usa incorrectamente, afectará el rendimiento del programa. Como la siguiente declaración:
String name=new String("HuangWeiFeng");
System.out.println(name+"es mi nombre"); p >Parece muy simplificado, pero no lo es. Para generar código binario se deben realizar los siguientes pasos y operaciones:
(1) Generar una nueva cadena new String (STR_1);
(2) Copiar la cadena;
(3) Cargar la constante de cadena "HuangWeiFeng" (STR_2);
(4) Llamar al constructor de cadena (Constructor);
(5) Guardar; la cadena en la matriz (comenzando desde la posición 0);
(6) Obtener la variable de salida estática de la clase java.io.PrintStream
(7) Generar un nuevo búfer de cadena; variable new StringBuffer(STR_BUF_1);
(8) Copiar la variable del búfer de cadena
(9) Llamar al constructor del búfer de cadena (Constructor); >(10) Guarde el búfer de cadena en la matriz (comenzando desde la posición 1);
(11) Con STR_1 como parámetro, llame al método append de clase del búfer de cadena (StringBuffer); >(12) Cargar la constante de cadena "es mi nombre" (STR_3);
(13) Usar STR_3 como parámetro para llamar al método append de la clase StringBuffer
(14) Ejecutar; Comando toString para STR_BUF_1;
(15) Llame al método println en la variable out para generar el resultado.
Se puede observar que estas dos simples líneas de código generan cinco variables de objeto STR_1, STR_2, STR_3, STR_4 y STR_BUF_1. Las instancias de estas clases generadas generalmente se almacenan en el montón. El montón debe inicializar las superclases y las instancias de todas las clases y, al mismo tiempo, llamar al constructor de la clase y de cada superclase. Estas operaciones consumen muchos recursos del sistema. Por tanto, es completamente necesario limitar la generación de objetos.
Después de la modificación, el código anterior se puede reemplazar con el siguiente código.
StringBuffer name=new StringBuffer("HuangWeiFeng");
System.out.println(name.append("es mi nombre").toString() ); p>
p>
El sistema realizará las siguientes operaciones:
(1) Generar una nueva variable de búfer de cadena new StringBuffer(STR_BUF_1);
(2) Copie la variable del búfer de cadena
(3) Cargue la constante de cadena "HuangWeiFeng"(STR_1)
(4) Llame al constructor (Constructor) del búfer de cadena ; p>
(5) Guarde el búfer de cadena en la matriz (comenzando desde la posición 1);
(6) Obtenga la variable de salida estática de la clase java.io.PrintStream;
(7) Cargar STR_BUF_1;
(8) Cargar la constante de cadena "es mi nombre"(STR_2);
(9) Llamar al búfer de cadena (StringBuffer) con STR_2 como parámetro) agregar método en la instancia
(10) Ejecutar el comando toString (STR_3) para STR_BUF_1
(11) Llamar al método println en la variable out para generar el resultado.
Se puede ver que el código mejorado solo genera cuatro variables de objeto: STR_1, STR_2, STR_3 y STR_BUF_1. Puede sentir que generar un objeto menos no tendrá un gran impacto en el rendimiento del programa. mejora. Pero el fragmento de código 2 a continuación se ejecutará dos veces más rápido que el fragmento de código 1. Porque el fragmento de código 1 genera ocho objetos y el fragmento de código 2 solo genera cuatro objetos.
Segmento de código 1:
String name= new StringBuffer("HuangWeiFeng");
name+="es mi"; name+="name";
Segmento de código 2:
StringBuffer name=new StringBuffer("HuangWeiFeng");
name.append("es mi" );
name.append("name.").toString();
Por lo tanto, hacer un uso completo de las funciones de la biblioteca proporcionadas por JAVA para optimizar el programa mejorará el rendimiento. del programa JAVA Es muy importante prestar atención a los siguientes aspectos:
(1) Utilice variables estáticas (Variables de clase estáticas) tanto como sea posible
Si las variables en. la clase no cambiará aleatoriamente. Si cambia entre sus instancias, se puede definir como una variable estática, de modo que todas sus instancias puedan compartir esta variable.
Ejemplo:
clase pública foo
{
SomeObject so=new SomeObject()
}
Se puede definir como:
clase pública foo
{
static SomeObject so=new SomeObject()
}
(2) No realice demasiados cambios en los objetos generados.
Para algunas clases (como la clase String), es mejor regenerar una nueva instancia de objeto que modificar la instancia de objeto ya generada.
Ejemplo:
Cadena nombre="Huang";
nombre="Wei"
nombre="Feng"; /p>
El código anterior genera tres instancias de objeto de tipo String. Los dos primeros requieren inmediatamente que el sistema realice la recolección de basura. Si desea concatenar cadenas, el rendimiento será aún peor, porque el sistema no tendrá que generar más variables temporales para esto, como se muestra en el Ejemplo 1 anterior.
(3) Al generar un objeto, asígnele un espacio y un tamaño razonables. Muchas clases en JAVA tienen sus tamaños de asignación de espacio predeterminados. Para la clase StringBuffer, el tamaño de espacio asignado predeterminado es de 16 caracteres. Si el tamaño del espacio de StringBuffer utilizado en el programa no es de 16 caracteres, entonces se debe realizar una inicialización correcta.
(4) Evite generar objetos o variables que se utilicen poco o que tengan un ciclo de vida corto. Para este caso, se debe definir un grupo de búfer de objetos. Se cree que la sobrecarga de administrar un grupo de búfer de objetos es mucho menor que la sobrecarga de generar y reciclar objetos con frecuencia.
(5) Inicializar solo dentro del alcance del objeto. JAVA permite definir e inicializar objetos en cualquier parte del código. De esta forma, la inicialización solo se puede realizar dentro del alcance del objeto. Esto ahorra gastos generales del sistema.
Ejemplo:
AlgúnObjeto so=new AlgúnObjeto();
Si(x==1) entonces
{
Foo=so.getXX();
}
Se puede modificar a:
if(x==1) entonces
{
AlgúnObjeto entonces=nuevo AlgúnObjeto();
Foo=so.getXX()
}
; 2. Excepciones (Exceptions)
El lenguaje JAVA proporciona try/catch para facilitar a los usuarios detectar excepciones y manejarlas. Sin embargo, si se usa incorrectamente, también afectará el rendimiento del programa JAVA. Por lo tanto, debemos prestar atención a los dos puntos siguientes:
(1) Evite usar try/catch para la lógica de la aplicación
Si se puede procesar con if, while y otras declaraciones lógicas , luego intente. Es posible no utilizar declaraciones try/catch.
(2) Reutilizar excepciones
Cuando se deben manejar excepciones, los objetos de excepción existentes deben reutilizarse tanto como sea posible. Creo que en el manejo de excepciones, generar un objeto de excepción consume la mayor parte del tiempo.
3. Threading
Los threads se utilizan generalmente en una aplicación de alto rendimiento. Porque los subprocesos pueden aprovechar al máximo los recursos del sistema. Mientras otros subprocesos esperan la lectura y escritura del disco duro o de la red, el programa puede continuar procesándose y ejecutándose. Sin embargo, el uso inadecuado de subprocesos también afectará el rendimiento del programa.
Ejemplo 2: Uso correcto de la clase Vector
Vector se utiliza principalmente para guardar varios tipos de objetos (incluidos objetos del mismo tipo y de diferentes tipos). Sin embargo, en algunos casos, su uso tendrá un impacto en el rendimiento del programa. Esto está determinado principalmente por dos características de la clase Vector. Primero, Vector proporciona protección de seguridad para subprocesos. Aunque muchos métodos de la clase Vector están sincronizados. Pero si ha confirmado que su aplicación es de un solo subproceso, la sincronización de estos métodos es completamente innecesaria. En segundo lugar, cuando se buscan varios objetos almacenados en Vector, a menudo lleva mucho tiempo realizar la coincidencia de tipos. Cuando estos objetos son todos del mismo tipo, estas coincidencias son completamente innecesarias.
Por lo tanto, es necesario diseñar una clase o colección de un solo subproceso que guarde objetos de un tipo específico para reemplazar la clase Vector. El programa de reemplazo es el siguiente (StringVector.java):
clase pública StringVector <. /p>
{
datos de cadena privada []
recuento de int privado
Vector de cadena público()
{
this(10); // el tamaño predeterminado es 10
}
public StringVector(int inicialSize)
{
datos = new String[initialSize]
}
public void add(String str)
{
/ / ignorar cadenas nulas
if(str == null) { return }
ensureCapacity(count + 1);
data[count++] = str;
}
private void asegurarCapacity(int minCapacity)
{
int oldCapacity = data.length
if (minCapacity > oldCapacity)
{
String oldData[] = datos
int newCapacity = oldCapacity * 2; datos = nueva cadena [nuevaCapacidad];
System.arraycopy(oldData, 0, data, 0, count
}
}
);public void remove(String str)
{
if(str == null) { return; // ignorar null str }
for( int i = 0 ; i < count; i++)
{
// comprobar si hay una coincidencia
if(data[i].equals(str) )
{
System.arraycopy(data,i+1,data,i,count-1); // copiar datos
// permitir previamente elemento de matriz válido ser gc′d
data[--count] = null
return
}
} p>
}
cadena final pública getStringAt(int index)
{
if(index < 0) { return null }
else if(index > count) { return null; // el índice es > # cadenas }
else { return data[index] // el índice es g;
ood }
}
}
Entonces, el código:
Vector Strings=new Vector()
<; p >Strings.add("Uno");Strings.add("Dos");
String Second=(String)Strings.elementAt(1); >
Se puede reemplazar con el siguiente código:
StringVector Strings=new StringVector()
Strings.add("One"); Strings. add("Two");
String Second=Strings.getStringAt(1);
De esta forma, se puede mejorar el rendimiento del programa JAVA optimizando los subprocesos.
El programa utilizado para las pruebas es el siguiente (TestCollection.java):
import java.util.Vector
public class TestCollection
{
;public static void main(String args [])
{
TestCollection recolectar = new TestCollection()
if(args.length == 0)
{
System.out.println("Uso: java TestCollection [vector | stringvector]");
System.out.println("Uso: java TestCollection [ vector | stringvector ]"); p>
}
if(args[0].equals("vector "))
{
Tienda de vectores = nuevo Vector();
inicio largo = System.currentTimeMillis();
para (int i = 0; i < 1000000; i++)
{
store.addElement("cadena"
}
final largo = System.currentTimeMillis();
System.out.println((finalizar-inicio));
inicio = System.currentTimeMillis(); p>for(int i = 0; i < 1000000; i++)
{
Resultado de cadena = (String)store.elementAt(i); }
finish = System.currentTimeMillis();
System.out .println((finish-start));
}
else if(args[0].equals("stringvector"))
{ p>
StringVector store = new StringVector()
inicio largo = System; .currentTimeMillis();
for(int i = 0; i < 1000000; i++) { store.add("cadena" }
final largo = System.currentTimeMillis(
System.out.println((finalizar-inicio));
inicio = System.currentTimeMillis(); ; i < 1000000; i++) {
Resultado de cadena = store.getStringAt(i);
}
finish = System.currentTimeMillis(); p>
System.out.println((finalizar-inicio));
}
}
>
}
En cuanto al funcionamiento de los hilos, debemos prestar atención a los siguientes aspectos:
(1) Evitar la sincronización excesiva
Como se muestra arriba La sincronización innecesaria a menudo da como resultado un rendimiento reducido del programa. Por lo tanto, si el programa tiene un solo subproceso, no debe utilizar la sincronización.
(2) Sincronizar métodos en lugar de sincronizar todo el segmento de código
Sincronizar un método o función tiene mejor rendimiento que sincronizar todo el segmento de código.
(3) Utilice múltiples mecanismos de "bloqueo" para cada objeto para aumentar la concurrencia.
Generalmente, cada objeto tiene solo un "bloqueo", lo que significa que si dos subprocesos ejecutan dos métodos de sincronización diferentes de un objeto, se producirá un "punto muerto". Aunque estos dos métodos no comparten ningún recurso. Para evitar este problema, puede implementar un mecanismo de "bloqueo múltiple" en un objeto. Como se muestra a continuación:
clase foo
{
private static int var1
private static Object lock1=new Object();
privado estático int var2;
objeto estático privado lock2=nuevo objeto();
incremento vacío estático público1()
{
sincronizado(lock1)
{
var1++
}
}
incremento de vacío estático público2()
{
sincronizado(lock2)
{
var2++; }
}
}
4. Entrada y salida (E/S)
La entrada y salida incluyen muchos aspectos, pero los más involucrados son las operaciones de lectura y escritura en el disco duro, la red o la base de datos. Para operaciones de lectura y escritura, se dividen en caché y no caché; para operaciones de base de datos, hay muchos tipos de controladores JDBC para elegir. Pero pase lo que pase, tendrá un impacto en el desempeño del programa. Por lo tanto, debe prestar atención a los siguientes puntos:
(1) Utilice el búfer de entrada y salida
Utilice la mayor cantidad de caché posible. Pero si desea actualizar el caché con frecuencia, se recomienda no utilizarlo.
(2) Flujo de salida (Output Stream) y cadena Unicode
Al usar Output Stream y cadena Unicode, la sobrecarga de la clase Write era relativamente alta. Porque necesita convertir Unicode a byte (byte). Por lo tanto, si es posible, implemente la conversión antes de usar la clase Write o use la clase OutputStream en lugar de la clase Writer.
(3) Utilice transitorio cuando se requiera serialización
Al serializar una clase u objeto, aquellos tipos atómicos (atómicos) o elementos que se pueden reconstruir deben representarse como tipo transitorio. Esto elimina la necesidad de serializar cada vez. Si estos objetos serializados se van a transmitir a través de la red, este pequeño cambio tendrá una gran mejora en el rendimiento.
(4) Usar caché (Cache)
Para objetos o datos que se usan con frecuencia y no cambian mucho, puedes almacenarlos en el caché. Esto puede mejorar la velocidad de acceso. Esto es especialmente importante para los conjuntos de resultados devueltos por la base de datos.
(5) Utilice un controlador JDBC rápido (Driver)
JAVA proporciona cuatro métodos para acceder a la base de datos. Dos de ellos son controladores JDBC. Uno es un controlador local subcontratado por JAVA y el otro es un controlador JAVA completo. El específico a utilizar depende del entorno de implementación de JAVA y de la aplicación en sí.
5. Algunas otras experiencias y técnicas
(1) Utilice variables locales.
(2) Evite llamar a funciones o métodos (get o set) en la misma clase para configurar o llamar variables.
(3) Evite generar la misma variable o llamar a la misma función en un bucle (lo mismo ocurre con las variables de parámetros).
(4) Utilice palabras clave estáticas, finales, privadas y otras tanto como sea posible.
(5) Al copiar una gran cantidad de datos, utilice el comando System.arraycopy().