I. Introduction
C# often performs read and write operations on data, and the basis for these operations is on streams, that is, C# reads data through streams. There are two commonly used streams in C#:
1. Output stream: When writing data to some external target, an input stream is used.
2. Input stream: used to read data into memory or variables that can be accessed by the program.
The System.IO namespace contains classes for reading and writing data in files. Here we only introduce the main classes for file input and output, as follows:
kind | illustrate |
File | Static utility class that provides many static methods for moving, copying and deleting files. |
Directory | Static utility class that provides many static methods for moving, copying and deleting directories. |
Path | Utility class for handling path names. |
FileInfo | Represents a physical file on disk, and this class contains methods for handling this file. To complete reading and writing files, a Stream object must be created. |
DirectoryInfo | Represents a physical directory on disk, and this class contains methods for handling this directory. |
FileSystemInfo | Used as a base class for FileInfo and DirectoryInfo, you can use polymorphism to handle both files and directories. |
FileStream | Represents a file that is writable or readable, or both. This file can be read and written synchronously or asynchronously. |
StreamReader | Reading character data from a stream can be created using FileStream as a base class. |
StreamWriter | To write character data to a stream, you can create it using FileStream as a base class. |
FileSystemWatcher | FileSystemWatcher is the most complex class introduced in this chapter. It is used to monitor files and directories, providing events that applications can capture when changes to these files and directories occur. |
DeflateStream - represents a stream that automatically compresses data when writing or automatically decompresses when reading, using the Deflate algorithm to achieve compression.
GZipStream - represents a stream that automatically compresses data when writing or automatically decompresses when reading, using the GZIP algorithm to achieve compression.
2. FiIe class and Directory class
The File and Directory utility classes provide a number of static methods for working with files and directories. These methods can move files, query and update properties, and create FileStream objects. The following are the most commonly used static class methods of the File class:
method | illustrate |
Copy() | Copy files from source location to destination location. |
Create() | Creates a file at the specified path. |
Delete() | Delete Files. |
Open() | Returns a FileStream object at the specified path. |
Move() | Moves the specified files to a new location. You can give the file a different name in the new location. |
The most commonly used static class methods of the Directory class:
method | illustrate |
CreateDirectory() | Creates a directory with the specified path. |
Delete() | Delete the specified directory and all files in it. |
GetDirectories() | Returns an array of string objects representing the directory names under the specified directory. |
EnumerateDirectories() | Similar to GetDirectories(), but returns an IEnumerable<string> collection of directory names. |
GetFiles() | Returns an array of string objects containing the file names in the specified directory. |
EnumerateFiles() | Similar to GetFiles(), but returns an IEnumerable<string> collection of file names. |
GetFileSystemEntries() | Returns an array of string objects containing the file and directory names in the specified directory. |
EnumerateFileSystemEntries() | Similar to GetFilesSystemEntries(), but returns an IEnumerable<string> collection of file and directory names. |
Move() | Moves the specified directory to a new location, giving the folder a new name in the new location. |
FileInfo class: It is not a static class and has no static methods. It can only be used after instantiation. The FileInfo object represents a file on the disk or network location. You can create a FileInfo object by providing the file path:
FileInfo aFile = new FileInfo (@"C:\Log.txt"); Note: @ means that the string is interpreted literally and not as an escape character
The FileInfo class provides many methods similar to the File class, but since File is a static method, it requires a string parameter to specify the file location for each method call. The following code does the same job:
FileInfo aFile = new FileInfo(“Data.txt”); if(File.Exists(“Data.txt”)) Console.WriteLine("File Exists");
if(aFile.Exists) Console,WriteLine("File Exists");
Usage scenarios of FileInfo and File:
1. If you only make a single method call, you can use the static File class. Because there is no need to instantiate, the call is faster.
2. If the application performs several operations on the file, it is better to instantiate the FileInfo object and use its methods.
The following introduces the properties of FileSystem:
Attributes | illustrate |
Attributes | Use the FileAttributes enumeration to get or set the attributes of the current file or directory. |
CreationTime, CreationTimeUtc |
Get the creation date and time of the current file, both UTC and non-UTC versions available. |
Extension | Extract the file extension. This property is read-only. |
Exists | Determines whether a file exists. This is a read-only abstract property overridden in FileInfo and DirectoryInfo. |
FullName | Retrieve the full path of the file. This property is read-only. |
LastAccessTime, LastAccessTimeUtc |
Gets or sets the date and time the current file was last accessed, UTC and non-UTC versions can be used |
LastWriteTime, LastWriteTimeUtc |
Gets or sets the date and time the current file was last written to, UTC and non-UTC versions can be used |
Name | Retrieves the full path to a file. This is a read-only abstract property, overridden in FileInfo and DirectoryInfo. |
Attributes | illustrate |
Directory | Retrieves a DirectoryInfo object representing the directory containing the current file. This property is read-only |
DirectoryName | Returns the path to the file directory. This property is read-only |
IsReadOnly | A shortcut for the read-only feature of the file. This attribute can also be accessed through Attributes |
Length | Get the size of the file in bytes, returning a long value. This property is read-only |
DirectoryInfo is introduced below:
The difference and usage between Directory and DirectoryInfo are similar to the difference and usage between File and FileInfo. The following are the dedicated properties of DirectoryInfo:
Attributes | illustrate |
Parent | Retrieves a DirectoryInfo object representing the directory containing the current directory. This property is read-only |
Root | Retrieve a DirectoryInfo object representing the root directory containing the current directory, such as the C:\ directory. This property is read-only |
Pathname: also becomes an absolute pathname. It is the known location from which the specified file or directory is displayed. In layman's terms, it is the complete path.
Relative path: Starting from the current working directory, this is the default for relative pathnames. For example, the application runs in the C:\Development\FileDemo directory and uses the relative path LogFile.txt. The file is: C:\Development\FileDemo\LogFile.txt. If you want to move the directory up, use the .. string, then the path ..\Log.txt represents the C:\Development\Log.txt file. If you move up two directories, it's ..\..\. If necessary, use Directory.GetCurrentDirectory() to find out the current location of the working directory, or use Directory.SetCurrentDirectory() to set a new path.
FileStream object:
A FileStream object represents a file stream pointing to a disk or network path. This class provides methods for reading and writing bytes in a file. However, StreamReader or StreamWrite are often used to perform these functions. This is because the FileStream class operates on bytes and byte arrays, while the Stream class operates on character data. Character data is easy to use, but some operations, such as random file access (accessing data at a certain point in the middle of the file), must be performed by a FileStream object. The simplest constructor to create a FileStream object has only two parameters, the file name and the FileMode enumeration value. as follows:
FileStream aFile = new FileStream(filename,FileMode.<Member>); The FileMode enumeration contains several members that specify how to open or create files. As shown in Table 1-7:
The file exists | file does not exist | |
Append | Open the file, the stream points to the end of the file, can only be used in conjunction with enumeration FileAccess.Write |
Create a new file. Can only be used with enumeration FileAccess.Write |
Create | Delete the file and create a new one | Create new file |
CreateNew | throw an exception | Create new file |
Open | Open the file and point the stream to the beginning of the file | throw an exception |
OpenOrCreate | Open the file and point the stream to the beginning of the file | Create new file |
Truncate | Open the file, clear its contents, stream to the beginning of the file , retain the original creation date of the file |
throw an exception |
Another commonly used constructor is as follows:
FileStream aFile = new FileStream (filename, FileMode.<Member>, FileAccess.<Member>); The third parameter is a member of the FileAcess enumeration, which specifies the role of the stream. As shown in Table 1-8:
illustrate | |
Read | Open the file for read-only |
Write | Open the file for writing only |
ReadWrite | Open a file for reading and writing |
Both the File and FileInfo classes provide OpenRead() and OpenWrite() methods to make it easier to create FileStream objects. The former opens the file for read-only access, and the latter only allows writing to the file. The Data.tex file is opened for read-only access as follows:
FileStream aFile = File.OpenRead("Data.txt");
The following code performs the same function: FileInfo aFileInfo = new aFileInfo("Data.txt"); FileStream aFile = aFileInfo.OpenRead();
1. File location
The FileStream class maintains an internal file pointer that points to the location in the file where the next read or write operation will occur. In most cases, when a file is opened, it points to the beginning of the file, but the position can be modified through the Seek() method. This method takes two parameters: the first specifies the distance in bytes to move the file pointer. The second one specifies the starting position to start calculation, represented by a value of the SeekOrigin enumeration. The SeekOrigin enumeration contains: Begin, Current and End. For example: aFile.Seek(8,SeekOrigin.Begin); moves the pointer 8 bytes from the beginning of the file.
2. Read data
The FileStream class can only process raw bytes (bytes), which makes the FileStream class can be used for any data files, not just text files, such as images and sounds, etc., but the FileStream object cannot directly read data into a string. , the StreamReader class does.
The FileStream.Read() method is the main means of accessing data from the file pointed to by the FileStream object. This method reads data from the file and then writes the data into a byte array. It has three parameters: the first one is passed The input byte array is used to accept data in the FileStream object; the second is the position in the byte array where data is started to be written, which is usually 0; the third specifies how many bytes to read from the file.
example:
static void Main(string[] args)
{
byte[] byteData = new byte[200];
char[] charaData = new char[200];
FileStream aFile = new FileStream("../../Program.cs",FileMode.OpenOrCreate);
aFile.Seek(144,SeekOrigin.Begin);
aFile.Read(byteData,0,200);
Decoder d = Encoding.UTF8.GetDecoder();
d.GetChars(byteData,0,byteData.Length,charaData,0);
Console.WriteLine(charaData);
Console.ReadLine();
}
3. Write data
The writing process is similar to the reading process. Examples below:
static void Main(string[] args)
{
byte[] byteData;
char[] charaData;
FileStream aFile = new FileStream("Program.txt",FileMode.Create);
charaData = "My pink half of the drainpipe.".ToCharArray();
byteData = new byte[charaData.Length];
Encoder d = Encoding.UTF8.GetEncoder();
d.GetBytes(charaData, 0, charaData.Length, byteData, 0,true);
aFile.Seek(0,SeekOrigin.Begin);
aFile.Write(byteData,0,byteData.Length);
}
StreamWriter object:
Operating byte arrays is troublesome, so StreamReader or StreamWriter is usually used to process files. If there is no need to change the file pointer position, these classes make it easy to operate files. The StreamWriter class allows characters and strings to be written to files. It handles the underlying conversion and writes data to a FileStream object. There are many ways to create a StreamWriter object. If you already have a FileStream object, you can use this object to create it:
FileStream aFile = new FileStream("Log.txt",FileMode.CreateNew);
StreamWriter sw = new StreamWriter(aFile);
You can also create a StreamWriter object directly from a file:
StreamWriter sw = new StreamWriter("Log.txt",true);The parameters of this constructor are the file name and a Boolean value. This Boolean value specifies whether to append the file or create a new file:
a. If it is false, create a new file or intercept the existing file and open it
b. If it is true, open the file and keep the original data. If it cannot be found, create a new file.
The StreamWriter object does not provide options similar to FileMode and FileAccess in FileStream. It can only use Boolean to append files or create new files, so it always has read and write rights to the file. If you want to use advanced parameters, you must first specify them in the FileStream constructor and then create a StreamWriter in the FileStream object. Examples are as follows:
static void Main(string[] args)
{
FileStream aFile = new FileStream("Log.txt",FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(aFile);
bool truth = true;
sw.WriteLine("Hell to you");
sw.WriteLine("It is now {0} and things are looking good.",DateTime.Now.ToLongDateString());
sw.Write("More than that");
sw.Write("it's {0} that C# is fun.",truth);
sw.Close();
}
Note: WriteLine() automatically wraps the line, and Write() will write at the end of the last time each time.
StreamReader object:
Used to read data, its creation method is similar to StreamWriter. The simplest creation method is as follows:
FileStream aFile = new FileStream(“Log.txt”,FileMode.Open);StreamReader sr = new StreamReader(aFile);
It can also be created with a specific file path: StreamReader sr = new StreamReader ("Log.txt"); specific examples are as follows:
static void Main(string[] args)
{
string line;
FileStream aFile = new FileStream("Log.txt",FileMode.OpenOrCreate);
StreamReader sr = new StreamReader(aFile);
line = sr.ReadLine();
while(line != null)
{
Console.WriteLine(line);
line = sr.ReadLine();
}
sr.Close();
Console.ReadKey();
}
1. Read data
The ReadLine() method is not the only way to access data in a file. The StreamReader class also contains many methods for reading data, the simplest of which is Read(). This method returns the next character of the stream as a positive integer value. If the end of the stream is reached, it returns -1. Use the Convert class. You can convert this value to a character and rewrite the above using this method:
StreamReader sr = new StreamReader(aFile);
int charCode;
charCode = sr.Read();
while(charCode !=-1)
{
Console.WriteLine(Convert.ToChar(charCode));
charCode = sr.Read();
}
sr.Close();
For small files, there is a very simple method, ReadToEnd(), which reads the entire file and returns it as a string. like:
StreamReader sr = new StreamReader(aFile);
line = sr.ReadToEnd();
Console.WriteLine(line);
sr.Close();
Another way to handle large files is the new static method File.ReadLines() in .NET 4, which returns an IEnumerable<string> collection. Iterates over the strings in this collection, reading the file one line at a time. Use as follows
foreach(string alternativeLine in File.ReadLines("Log.txt"))
{
Console.WriteLine(alternativeLine);
}
2. Files separated by delimiters
For data formats separated by delimiters, the Split() method of the String class is commonly used to convert a string into an array. Specific examples are as follows:
private static List<Dictionary<string, string>> GetData(out List<string> columns)
{
string line;
string[] stringArray;
char[] charArray = new char[] { ',' };
List<Dictionary<string, string>> data =
new List<Dictionary<string, string>>();
columns = new List<string>();
FileStream aFile = new FileStream(@"..\..\SomeData.txt",FileMode.Open);
StreamReader sr = new StreamReader(aFile);
line = sr.ReadLine();
stringArray = line.Split(charArray);
for(int x = 0;x<=stringArray.GetUpperBound(0);x++)
{
columns.Add(stringArray[x]);
}
line = sr.ReadLine();
while(line!=null)
{
stringArray = line.Split(charArray);
Dictionary<string, string> dataRow = new Dictionary<string, string>();
for(int x = 0;x<=stringArray.GetUpperBound(0);x++)
{
dataRow.Add(columns[x],stringArray[x]);
}
data.Add(dataRow);
line = sr.ReadLine();
}
sr.Close();
return data;
}
static void Main(string[] args)
{
List<string> columns;
List<Dictionary<string, string>> myData = GetData(out columns);
foreach(string column in columns)
{
Console.Write("{0,-20}",column);
}
Console.WriteLine();
foreach(Dictionary<string,string> row in myData)
{
foreach(string column in columns)
{
Console.Write("{0,-20}", row[column]);
}
Console.WriteLine();
}
Console.ReadKey();
}
Asynchronous file access:
When performing a large number of file access operations at one time or processing very large files, reading and writing file system data is very slow. At this time, if you want to perform other operations while waiting for these operations to be completed, you need to perform a one-step operation. This This kind of asynchronous is applicable to the FileStream, StreamWriter and StreamReader classes, usually with the Async suffix, such as the ReaderLineAsync() method of the StreamReader class.
Read and write compressed files:
When processing files, using compressed files will save a lot of hard drive space. The System.IO.Compression namespace contains classes that can compress files in code, using the GZIP or Deflate algorithms. But compressing files doesn't just mean compressing them. Commercial applications allow multiple files to be placed in a compressed file (often called an archive file). This section is much simpler: just saving text data in a compressed file. This file cannot be accessed in external utilities, but the file is much smaller than the uncompressed version.
There are two compression stream classes DeflateStream and GZipStream in the System.IO.Compression namespace. They work very similarly. For these two classes, they must be initialized with an existing stream. For files, the stream is a FileStream object. You can then use them for StreamReader and StreamWriter. Additionally just specify whether the stream is for compression (saving a file) or decompression (loading a file), and the class knows what to do with the data passed to it. Examples are as follows:
static void SaveCompressedFile(string filename,string data)
{
FileStream fileStream = new FileStream(filename,FileMode.Create,FileAccess.Write);
GZipStream compressionStream = new GZipStream(fileStream,CompressionMode.Compress);
StreamWriter writer = new StreamWriter(compressionStream);
writer.Write(data);
writer.Close();
}
static string LoadCompressedFile(string filename)
{
FileStream fileStream = new FileStream(filename,FileMode.Open,FileAccess.Read);
GZipStream compressionStream = new GZipStream(fileStream,CompressionMode.Decompress);
StreamReader reader = new StreamReader(compressionStream);
string data = reader.ReadToEnd();
reader.Close();
return data;
}
static void Main(string[] args)
{
string filename = "compressedFile.txt";
string sourceString = Console.ReadLine();
StringBuilder sourceStringMultiplier = new StringBuilder(sourceString.Length*100);
for(int i = 0; i<100; i++)
{
sourceStringMultiplier.Append(sourceString);
}
sourceString = sourceStringMultiplier.ToString();
Console.WriteLine("Source data is {0} bytes long.",sourceString.Length);
SaveCompressedFile(filename, sourceString);
Console.WriteLine("\nData saved to {0}.",filename);
FileInfo compressedFileData = new FileInfo(filename);
Console.WriteLine("Compressed file is {0} bytes long.",compressedFileData.Length);
string recoveredString = LoadCompressedFile(filename);
recoveredString = recoveredString.Substring(0,recoveredString.Length/100);
Console.WriteLine("\nRecovered data: {0}",recoveredString);
Console.ReadKey();
}
Serialized object:
Applications often need to store data on the hard drive. Building text and data files piece by piece was described earlier, but this is usually not the simplest way. Sometimes it's better to store data in the form of objects.
1. .NET Framework provides the infrastructure for serialized objects in the System.Runtime.Serialization and System.Runtime.Serialization.Formatter namespaces. The latter contains some namespaces.Concrete classes implement this infrastructure. In the framework, there is an important implementation available: System.Runtime.Serialization,Formatter.Binary. This namespace contains the BinaryFormatter class that converts objects intoSerialize to binary data, and binary data can also be serialized to objects.
method | illustrate |
void Serialize(Stream stream,object source) | Serialize source to stream |
object Deserialize(Stream stream) | Deserialize the data in the stream and return the resulting object |
IFormatter serializer = new BinaryFormatter(); serializer.Serialize(myStream,myObject);
Deserialization is equally simple:
IFormatter serializer = new BinaryFormatter(); MyObjectType myNewObject = serializer.Deserialize(myStream) as MyObjectType;
This code will be put into practice below:
Add a new Product class:
public class Product
{
public long Id;
public string Name;
public double Price;
[NonSerialized]
string Notes;
public Product(long id, string name, double price, string notes)
{
Id = id;
Name = name;
Price = price;
Notes = notes;
}
public override string ToString()
{
return string.Format("{0}: {1} (${2:F2}) {3}", Id, Name, Price, Notes);
}
}
Then add in the Main method of the Progress class:
static void Main(string[] args)
{
List<Product> products = new List<Product>();
products.Add(new Product(1,"Spily Pung",1000.0,"Goods stuff."));
products.Add(new Product(2, "Gloop Galloop Soup", 25.0, "Tasty."));
products.Add(new Product(4, "Hat Sauce", 12.0, "One for the kids."));
Console.WriteLine("Products to save:");
foreach(Product product in products)
{
Console.WriteLine(product);
}
Console.WriteLine();
IFormatter serializer = new BinaryFormatter();
FileStream saveFile = new FileStream("Products.bin", FileMode.Create, FileAccess.Write);
serializer.Serialize(saveFile,products);
saveFile.Close();
FileStream loadFile = new FileStream("Products.bin",FileMode.Open,FileAccess.Read);
List<Product> saveProducts = serializer.Deserialize(loadFile) as List<Product>;
loadFile.Close();
Console.WriteLine("Products loaded:");
foreach(Product product in saveProducts)
{
Console.WriteLine(product);
}
}
This example creates a collection of Product objects, saves the collection to disk, and then reloads it. But the first run throws an exception because the Product object is not marked as "serializable". The .NET Framework requires objects to be marked as serializable in order to serialize them. There are many reasons for this, including:
1. The serialization effect of some objects is not good. For example, they need to reference local data that only exists if they themselves are in memory.
2. Some objects contain sensitive data that should not be saved or transferred to another process in an unsafe manner.
The Serializable attribute is not inherited by derived classes; it must be applied to every class that is to be serialized. NoSerialized This attribute can be used by any member, and will not be serialized after use.
Monitor the file system:
.NET Freamwork uses FileSystemWatcher to monitor files. The process is very simple: you must first set some properties to specify the location and content of the monitoring and the time when the event to be processed by the application is triggered. Then provide the address of the customized event handler to FileSystemWatcher. When an important event occurs, FileSystemWatcher can call these event handlers, and finally open FileSystemWatcher and wait for the event. The properties that must be set before enabling FileSystemWatcher are as follows:
Attributes | illustrate |
Path | Set the file location or directory to monitor |
NotifyFilter | This is a combination of NotifyFilters enumeration values. The NotifyFilters enumeration value specifies what content is to be monitored in the monitored file. These represent the properties of the file or folder to be monitored. If the specified property changes, the event is raised. Possible enumeration values are Attributes, CreationTime, DirectoryName, FileName, LastAccess, LastWrite, Security, and Size. Note that these enumeration values can be combined using the binary OR operator |
Filter | A filter that specifies which files to monitor. For example, *.txt |
Instantiate the FileSystemWatcher object and add a listener
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Deleted += (s, e) => AddMessage("File: {0} Deleted",e.FullPath);
watcher.Renamed += (s, e) => AddMessage("File renamed from {0} to {1}",e.OldName,e.FullPath);
watcher.Changed += (s, e) => AddMessage("File: {0} {1}",e.FullPath,e.ChangeType.ToString());
watcher.Created += (s, e) => AddMessage("File: {0} Created",e.FullPath);
Set the listening position and other listening conditions:
watcher.Path = System.IO.Path.GetDirectoryName("Progress.txt");
watcher.Filter = System.IO.Path.GetFileName("Progress.txt");
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size;
watcher.EnableRaisingEvents = true;
System.IO.Path is used to process and extract the information in the file location string. Here we first use it to extract the directory name entered by the user in the text box through the GetDirectoryName() method. NotifyFilter is the filter.