Sie sind auf Seite 1von 27

6.

Flujos y archivos
Competencia no. 6 Descripción: Comprende y aplica la clasificación de
archivos y operaciones básicas sobre
éstos para manipular su información.

Temas y subtemas
6. Flujos y archivos
6.1. Definición.
6.2. Clasificación: Archivos de texto y binarios.
6.3. Operaciones básicas y tipos de acceso.
6.4. Manejo de objetos persistentes.

Matriz de evaluación
Tareas 10%
Práctica 40%
Examen escrito 50%

Fuentes de información
• Dale Nell et al - Programming and Problem Solving with Java 2013.
• Kathy Sierra - Head First Java, Oreilly 2005.
• Deitel Java How to Program, 7th Edition.
• Mc Graw Hill, Serie Schaum Programacion en Java 2
• Barnes & Kolling - Objects First with Java Using BlueJ 5 ed.
• https://docs.oracle.com/javase/8/docs/api/?java/io/File.html

6.1. Definición
La información que necesita un programa para su función se obtiene mediante una
entrada de datos de una fuente que puede ser de tipos muy variados: desde el
teclado, un archivo, una comunicación de red, un objeto en internet, etc. Cuando
el programa genera los resultados como salida de la ejecución puede hacerlo de muy
diversas maneras: en un archivo, en la pantalla, en una impresora, etc.

En java la entrada de los datos se realiza mediante un flujo de entrada. La salida de


datos realiza mediante un flujo de salida.

Esquema para trabajar con los flujos de datos Entrada/Salida.


Un Flujo de E/S representa una fuente de entrada y un destino de salida. Un flujo
puede representar muchos tipos diferentes de fuentes y destinos, incluyendo
archivos de disco, dispositivos, otros programas y arreglos de memoria.

Los flujos soportan muchos tipos diferentes de datos, incluyendo bytes simples, tipos
primitivos de datos, caracteres localizados, y objetos. Algunos flujos simplementa
pasan datos, otros manipulan y transforman datos en formas útiles.
No importa cómo trabajan internamente; todos los flujos presentan el mismo
modelo simple a los programas que los usan: un flujo es una secuencia de datos.
Un programa usa un flujo de entrada para leer datos desde una fuente, o artículo a
la vez:

Leyendo información hacia un programa.


Un programa usa un flujo de salida para escribir datos a su destino, o artículo a la
vez:
Escribiendo información desde un programa.

La fuente y el destino de datos retratado arriba puede ser cualquier cosa que
mantenga, genere o consuma datos. Obviamente esto incluye archivos en disco,
pero una fuente o destino puede también ser otro programa, un dispositivo
periférico, un socket de red, o un arreglo.

TIPOS DE FLUJOS

Existen dos tipos de Flujos:

-Los que trabajan con Bytes


-Los que trabajan con Caracteres

Las clases mas importantes a tener en cuenta son las siguientes, donde el sangrado
de las líneas indica la herencia, es decir, DataInputStream hereda de
FilterInputStream que, a su vez, hereda de InputStream.
TIPOS DE FLUJOS
Existen dos tipos de Flujos:

-Los que trabajan con Bytes


-Los que trabajan con Caracteres

Las clases mas importantes a tener en cuenta son las siguientes, donde el sangrado
de las líneas indica la herencia, es decir, DataInputStream hereda de
FilterInputStream que, a su vez, hereda de InputStream.

6.2 Clasificación: Archivos de texto y binarios.


Los archivos de texto plano son aquellos que están compuestos únicamente por
texto sin formato, solo caracteres. estos caracteres se pueden codificar de distintos
modos dependiendo de la lengua usada. Se les conoce también como archivos de
texto llano o texto simple por carecer de información destinada a generar formatos
y tipos de letra.

Un archivo binario es una archivo informático que contiene información de cualquier


tipo, codificada en forma binaria para el propósito de almacenamiento y
procesamiento de ordenadores.

Muchos formatos binarios contienen partes que pueden ser interpretados como
texto. Un archivo binario que solo contiene información de tipo textual sin
información sobre el formato del mismo, se dice que es un archivo de texto plano.
Habitualmente se contraponen los términos archivo binario y archivo de texto de
forma que los primeros no contienen solamente texto.

6.3 Operaciones básicas y tipos de acceso.


Normalmente, cuando se codifica un programa, se hace con la intención de que ese
programa pueda interactuar con los usuarios del mismo, es decir, que el usuario
pueda pedirle que realice cosas y pueda suministrarle datos con los que se quiere
que haga algo. Una vez introducidos los datos y las órdenes, se espera que el
programa manipule de alguna forma esos datos para proporcionarnos una respuesta
a lo solicitado.

Además, en muchas ocasiones interesa que el programa guarde los datos que se le
han introducido, de forma que si el programa termina los datos no se pierdan y
puedan ser recuperados en una sesión posterior. La forma más normal de hacer esto
es mediante la utilización de ficheros que se guardarán en un dispositivo de memoria
no volátil (normalmente un disco).

A todas estas operaciones, que constituyen un flujo de información del programa


con el exterior, se les conoce como Entrada/Salida (E/S).

Existen dos tipos de E/S; la E/S estándar que se realiza con el terminal del usuario
y la E/S a través de fichero, en la que se trabaja con ficheros de disco.
Todas las operaciones de E/S en Java vienen proporcionadas por el paquete estándar
de la API de Java denominado java.io que incorpora interfaces, clases y excepciones
para acceder a todo tipo de ficheros. En este tutorial sólo se van a dar algunas
pinceladas de la potencia de este paquete.

Entrada/Salida estándar

Aquí sólo trataremos la entrada/salida que se comunica con el usuario a través de


la pantalla o de la ventana del terminal.

El acceso a la entrada y salida estándar es controlado por tres objetos que se crean
automáticamente al iniciar la aplicación: System.in, System.out y System.err

a.) System.in
Este objeto implementa la entrada estándar (normalmente el teclado). Los métodos
que nos proporciona para controlar la entrada son:
• read(): Devuelve el carácter que se ha introducido por el teclado leyéndolo
del buffer de entrada y lo elimina del buffer para que en la siguiente lectura sea
leído el siguiente carácter. Si no se ha introducido ningún carácter por el teclado
devuelve el valor -1.
• skip(n): Ignora los n caracteres siguientes de la entrada.
b.) System.out
Este objeto implementa la salida estándar. Los métodos que nos proporciona para
controlar la salida son:
• print(a): Imprime a en la salida, donde a puede ser cualquier tipo básico Java
ya que Java hace su conversión automática a cadena.
• println(a): Es idéntico a print(a) salvo que con println() se imprime un salto
de línea al final de la impresión de a.
c.) System.err
Este objeto implementa la salida en caso de error. Normalmente esta salida es la
pantalla o la ventana del terminal como con System.out, pero puede ser interesante
redirigirlo, por ejemplo hacia un fichero, para diferenciar claramente ambos tipos de
salidas.
Las funciones que ofrece este objeto son idénticas a las proporcionadas
por System.out.

Ejemplo
A continuación vemos un ejemplo del uso de estas funciones que acepta texto hasta
que se pulsa el retorno de carro e informa del número de caracteres introducidos.

import java.io.*;
class CuentaCaracteres {
public static void main(String args[]) throws IOException {
int contador=0;
while(System.in.read()!='\n')
contador++;
System.out.println(); // Retorno de carro "gratuito"
System.out.println("Tecleados "+contador+" caracteres.");
}
}

Entrada/Salida por fichero

En Java es posible utilizar dos tipos de ficheros (de texto o binarios) y dos tipos de
acceso a los ficheros (secuencial o aleatorio).

Los ficheros de texto están compuestos de caracteres legibles, mientras que los
binarios pueden almacenar cualquier tipo de datos (int, float, boolean,...).

Una lectura secuencial implica tener que acceder a un elemento antes de acceder al
siguiente, es decir, de una manera lineal (sin saltos). Sin embargo los ficheros de
acceso aleatorio permiten acceder a sus datos de una forma aleatoria, esto es
indicando una determinada posición desde la que leer/escribir.

Clases a estudiar
En el paquete java.io existen varias clases de las cuales podemos crear instancias
de clases para tratar todo tipo de ficheros.
En este tutorial sólo vamos a trata las tres principales:
• FileOutputStream: Fichero de salida de texto. Representa ficheros de texto
para escritura a los que se accede de forma secuencial.
• FileInputStream: Fichero de entrada de texto. Representa ficheros de texto
de sólo lectura a los que se accede de forma secuencial.
• RandomAccessFile: Fichero de entrada o salida binario con acceso
aleatorio. Es la base para crear los objetos de tipo fichero de acceso aleatorio. Estos
ficheros permiten multitud de operaciones; saltar hacia delante y hacia atrás para
leer la información que necesitemos en cada momento, e incluso leer o escribir
partes del fichero sin necesidad de cerrarlo y volverlo a abrir en un modo distinto.

Generalidades
Para tratar con un fichero siempre hay que actuar de la misma manera:
1. Se abre el fichero. Para ello hay que crear un objeto de la clase correspondiente
al tipo de fichero que vamos a manejar, y el tipo de acceso que vamos a utilizar:
TipoDeFichero obj = new TipoDeFichero( ruta );
Donde ruta es la ruta de disco en que se encuentra el fichero o un descriptor de
fichero válido. Este formato es válido, excepto para los objetos de la
clase RandomAccessFile (acceso aleatorio), para los que se ha de instanciar de la
siguiente forma:
RandomAccessFile obj = new RandomAccessFile( ruta, modo );
Donde modo es una cadena de texto que indica el modo en que se desea abrir el
fichero; "r" para sólo lectura o "rw" para lectura y escritura.

2. Se utiliza el fichero. Para ello cada clase presenta diferentes métodos de acceso
para escribir o leer en el fichero.

3. Gestión de excepciones (opcional, pero recomendada) Se puede observar que


todos los métodos que utilicen clases de este paquete deben tener en su definición
una cláusula throws IOException. Los métodos de estas clases pueden lanzar
excepciones de esta clase (o sus hijas) en el transcurso de su ejecución, y dichas
excepciones deben de ser capturadas y debidamente gestionadas para evitar
problemas.

4. Se cierra el fichero y se destruye el objeto. Para cerrar un fichero lo que hay que
hacer es destruir el objeto. Esto se puede realizar de dos formas, dejando que sea
el recolector de basura de Java el que lo destruya cuando no lo necesite (no se
recomienda) o destruyendo el objeto explícitamente mediante el uso del
procedimiento close() del objeto:
obj.close()

La clase FileOutputStream
Mediante los objetos de esta clase escribimos en ficheros de texto de forma
secuencial.
Presenta el método write() para la escritura en el fichero. Presenta varios formatos:
• int write( int c ): Escribe el carácter en el fichero.
• int write( byte a[] ): Escribe el contenido del vector en el fichero.
• int write( byte a[], int off, int len ): Escribe len caracteres del vector a en el
fichero, comenzando desde la posición off.
El siguiente ejemplo crea el fichero de texto "/carta.txt" a partir de un texto que se
le introduce por teclado:

import java.io.*;
class CreaCarta {
public static void main(String args[]) throws IOException{
int c;
FileOutputStream f=new FileOutputStream("/carta.txt");
while( ( c=System.in.read() ) != -1 )
f.write( (char)c );
f.close();
}
}
La clase FileInputStream
Mediante los objetos de esta clase leemos de ficheros de texto de forma secuencial.
Presenta el método read() para la lectura del fichero. Este método se puede invocar
de varias formas.
• int read(): Devuelve el siguiente carácter del fichero.
• int read( byte a[] ): Llena el vector a con los caracteres leídos del fichero.
Devuelve la longitud del vector que se ha llenado si se realizó con éxito o –1 si no
había suficientes caracteres en el fichero para llenar el vector.
• int read( byte a[], int off, int len ): Lee len caracteres del fichero,
insertándolos en el vector a.
Todos ellos devuelven -1 si se ha llegado al final del fichero (momento de cerrarle).
El siguiente ejemplo muestra el fichero de texto "/carta.txt" en pantalla:

import java.io.*;
class MuestraCarta {
public static void main(String args[]) throws IOException {
int c;
FileInputStream f=new FileInputStream("/carta.txt");
while( ( c=f.read() ) != -1 )
System.out.print( (char)c );
f.close();
}
}

La clase RandomAccessFile
Mediante los objetos de esta clase utilizamos ficheros binarios mediante un acceso aleatorio,
tanto para lectura como para escritura. En estos ficheros hay un índice que nos dice en qué
posición del fichero nos encontramos, y con el que se puede trabajar para posicionarse en el
fichero.

Métodos de desplazamiento
Cuenta con una serie de funciones para realizar el desplazamiento del puntero del fichero.
Hay que tener en cuenta que cualquier lectura o escritura de datos se realizará a partir de la
posición actual del puntero del fichero.
• long getFilePointer();Devuelve la posición actual del puntero del fichero.
• void seek( long l ); Coloca el puntero del fichero en la posición indicada por l. Un
fichero siempre empieza en la posición 0.
• int skipBytes( int n ); Intenta saltar n bytes desde la posición actual.
• long length(); Devuelve la longitud del fichero.
• void setLength( long l); Establece a l el tamaño de este fichero.
• FileDescriptor getFD(); Devuelve el descriptor de este fichero.

Métodos de escritura
La escritura del fichero se realiza con una función que depende el tipo de datos que se desee
escribir.
• void write( byte b[], int ini, int len ); Escribe len caracteres del vector b.
• void write( int i ); Escribe la parte baja de i (un byte) en el flujo.
• void writeBoolean( boolean b ); Escribe el boolean b como un byte.
• void writeByte( int i ); Escribe i como un byte.
• void writeBytes( String s ); Escribe la cadena s tratada como bytes, no caracteres.
• void writeChar( int i ); Escribe i como 1 byte.
• void writeChars( String s ); Escribe la cadena s.
• void writeDouble( double d ); Convierte d a long y le escribe como 8 bytes.
• void writeFloat( float f ); Convierte f a entero y le escribe como 4 bytes.
• void writeInt( int i ); Escribe i como 4 bytes.
• void writeLong( long v ); Escribe v como 8 bytes.
• void writeShort( int i ); Escribe i como 2 bytes.
• void writeUTF( String s ); Escribe la cadena s utilizando la codificación UTF-8.
Los métodos que escriben números de más de un byte escriben el primero su parte alta.

Métodos de lectura
La lectura del fichero se realiza con una función que depende del tipo de datos que queremos
leer.
• boolean readBoolean(); Lee un byte y devuelve false si vale 0 o truesino.
• byte readByte(); Lee y devuelve un byte.
• char readChar(); Lee y devuelve un caracter.
• double readDouble(); Lee 8 bytes, y devuelve un double.
• float readFloat(); Lee 4 bytes, y devuelve un float.
• void readFully( byte b[] ); Lee bytes del fichero y los almacena en un vector b.
• void readFully( byte b[], int ini, int len ); Lee len bytes del fichero y los almacena en
un vector b.
• int readInt(); Lee 4 bytes, y devuelve un int.
• long readLong(); Lee 8 bytes, y devuelve un long.
• short readShort(); Lee 2 bytes, y devuelve un short.
• int readUnsignedByte(); Lee 1 byte, y devuelve un valor de 0 a 255.
• int readUnsignedShort(); Lee 2 bytes, y devuelve un valor de 0 a 65535.
• String readUTF(); Lee una cadena codificada con el formato UTF-8.
• int skipBytes(int n); Salta n bytes del fichero.
Si no es posible la lectura devuelven –1.

Ejemplo
Vamos a crear un pequeño programa que cree y acceda a un fichero binario, mediante acceso
aleatorio. El siguiente ejemplo crear un fichero binario que contiene los 100 primeros
números (en orden):

// Crea un fichero binario con los 100 primeros numeros


static void creaFichBin( String ruta ) throws IOException {
RandomAccessFile f=new RandomAccessFile(ruta,"rw"); // E/S
for ( int i=1; i <= 100 ; i++ )
{
try{
f.writeByte( i );
} catch( IOException e){

// Gestion de excepcion de ejemplo


break; // No se puede seguir escribiendo
}
f.close();
}
}

El siguiente método accede al elemento cual de un fichero binario, imprimiendo la longitud


del fichero, el elemento cual y su 10 veces siguiente elemento:

static void imprimeEltoN(String ruta, long cual)


throws IOException{
RandomAccessFile f=new RandomAccessFile(ruta,"r"); // E/
System.out.print( "El fichero " + ruta );
System.out.println( " ocupa " + f.length() + " bytes." );
f.seek( cual-1 ); // Me posiciono (-1 porque empieza en 0)
System.out.print(" En la posicion " + f.getFilePointer() );
System.out.println(" esta el numero " + f.readByte() );
f.skipBytes( 9 ); // Salto 9 => Elemento 10 mas alla
System.out.print(" 10 elementos más allá, esta el ");
System.out.println( f.readByte() );
f.close();
}

Si incluimos ambos métodos en una clase, y les llamamos con el siguiente programa principal
(main()):

public static void main(String args[]) throws IOException {


String ruta="numeros.dat"; // Fichero
creaFichBin( ruta ); // Se crea
imprimeEltoN( ruta, 14 ); // Accedo al elemento 14.
}

Obtendremos la siguiente salida:

El fichero numeros.dat ocupa 100 bytes.


En la posicion 13 esta el numero 14
10 elementos más allá, esta el 24

6.4 Manejo de objetos persistentes


Las clases presistentes son clases en una aplicación que implementan las entidades
del problema empresarial (por ejemplo, Customer y Order en una aplicación de
comercio electrónico). No se considera que todas las instancias de una clase
persistente estén en estado persistente. Por ejemplo, una instancia puede ser
transitoria o separada.
Hibernate funciona mejor si estas clases siguen algunas reglas simples, también
conocidas como el modelo de programación POJO (Plain Old Java Object). Sin
embargo, ninguna de estas reglas son requerimientos rígidos. De hecho, Hibernate3
asume muy poco acerca de la naturaleza de sus objetos persistentes. Puede
expresar un modelo de dominio en otras formas (por ejemplo, utilizando árboles de
instancias de Map).

Ejemplo simple de POJO


La mayoría de aplicaciones Java requieren una clase persistente que
represente a los felinos. Por ejemplo:

package eg;
import java.util.Set;
import java.util.Date;

public class Cat {


private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;

private Cat mother;


private Set kittens = new HashSet();

private void setId(Long id) {


this.id=id;
}
public Long getId() {
return id;
}

void setBirthdate(Date date) {


birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}

void setWeight(float weight) {


this.weight = weight;
}
public float getWeight() {
return weight;
}

public Color getColor() {


return color;
}
void setColor(Color color) {
this.color = color;
}

void setSex(char sex) {


this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}

void setMother(Cat mother) {


this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}

// addKitten not needed by Hibernate


public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}

Cuatro reglas principales de las clases


persistentes
• Implemente un constructor sin argumentos

Cat tiene un contructor sin argumentos. Todas las clases persistentes deben tener
un constructor predeterminado (el cual puede ser no-público) de modo que
Hibernate pueda instanciarlas usando Constructor.newInstance(). Le
recomendamos contar con un constructor por defecto con al menos una visibilidad
de paquete para la generación de proxies en tiempo de ejecución en Hibernate.

• Proporcione una propiedad identificador


Cat tiene una propiedad llamada id. Esta propiedad mapea a la columna de la llave
principal de la tabla de la base de datos. La propiedad podría llamarse de cualquier
manera y su tipo podría haber sido cualquier tipo primitivo, cualquier tipo de
"wrapper" primitivo, java.lang.String o java.util.Date. Si su tabla de base de datos
heredada tiene claves compuestas, puede utilizar una clase definida por el usuario
con propiedades de estos tipos
La propiedad identificadora es estrictamente opcional. Puede olvidarla y dejar que
Hibernate siga internamente la pista de los identificadores del objeto. Sin embargo,
no recomendamos que esto suceda.
De hecho, algunas funcionalidades se encuentran disponibles sólamente para clases
que declaran una propiedad identificadora:
• Session.saveOrUpdate()
• Session.merge()
Le recomendamos que declare propiedades identificadoras nombradas-
consistentemente en clases persistentes. y que utilice un tipo nulable (por ejemplo,
no primitivo).

• Prefiera las clases no finales

Un aspecto central de Hibernate, los proxies, dependen de que las clases


persistentes sean no finales o de la implementación de una interfaz que declare
todos los métodos públicos.
Con Hibernate puede persistir las clases finales que no implementen una interfaz.
Sin embargo, no podrá utilizar proxies para recuperación perezosa de asociaciones,
lo cual limitará sus opciones para afinar el rendimiento.
También debe evitar el declarar métodos public final en las clases no-finales. Si
quiere utilizar una clase con un método public final, debe deshabilitar explícitamente
el uso de proxies estableciendo lazy="false".

• Declare métodos de acceso y de modificación para los campos persistentes

Cat declara métodos de acceso para todos sus campos persistentes. Muchas otras
herramientas ORM persisten directamente variables de instancia. Es mejor
proporcionar una indirección entre el esquema relacional y las estructuras de datos
internos de la clase. Por defecto, Hibernate persiste las propiedades del estilo
JavaBeans, y reconoce los nombres de método de la forma getFoo, isFoo y setFoo.
De ser necesario, puede cambiarse al acceso directo a campos para propiedades
específicas.
No es necesario declarar públicas las propiedades. Hibernate puede persistir una
propiedad con un par get / set protected o private.

Implementación de herencia
Una subclase también tiene que cumplir con la primera y la segunda regla. Hereda su propiedad
identificadora de la superclase Cat. Por ejemplo:
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}
Implementando equals() y hashCode()
Tiene que sobrescribir los métodos equals() y hashCode() si:
• piensa poner instancias de clases persistentes en un Set (la forma
recomendada de representar asociaciones multivaluadas); y
• piensa utilizar reasociación de instancias separadas.
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos)
y de identidad Java sólamente dentro del ámbito de una sesión en particular. De
modo que en el momento en que mezcla instancias recuperadas en sesiones
diferentes, tiene que implementar equals() y hashCode() si desea tener una
semántica significativa para Sets.
La forma más obvia es implementar equals()/hashCode() comparando el valor
identificador de ambos objetos. Si el valor es el mismo, ambos deben ser la misma
fila de la base de datos ya que son iguales. Si ambos son agregados a un Set, sólo
tendremos un elemento en el Set). Desafortunadamente, no puede utilizar este
enfoque con identificadores generados. Hibernate sólo asignará valores
identificadores a objetos que son persistentes; una instancia recién creada no tendrá
ningún valor identificador. Además, si una instancia no se encuentra guardada y
está actualmente en un Set, al guardarla se asignará un valor identificador al objeto.
Si equals() y hashCode() están basados en el valor identificador, el código hash
podría cambiar, rompiendo el contrato del Set. Consulte el sitio web de Hibernate y
allí encontrará una discusión completa sobre este problema. Este no es un problema
de Hibernate, sino de la semántica normal de Java de identidad de objeto e
igualdad.
Le recomendamos implementar equals() y hashCode() utilizando igualdad de clave
empresarial (Business key equality). Igualdad de clave empresarial significa que el
método equals() sólamente compara las propiedades que forman la clave
empresarial. Esta es una clave que podría identificar nuestra instancia en el mundo
real (una clave candidata natural):

public class Cat {


public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}
Modelos dinámicos
Las entidades persistentes no necesariamente tienen que estar representadas como
clases POJO o como objetos JavaBean en tiempo de ejecución. Hibernate también
soporta modelos dinámicos (utilizando Mapeos de Mapeos en tiempo de ejecución)
y la representación de entidades como árboles de DOM4J. No escriba clases
persistentes con este enfoque, sólamente archivos de mapeo.
Los siguientes ejemplos demuestran la representación utilizando Mapeos. Primero,
en el archivo de mapeo tiene que declararse un entity-name en lugar de, o además
de un nombre de clase:
<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>

<property name="name"
column="NAME"
type="string"/>

<property name="address"
column="ADDRESS"
type="string"/>

<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>

<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>

</class>

</hibernate-mapping
>
Aunque las asociaciones se declaran utilizando nombres de clase destino, el tipo
destino de una asociación puede ser además una entidad dinámica en lugar de un
POJO.
Después de establecer el modo de entidad predeterminado como dynamic-map para
la SessionFactory, puede trabajar en tiempo de ejecución con Mapeos de Mapeos:

Session s = openSession();
Transaction tx = s.beginTransaction();
// Crea un cliente
Map david = new HashMap();
david.put("name", "David");

// Crea una organizacion


Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");

// Liga ambos
david.put("organization", foobar);

// Guarda ambos
s.save("Customer", david);
s.save("Organization", foobar);

tx.commit();
s.close();
Una de las ventajas principales de un mapeo dinámico es el rápido tiempo de entrega
del prototipado sin la necesidad de implementar clases de entidad. Sin embargo,
pierde el chequeo de tipos en tiempo de compilación y muy probablemente tendrá
que tratar con muchas excepciones en tiempo de ejecución. Gracias al mapeo de
Hibernate, el esquema de base de datos se puede normalizar y volver sólido,
permitiendo añadir una implementación apropiada del modelo de dominio más
adelante.
Los modos de representación de entidad se pueden establecer por Session:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);

// Crea un cliente
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continua en pojoSession
Tenga en cuenta que la llamada a getSession() utilizando un EntityMode está en la
API de Session, no en la de SessionFactory. De esta forma, la nueva Session
comparte la conexión JDBC, la transacción y otra información de contexto. Esto
significa que no tiene que llamar a flush() ni a close() en la Session secundaria, y
también tiene que dejar el manejo de la transacción y de la conexión a la unidad de
trabajo primaria.
Tuplizers
org.hibernate.tuple.Tuplizer y sus subinterfaces son las responsables de administrar
una representación en particular de un dato, dadas las representaciones de
org.hibernate.EntityMode. Si un dato dado se considera como una estructura de
datos entonces un tuplizer es la cosa que sabe como crear tal estructura de datos y
sabe como extraer e insertar valores en dicha estructura de datos. Por ejemplo, para
el modo de entidad POJO, el tuplizer correspondiente sabe como crear el POJO por
medio de su constructor. También sabe como acceder a las propiedades POJO
utilizando los accesores de propiedad definidos.

Hay dos tipos altos de niveles de Tuplizers, representados por las interfaces
org.hibernate.tuple.entity.EntityTuplizer y
org.hibernate.tuple.component.ComponentTuplizer. Los EntityTuplizers son los
responsables de administrar los contratos mencionados anteriormente en relación
con las entidades mientras que los ComponentTuplizers hacen lo mismo para los
componentes.
Los usuarios también pueden enchufar sus propios tuplizers. Tal vez necesite que
una implementación java.util.Map diferente de java.util.HashMap se utilice en el
modo de entidad de mapeo dinámico. O quizás necesite definir una estrategia de
generación proxy diferente de la que se utiliza por defecto. Se pueden obtener
ambas al definir una implementación tuplizer personalizada. Las definiciones de los
tuplizers se encuentran sujetas a la entidad o componente de mapeo que se supone
que tienen que administrar. Regresando al ejemplo de nuestra entidad de cliente:

<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>

<id name="id" type="long" column="ID">


<generator class="sequence"/>
</id>

<!-- other properties -->


...
</class>
</hibernate-mapping>

public class CustomMapTuplizerImpl extends org.hibernate.tuple.entity.DynamicMapEntit


yTuplizer{
// reemplaza el método buildInstantiator () para conectar a nuestro mapa personalizado…
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// reemplaza el método generateMap() para regresar nuestro mapa personalizado...
protected final Map generateMap() {
return new CustomMap();
}
}
}
EntityNameResolvers
La interfaz org.hibernate.EntityNameResolver es un contrato para resolver el
nombre de la entidad de una instancia de entidad dada. La interfaz define un solo
método resolveEntityName, el cual se le pasa la instancia entidad y se espera que
retorne el nombre de entidad apropriado (se permite nulo e indicaría que el
resolvedor no sabe cómo resolver el nombre de la entidad de la instancia de entidad
dada). Generalmente hablando, un org.hibernate.EntityNameResolver será más útil
en el caso de modelos dinámicos. Un ejemplo puede ser el usar interfaces con proxis
como su modelo de dominio. La suite de prueba de hibernate tiene un ejemplo de
este estilo exacto de uso bajo el org.hibernate.test.dynamicentity.tuplizer2. Aquí
está algo del código de ese paquete para su ilustración.
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();

public DataProxyHandler(String entityName, Serializable id) {


this.entityName = entityName;
data.put( "Id", id );
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}

public String getEntityName() {


return entityName;
}

public HashMap getData() {


return data;
}
}

public class ProxyHelper {


public static String extractEntityName(Object object) {
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}

// Otros metodos utiles....

public class MyEntityNameResolver implements EntityNameResolver {


public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();

public String resolveEntityName(Object entity) {


return ProxyHelper.extractEntityName( entity );
}

public boolean equals(Object obj) {


return getClass().equals( obj.getClass() );
}

public int hashCode() {


return getClass().hashCode();
}
}

public class MyEntityTuplizer extends PojoEntityTuplizer {


public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mapped
Entity) {
super( entityMetamodel, mappedEntity );
}

public EntityNameResolver[] getEntityNameResolvers() {


return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}

public String determineConcreteSubclassEntityName(Object entityInstance, SessionFact


oryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}

...
}
I/O y NIO - Navegación de archivos y I/O
I/O sabemos que se trata de Input / Output, y consiste - generalmente - en el
manejo de archivos, aunque también puede manejar la entrada y salida de bytes
desde cualquier entrada/salida como fuera un puerto serial, cadena de caracteres,
impresora, etc.

NIO es el "Nuevo I/O" que contiene paquetes nuevos desde Java 1.4. Actualmente
hay un NIO.2, o sea, paquetes más nuevos aún, que aparece en Java 7. Para
generalizar, cuando se mencione NIO, se estará tratando de NIO2.

En este post veremos algunos ejemplos sobre estos paquetes que son muy útiles,
y también necesarios para el examen de certificación.

Creación de un archivo
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Ejemplo01 {

private static final Logger LOG =


Logger.getLogger(Ejemplo01.class.getName());

public static void main(String[] args) {


try {
//Se crea una referenia a un archivo, pero no existe aún
File file = new File("archivo01.txt");
//A ver, ¿existe?
System.out.println("Existe:" + file.exists());//la primera
vez, no
//tratamos de crearlo...
boolean newFile = file.createNewFile(); //devuelve TRUE si
logró crearlo porque no existía
//a ver..
System.out.println("Se creo el archivo:"+newFile);
//y existe???
System.out.println("Existe 2:" + file.exists());//la primera
vez, no
} catch (IOException ex) {
LOG.log(Level.SEVERE, null, ex);
}
}

}
Escribir y Leer contenido de un archivo
Ahora, hagamos un programa que pueda escribir un contenido en un archivo
creado.
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ReaderWritter {

private static final Logger LOG =


Logger.getLogger(ReaderWritter.class.getName());

public static void main(String[] args) {


//Nuevo archivo de pruebas
File file = new File("archivo02.txt");
//Creamos un objeto para escribir caracteres en el archivo de
prueba
try (FileWriter fw = new FileWriter(file)) {
fw.write("Hola todos\nsaludos desde el espacio\n"); //Este es
el contenido
} catch (IOException ex) {
LOG.log(Level.SEVERE, null, ex);
}

//Ahora leeremos el contenido


try (FileReader fr = new FileReader(file)) {
//tenemos nuestro espacio para leer el contenido
char[] buffer = new char[100];
//... y al leerlo, guardamos el tamanio leiodo
int tamanio = fr.read(buffer);
System.out.println("Tamaño del contenido:" + tamanio);
//Escribimos el contenido
System.out.println("Contenido:");
for (char c : buffer) {
System.out.print(c); //caracter por caracter
}
} catch (IOException ex) {
LOG.log(Level.SEVERE, null, ex);
}
}

En la siguiente tabla veremos un pequeño api de las clases y métodos más


importantes para java.io
Si revisamos un poco la tabla, podemos notar que si queremos leer de archivo con
texto plano (usando el método readLine) solo está en la clase BufferedReader....
pero este no tiene un constructor con el nombre del archivo como argumento.
Entonces ¿cómo leo el archivo?. Aquí es donde debemos juntar con otras clases.

private static final Logger LOG =


Logger.getLogger(BufferedReaderTest.class.getName());

public static void main(String[] args) {


String nombreArchivo = "archivo03.txt";

//vamos a crear un archivo para escribir en él


try (PrintWriter pw = new PrintWriter(nombreArchivo)) {
pw.println("Este es un texto de ejemplo");
} catch (FileNotFoundException ex) {
LOG.log(Level.SEVERE, null, ex);
}

//ahora, vamos a leerlo


try (FileReader fr = new FileReader(nombreArchivo)) {
BufferedReader br = new BufferedReader(fr);

String linea;
//leer hasta que sea null, que es el fin de archivo
while ((linea = br.readLine()) != null) {
LOG.info(linea);//imprimir el contenido en pantalla
}
} catch (IOException ex) {
LOG.log(Level.SEVERE, null, ex);
}
}

En la línea 35 podemos ver que se creará un objeto de FileReader utilizando el


nombre del archivo dado en un String, y en la línea 36 se utiliza este objeto para
la creación de un objeto de tipo BufferedReader. A partir de aquí ya se puede leer
el contenido de manera natural.

Manejando archivos y carpetas (directorios)


La clase java.io.File permite manejar tanto archivos como carpetas.
Adicionalmente, sus métodos pueden ser usados para borrar archivos,
renombrarlos, saber si existe el archivo, crear archivos temporales, cambiar
atributos del archivo y determinar si es un archivo o una carpeta. La confusión
sucede cuando un objeto de tipo java.io.File es usado para representar o un
archivo o una carpeta. Por ejemplo, si creamos un objeto así:

1 File file = new File("algo");

... pueden suceder dos cosas:

1. Si "algo" no existe, ningún archivo es creado


2. Si "algo" existe, el objeto referenciado file apunta a un archivo
existente.
Recordemos que la instrucción file = new File("algo"); NUNCA crea un
archivo.

Existen dos maneras de crear un archivo.

1. Invocando al método createNewFile() del objeto java.io.File.

?
1 File file = new File("algo"); //Archivo aún no creado
2 file.createNewFile(); //Crea un archivo llamado "algo" apuntado por 'file'

2. Creando una instancia de Writer o Stream. Específicamente, instanciar


un FileWriter, PrintWriter o FileOutputStream. De cualquier manera, se creará
el archivo ni bien es instanciado. Por ejemplo:

File file = new File("algo"); //Archivo aún no creado


PrintWriter pw = new PrintWriter(file); //Se instancia 'pw' de tipo
'PrintWriter'
//y apunta al archivo 'algo'
asignado por 'file'.
Crear una carpeta (o directorio) es similar al de un archivo.

File miDir = new File("mi_directorio"); // crea un objeto


miDir.mkdir(); // crea la carpeta con el nombre
// descrito en el objeto 'miDir'
//una vez creada la carpeta, se puede crear un archivo
File miArchivo = new File(miDir,"archivo04.txt"); //el primer argumento
es el
//directorio donde
estará el archivo
miArchivo.createNewFile();

Si no se crea la carpeta (línea 2 del código anterior) al momento de crear el archivo


(línea 7), lanzará una excepción

Además de crear archivos, la clase java.io.File hace otras cosas como renombrar
y borrar archivos. El siguiente código muestra un poco de estos métodos. Notar las
líneas resaltadas.

public class DeleteRenameFilesTest {

private static final Logger LOG =


Logger.getLogger(DeleteRenameFilesTest.class.getName());

public static void main(String[] args) {


try {
File dir0 = new File("dir0"); //haciendo un directorio
File dir1 = new File(dir0, "dir1");//haciendo un
subdirectorio
dir1.mkdirs(); //con esto se crean todas las carpetas
necesarias

File file1 = new File(dir1, "file1.txt"); //creando un


archivo
file1.createNewFile();

File file2 = new File(dir0, "file2.txt");//creando otro


archivo
file2.createNewFile();

//tratando de borrar el directorio 'dir0'...


LOG.log(Level.INFO, "Borrando el directorio {0}:{1}",
new Object[]{dir0, dir0.delete()});
//... pero no va a poder porque tiene contenido. Debe estar
vacío

File file3 = new File(dir0, "file3.txt");//otro objeto


file1.renameTo(file3); //.. lo renombramos (o movemos)

//Tratando de borrar el directorio 'dir1'...


LOG.log(Level.INFO, "Borrando el directorio {0}:{1}",
new Object[]{dir1, dir1.delete()});
//... y lo logra, porque el archivo ya fue movido

File dir2 = new File("nuevo_dir");//renombramos el


directorio...
LOG.log(Level.INFO, "Renombrando el directorio {0} a {1}:
{2}",
new Object[]{dir0, dir2, dir0.renameTo(dir2)}); //...
y mantendrá el contenido
//La primera vez lo hará, pero la segunda no, porque ya
existe

} catch (IOException ex) {


LOG.log(Level.SEVERE, null, ex);
}
}

La clase java.io.Console
Esta clase apareción en Java 6. En el contexto común, la consola es un dispositivo
físico con un teclado y una pantalla (como una PC o una MAC). Si estamos
ejecutando Java SE 6 desde la línea de comandos, ya estamos accediendo al
objeto de la consola, el cual está referenciando invocando al
método System.console(). Recordemos que es posible ejecutar nuestros
programas Java en un entorno que no tenga acceso al objeto de la consola, y
cuando se invoca al método System.console(), este retornará null. ¿Como en qué
situación? Por ejemplo, en la ventana "Output" del IDE: esa ventana no tiene
entrada de teclado.

La clase Console hace que sea más fácil la entrada de datos desde la línea de
comando, tanto para entradas con eco o sin eco (como los password), y también
hace que sea más fácil escribir textos formateados a la línea de comandos. Esto es
bastante útil cuando se quiere trabajar con pruebas que no necesite interfaz
gráfica (GUI). Veamos un ejemplo bastante explicado.

public class ClaseConsole {

public static void main(String[] args) {


String nombre = "";
Console c = System.console(); //obtiendo un Console
char[] pass;
pass = c.readPassword("%s", "Escriba contraseña:"); //devuelve un
char[]
for (char chr : pass) {
c.format("%c", chr); //salida con formato
}
c.format("\n");

//probamos la entrada de texto


while (true) {
nombre = c.readLine("%s", "Escribe algo:"); //devuelve un
objeto
c.format("\tsalida: %s\n", nombre);
}
}

Para salir, presionar Ctrl+C


La salida sería esta:

Referencias:
http://www.apuntesdejava.com/2016/01/io-y-nio.html
https://docs.oracle.com/javase/8/docs/api/?java/io/File.html
http://fjglez90.blogspot.mx/2012/06/63-operaciones-basicas-y-tipos-de.html
http://p-o-o-itsav.blogspot.mx/2012/02/62-clasificacion-de-archivos-de-texto-y.html

Das könnte Ihnen auch gefallen