Lectura y escritura en la memoria



Descripción general

Stream es la clase base de todas las secuencias. Una secuencia es una abstracción de una secuencia de bytes, como un archivo, un dispositivo de entrada/salida, un canal de comunicación interprocesos o un socket TCP/IP. La clase Stream y sus clases derivadas proporcionan una visión genérica de diferentes tipos de entrada y salida, aislando al programador de los detalles específicos del sistema operativo y sus dispositivos subyacentes.

[TOC] Tabla de Contenidos


↑↑↑

Acceso al contenido de los archivos

Para acceder al contenido de un archivo trabajaremos con objetos de alguna clase derivada de Stream. Ésta, definida en System.IO, es una clase abstracta, es decir, no pueden crearse objetos a partir de ella, usada como base de las clases FileStream, MemoryStream, BufferedStream, DeflateStream, AuthenticatedStream, CryptoStream y NetworkStream, cada una de ellas especializada en almacenar o recuperar el flujo de información de un cierto medio, como puede ser el disco, la memoria o una conexión de red.

Que todas estas clases tengan una ascendente común facilita nuestra labor, ya que conociendo los miembros de Stream sabremos cómo trabajar con cualquiera de sus derivadas.


↑↑↑

Metodología general

Nunca crearemos un objeto de la clase Stream sino que nos serviremos de sus clases derivadas. Al crear un objeto de una de esas clases, como puede ser FileStream o NetworkStream, debemos facilitar al constructor un parámetro que le permita acceder al dispositivo o fuente de la que se obtendrá la información. En el caso de FileStream, por ejemplo, dicho parámetro será un camino completo con el nombre del archivo, mientras que con NetworkStream el parámetro necesario es un identificador de socket TCP/IP.

Las clases NetworkStream, AuthenticatedStream y CryptoStream, aunque derivadas deStream, no se encuentran en el ámbito System.IO sino en ámbitos específicos relacionados con el trabajo en red y la seguridad.

La mayoría de los objetos de secuencia almacenan los datos en búferes de una forma transparente. Por ejemplo, los datos no se escriben de forma inmediata en el disco cuando escriba en una secuencia de archivos ; por el contrario, los bytes se almacenan en búferes y se volcarán cuando se cierre la secuencia o cuando ejecute explícitamente un método Flush. No hay que decir que el almacenamiento en búfer mejora el rendimiento de manera notable. Las secuencias de archivo se almacenan en búferes, mientras que las secuencias de memoria no lo son porque no existe un motivo para almacenar en un búfer una secuencia asignada a la memoria. Podrá utilizar el objeto BufferedStream para agregar capacidad de almacenamiento en búfer a un objeto Stream que no cuente con ella de forma nativa

Una vez tenemos el objeto de la clase Stream creado, y, antes de realizar ninguna operación de lectura, escritura o desplazamiento podemos comprobar si esas operaciones son posibles en el flujo que hemos obtenido. Con este fin consultaremos las propiedades CanRead, CanWrite y CanSeek que, como puede suponer, son de tipo Boolean. Las tres son también sólo de lectura.

Para leer y escribir datos en el flujo tenemos dos parejas de métodos: ReadByte() y Read() ,para lectura, y WriteByte() y Write() para escritura. Los primeros leen o escriben un solo byte, mientras que los segundos leen o escriben una secuencia de bytes. WriteByte() necesita como único parámetro el byte a escribir, mientras que ReadByte() no toma parámetros y devuelve el dato leído como un Integer.

Public Function ReadByte As Integer 
Public Sub WriteByte (value As Byte)
Public Function Read (<OutAttribute> buffer As Byte(), 
                      offset As Integer, 
                      count As Integer ) As Integer
Public Sub Write (buffer As Byte(),offset As Integer, count As Integer)

Los métodos Read() y Write() toman ambos tres parámetros: una matriz de tipo Byte en el que va a almacenarse o donde se encuentra la secuencia de bytes, un entero indicando el elemento de dicha matriz a partir del que se almacenará y, en tercer lugar, otro entero especificando el número de bytes a escribir o leer. En los dos casos la posición, entregada como segundo parámetro, hace referencia al punto de la matriz donde van a alojarse los bytes leídos o se encuentran los bytes a escribir, no a la posición relativa en el flujo. Cuando se abre un flujo de datos se crea un puntero o marcador que, generalmente, se sitúa al inicio de ese flujo. Cada vez que se efectúa una operación de lectura o escritura el marcador se desplaza hacia delante, indicando la posición donde se efectuará la próxima operación.

En caso de que la propiedad CanSeek tenga el valor True podemos mover ese marcador libremente, determinando nosotros mismos cuál será el punto del flujo en el que se escriba o del que se lea.

La propiedad Length contiene el número de bytes de información existentes en el flujo. Conociendo este dato, podemos usar la propiedad Position tanto para saber cuál es la posición actual como para modificarla. Una asignación a esta última propiedad puede causar una excepción NotSupportedException en caso de que el flujo no permita cambiar la posición, por ello es importante comprobar antes el valor de CanSeek.

Además de los métodos ya citados para lectura y escritura, en la clase Stream también existen otros que facilitan el lanzamiento de operaciones asíncronas de lectura y escritura. Éstas no bloquean la ejecución del proceso principal sino que invocan a un método de retorno, ajustado al delegado AsyncCallback, cuando la lectura o escritura ha terminado.

Finalizado el trabajo con el flujo de datos, una llamada al método Close () lo cerrará liberando los recursos que pudieran haberse asignado. Este cierre, no obstante, no destruye el objeto Stream que estemos usando.

Las secuencias específicas pueden implementar métodos y propiedades adicionales, tales como las siguientes:


↑↑↑

Lectores y escritores de secuencias

Como el objeto Stream genérico sólo puede leer y escribir bytes individuales o grupos de bytes, la mayor parte de las veces deberá utilizar objetos auxiliares lectores de secuencia y escritores de secuencia que le permitirán trabajar con datos de forma más estructura, por ejemplo, una línea de texto o un valor Double. .NET Framework dispone de vanos lectores y escritores de secuencia:


↑↑↑

Ejemplo de uso de los Stream

Normalmente utilizará el objeto StreamReader para leer desde un archivo de texto. Podrá obtener una referencia a este tipo de objeto de diferentes formas:

'Imports System.IO
'El método compartido File.OpenText. 
Dim sr As StreamReader = File.OpenText("c:\autoexec.bat")

'Método instancia OpenText de un objeto Filelnfo.

Dim fi2 As New FileInfo("c:\autoexec.bat")
Dim sr2 As StreamReader = fi2.OpenText

'Pasando un FileStream desde el método Open de la
    clase File a 
'el método constructor de StreamReader. 
'(Esta técnica le permitirá especificar el modo,
    acceso y modo compartido.) 
Dim st3 As Stream = File.Open("C:\autoexec.bat", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
Dim sr3 As New StreamReader(st3)

'Abriendo un FileStream en el archivo y, a continuación,
    pasándolo al método constructor StreamReader. 
Dim fs4 As New FileStream("C\autoexec.bat", FileMode.Open)
Dim sr4 As New StreamReader(fs4)

'Obteniendo un FileStream desde el método OpenRead
    de la clase File 
'y pasándolo al constructor StreamReader. 
Dim sr5 As New StreamReader(File.OpenRead("c:
        \autoexec .bat"))

'Pasando el nombre del archivo al constructor StreamReader.

Dim sr6 As New StreamReader("c:\autoexec.bat")

'Pasando el nombre del archivo y codificándolo. 
Dim sr7 As New StreamReader("c:\autoexec.bat", System.Text.Encoding.Unicode)
Dim sr8 As New StreamReader("c: \autoexec
        .bat", System.Text.Encoding.ASCII)

'Como antes, pero permitiremos que el sistema sea el que
    decide la mejor codificación. 
Dim sr9 As New StreamReader("c:\autoexec.bat", True)

Después de que obtenga una referencia a un objeto StreamReader, podrá utilizar uno de sus numerosos métodos para leer uno o más caracteres e, incluso, líneas de texto completas. El método Peek devuelve el código del siguiente carácter contenido en la cadena sin llegar a extraerlo realmente, o devuelve el valor especial -1 si no existen más caracteres. En la práctica, este método se utiliza para probar las condiciones fin de archivo:

Por ejemplo, mostrar todas las líneas de texto contenidas en el archivo

'Muestra todas las líneas de texto contenidas
            en el archivo. 
Do Until sr.Peek = -1
    'El método ReadLine lee líneas completas. 
    Console.WriteLine(sr.ReadLine)
Loop
'Cierre siempre un StreamReader cuando haya terminado con
    él. 
sr.Close()

Advertencia Siempre deberá cerrar el objeto Stream después de utilizarlo. En caso contrario, la secuencia mantendrá abierta el archivo hasta que la siguiente recolección de elementos no utilizados llame al método Finalize del Stream. Al menos, existen dos motivos por los que deberá cerrar manualmente la secuencia. La primera es que si el archivo permanece abierto durante más tiempo del que sea estrictamente necesario no podrá eliminarlo ni moverlo, ni podrá permitir que otra aplicación lo abra para leer o escribir su contenido (dependiendo del modo de acceso que haya especificado cuando abrió el archivo). El segundo motivo es el rendimiento: el código contenido en el método Close de Stream llama al método GC.SuppressFinalize, por lo que la secuencia no se habrá finalizado y, por tanto, los recursos que utiliza se liberan antes.

También podrá leer carácter a carácter utilizando el método Read o podrá leer todos los caracteres restantes utilizando el método ReadToEnd:

'Lee el contenido completo de C:\Autoexec.bat de una
            vez. 
sr = New StreamReader("c:\autoexec.bat")
Dim contenidoArchivo As String = sr.ReadToEnd

Si abrió el StreamReader utilizando un objeto Stream, podrá utilizar el método Seek del objeto Stream para mover el puntero o, incluso, para leer únicamente su posición actual. Si no abrió el StreamReader mediante un objeto Stream, podrá acceder al objeto Stream más interno que el runtime de .NET ha creado, utilizando la propiedad BaseStream de StreamReader:

'Lee el contenido completo de C:\Autoexec.bat de una
            vez. 
sr = New StreamReader("c:\autoexec.bat")
Dim contenidoArchivo As String = sr.ReadToEnd

'Si el archivo contiene más de 100 caracteres, procesarlo
    de nuevo, carácter a 
'carácter (de acuerdo, resulta un poco tonto, pero
    se trata sólo de una demostración) 
If contenidoArchivo.Length >= 100 Then
    'Volver a definir el puntero de la secuencia al principio.
    
    sr.BaseStream.Seek(0, SeekOrigin.Begin)
    'Leer caracteres individuales hasta que se alcance el EOF
        )fin de archivo) 
    Do Until sr.Peek = -l
        ' El método Read devuelve un entero, convertirlo en
            un carácter. 
        Console.Write(sr.Read.ToString)
    Loop
End If
sr.Close()

Podrá utilizar un objeto StreamWriter para escribir en un archivo de texto. Como sucede con el objeto StreamReader, podrá crear un objeto StreamWriter de diferentes formas:

Dim swl As StreamWriter = File.CreateText("c:\temp.dat")

'Pasando un FileStream desde el método Open de la
    clase File al método 
'constructor de StreamWriter. 
Dim st2 As Stream = File.Open("C:\temp.dat", FileMode.Create, FileAccess.ReadWrite, FileShare.None)
Dim sw2 As New StreamWriter(st2)

'Abriendo un FileStream en el archivo y pasándolo
    al método 
'constructor de StreamWriter. 
Dim fs3 As New FileStream("C:\autoexec.bat", FileMode.Open)
Dim sw3 As New StreamWriter(fs3)

'Obteniendo un FileStream desde el método OpenWrite
    de la clase File 
'y pasándolo al constructor StreamWriter. 
Dim sw4 As New StreamWriter(File.OpenWrite("C:\temp.dat"))

'Pasando el nombre del archivo al constructor StreamWriter.

Dim sw5 As New StreamWriter("C:\temp.dat")

La clase StreamWriter expone los métodos Write y WriteLine: el método Write puede escribir la representación textual de cualquier tipo de datos básico (Integer, Double, etc.); el método WriteLine sólo funciona con cadenas y añade automáticamente un carácter de salto de línea. Asigne el valor False a la propiedad AutoFlush (valor predetenninado) si desea que StreamWriter adopte una forma limitada de almacenamiento en caché; probablemente, necesitará ejecutar periódicamente un método Flush en este caso. Configure esta propiedad como True para aquellas secuencias o dispositivos, tales como la ventana de consola, de las que el usuario espere obtener una rápida respuesta.

El código siguiente utiliza un objeto StreamReader para leer de un archivo y un objeto StreamWriter para copiar el texto en otro archivo, después de convertir todo el texto a mayúsculas:

Dim sr As New StreamReader("C:\Autoexec.bat")
Dim sw As New StreamWriter("C:\Autoexec.new")
Do Until sr.Peek = -1
    'El método Readtine devuelve una cadena, por lo que
        podemos pasar a mayúsculas su contenido sobre la marcha. 
    sw.WriteLine(sr.ReadLine.ToUpper)
Loop
sw.Flush()
sr.Close()
sw.Close() ' Esta instrucción escribe datos en el
    archivo y lo cierra.

↑↑↑

Clase MemoryStream - Lectura y escritura de secuencias en memoria

La clase MemoryStream permite leer y escribir secuencias en la memoria., dicho de otro modo, permite utilizar la memoria como almacenamiento temporal de datos. Datos que se escriben con clases derivadas de Stream como FileStresm o StreamWriter


↑↑↑

Ejemplo sencillo

' Crear una secuencia de memoria con una capacidad
            inicial de 1K. 
Dim MS As New System.IO.MemoryStream(1024)
' Una clase que escribe en el MemoryStream
Dim BW As New System.IO.BinaryWriter(MS)
' Escribir algo en el MemoryStream
BW.Write("Algo")
' Forzar a escribir todo en el MemoryStream
BW.Flush()
' Rebobinar MemoryStream hasta el principio,
MS.Seek(0, System.IO.SeekOrigin.Begin)
'------------------------------------------
' Una clase que lee en el MemoryStream
Dim BR As New System.IO.BinaryReader(MS)
'------------------------------------------
' leer hasta llegar al final y escribir 
Do Until BR.PeekChar = -1
    Debug.WriteLine(BR.ReadDouble)
Loop
'-------------------------------------
' Cerrar y destruir los objetos
' Observa que primero se destruye el writer/Reader
' y despues el Stream
BR.Close()
BR = Nothing
BW.Close()
BW = Nothing
MS.Close()
MS.Dispose()
MS = Nothing

↑↑↑

El mismo ejemplo un poco mejor escrito

Los lectores y escritores de secuencias no sirven exclusivamente para los archivos. Por ejemplo, podrá utilizarlos en unión del objeto MemoryStream para trabajar con la memoria como si fuera un archivo temporal (algo que, normalmente, proporciona un mejor rendimiento que utilizar un archivo real). El código siguiente escribe 10 números aleatorios en un MemoryStream y luego los lee:

'------------------------------------------------------
' Secuencia de memoria un MemoryStream
Dim MS As System.IO.MemoryStream = Nothing
' Una clase que escribe en el MemoryStream
Dim BW As System.IO.BinaryWriter = Nothing
' Una clase que lee en el MemoryStream
Dim BR As System.IO.BinaryReader = Nothing
' Un generador de numeros aleatorios
Dim objRandom As Random = Nothing
'------------------------------------------------------

Try
    ' Crear una secuencia de memoria con una capacidad inicial
        de 1K. 
    MS = New System.IO.MemoryStream(1024)
    ' Una clase que escribe en el MemoryStream
    BW = New System.IO.BinaryWriter(MS)

    ' Escribir 10 valores Double aleatorios en el MemoryStream
    Dim i As Integer
    objRandom = New Random
    For i = 1 To 10
        BW.Write(objRandom.NextDouble)
    Next
    ' Forzar a escribir todo en el MemoryStream
    BW.Flush()
    ' Rebobinar MemoryStream hasta el principio,
    MS.Seek(0, System.IO.SeekOrigin.Begin)
    '------------------------------------------
    ' Una clase que lee en el MemoryStream
    ' leer hasta llegar al final y escribir 
    '------------------------------------------
    'Bug 
    ' Hay que instanciarla en este mometo, si no da errores
    ' y no se pude leer el [MemoryStream]
    ' Dim BR As New System.IO.BinaryReader(MS)
    '------------------------------------------
    BR = New System.IO.BinaryReader(MS)
    Do Until BR.PeekChar = -1
        Debug.WriteLine(BR.ReadDouble)
    Loop
    '-------------------------------------
    ' Cerrar y destruir los objetos en Finally
Catch ex As Exception
    Throw

Finally
    ' Destruir los objetos
    If Not (BR Is Nothing) Then
        BR.Close()
        BR = Nothing
    End If
    If Not (BW Is Nothing) Then
        BW.Close()
        BW = Nothing
    End If
    If Not (MS Is Nothing) Then
        MS.Close()
        MS.Dispose()
        MS = Nothing
    End If
    If Not (objRandom Is Nothing) Then
        objRandom = Nothing
    End If
End Try

↑↑↑

StringReader y StringWriter Lectura y escritura de cadenas en memoria

Podemos utilizar la memoria como almacenamiento temporal para leer y escribir cadenas en la memoria. Por ejemplo, supongamos que cargamos el contenido de un control TextBox, en una variable de memoria (por ejemplo -cadenaMuyLarga-) a continuación, usando un objeto StringReader podemos leerla y recuperar las líneas individuales utilizando el método StringReader.ReadLine:

'La variable cadenaMuyLarga contiene el texto que
            se desee analizar. 
Dim strReader As New System.IO.StringReader(cadenaMuyLarga)
'Mostrar líneas individuales de texto. 
Do Until strReader.Peek = -l
    Console.WriteLine(strReader.ReadLine)
Loop

Naturalmente, podrá resolver este problema de otras formas distintas, aunque equivalentes. Por ejemplo, utilizando la función Split para obtener una matriz con todas las líneas de código

'-----------------------------------------------------
' proceso de separcion del texto en lineas
Dim lineas As String()
Dim separador(0) As String
separador(0) = Environment.NewLine
lineas = cadenaMuyLarga.Split(separador, System.StringSplitOptions.RemoveEmptyEntries)

' cada linea del dcoumento
For Each linea As String In lineas
    Console.WriteLine(linea)
Next

Sin embargo, la solución basada en el objeto StringReader consume menos recursos porque no duplica los datos en la memoria. En realidad, las clases StringReader y StringWriter ni siquiera crearán un objeto secuencia interno porque utilizan la propia cadena como secuencia (este hecho explica el porqué estas dos clases no exponen la propiedad BaseStream).

Se utiliza un objeto StringWriter para escribir los valores en una cadena. Sin embargo, no podrá asociarlo con un objeto String porque los objetos String son inmutables. En su lugar, tendrá que crear un StringBuilder y, a continuación, asociarlo con un objeto StringWriter:

'Crear una cadena con los nombres abreviados de los
            días de la semana separados por espacios. 
'Un StringBuilder de 7*4 caracteres resultará más
    que suficiente. 
Dim sb As New System.Text.StringBuilder(28)
'El StringWriter asociado con el StringBuilder 
Dim strWriter As New System.IO.StringWriter(sb)
'Asociar los nombres de los días a la cadena. 
For Each dia As String In _
         System.Globalization.DateTimeFormatInfo.CurrentInfo.AbbreviatedDayNames
    strWriter.Write(dia)
    strWriter.Write(" "c) 'Agregar
        un espacio. 
Next
Console.WriteLine(sb) ' > Dom Lun Mar Mié Jue Vie
    Sáb

Aunque en realidad con la clase StringWriter no necesitamos utilizar un objeto "StringBuilder" ya que lo instancia internamente para escribir el texto, de manera que el código queda mucho más sencillo y elegante

Using SW As New System.IO.StringWriter
    For Each dia As String In _
             System.Globalization.DateTimeFormatInfo.CurrentInfo.AbbreviatedDayNames
        strWriter.Write(dia)
        strWriter.Write(" "c) 'Agregar
            un espacio. 
    Next
    Console.WriteLine(SW.ToString) ' > Dom Lun Mar Mié
        Jue Vie Sáb 
End Using

↑↑↑


↑↑↑

A.2.Enlaces

[Para saber mas]

Referencias

Biblioteca de clases de .NET Framework

[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]

↑↑↑

A.3.Información del documento

Título
Lectura y escritura en la memoria
Autor
Palabras claves de busqueda
'acceder al contenido de un archivo', archivo, archivos, AuthenticatedStream, BinaryReader, BinaryWriter, buffer, BufferedStream, bytes, 'clase abstracta', clases, CryptoStream, DeflateStream, escribir, FileStream, leer, MemoryStream, NetworkStream, objetos, rendimiento, secuencia, Stream, StreamReader, StreamWriter, StringReader, StringWriter, texto, TextReader, TextWriter, XmlTextReader, XmlTextWriter
Contenido del documento
Stream es la clase base de todas las secuencias. Una secuencia es una abstracción de una secuencia de bytes, como un archivo, un dispositivo de entrada/salida, un canal de comunicación interprocesos o un socket TCP/IP. La clase Stream y sus clases derivadas proporcionan una visión genérica de diferentes tipos de entrada y salida, aislando al programador de los detalles específicos del sistema operativo y sus dispositivos subyacentes.
Tabla de contenidos
[ Acceso al contenido de los archivos ], [ Metodología general ], [ Lectores y escritores de secuencias ], [ Ejemplo de uso de los Stream ], [ Clase MemoryStream - Lectura y escritura de secuencias en memoria ], [ Ejemplo sencillo ], [ El mismo ejemplo un poco mejor escrito ], [ StringReader y StringWriter Lectura y escritura de cadenas en memoria ], [ Referencias ]
Archivado en:
informática\lenguajes\NET\System\IO
Fechas
Fecha Creación
2010-01-03T09:54:15 [domingo, 03 de enero de 2010 a las 9:54:15 horas]
Fecha Publicación
2010-01-03T09:54:15 [domingo, 03 de enero de 2010 a las 9:54:15 horas]
Fecha de la última actualización en disco
Fecha última Modificación
2010-01-03T09:54:15 [domingo, 03 de enero de 2010 a las 9:54:15 horas]
Naturaleza del recurso
Text
IMT (Internet Media Type)
text/xhtml+xml
Idioma
es-ES [es = Español] [ES = España]
Copyright
Texto con los derechos
© Copyright Joaquin 'jms32®' Medina Serrano 1.997-2010 - Reservados todos los derechos.
Información obtenida con JavaScript
Situación de ESTE documento en la red
¿ Quien ha llamado a ésta página ?
Navegador empleado para ver ésta página
© 1997 - - La Güeb de Joaquín
Joaquin Medina Serrano
Ésta página es española