In the print dialog, choose "Save as PDF" as the destination.
Operating Systems 1, Lab 3
How a program moves bytes in and out of files, and how it walks the folders that hold them
IDisposable, Dispose() and the using statementFileStream with the right FileMode, FileAccess and FileShare, and predict Position and LengthStreamReader / StreamWriter and values with BinaryReader / BinaryWriterFile shortcuts and the DeflateStream decoratorDirectory, DirectoryInfo and FileInfoPart 1
Bytes flowing between a program and the outside world
A new text file holds only A. Then it holds A, four spaces and z. How many bytes each time?
Size: 1 byte(s)
Size: 6 byte(s)
65 = 01000001 = 'A'
32 = 00100000 = ' '
32 = 00100000 = ' '
32 = 00100000 = ' '
32 = 00100000 = ' '
122 = 01111010 = 'z'A is the number 65, a space is 32, z is 122External source
file, network
Backing store
FileStream
Decorator
DeflateStream
Adapter
StreamReader, BinaryWriter
Your program
Part 2
Who closes the file?
An open file is an operating system handle the garbage collector does not track: its class implements IDisposable, and you call Dispose().
class LogFile : IDisposable
{
private readonly FileStream fs;
private bool disposed = false;
public void Dispose()
{ Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing) fs.Dispose();
disposed = true;
}
~LogFile() { Dispose(false); }
}IDisposableDispose() for the users of the classDispose(bool) that does the work; the disposed flag makes it run onceGC.SuppressFinalize(this): cleanup is done, skip the finalizer| Way | Shape | Verdict |
|---|---|---|
| Call it yourself | r.Dispose(); at the end | skipped if an exception is thrown first |
| try / finally | finally { r?.Dispose(); } | always runs; ?. guards a failed constructor |
| using block | using (var r = ...) { ... } | disposed at the closing brace |
| using declaration | using var r = ...; | disposed at the end of the enclosing scope |
using LogFile log4 = new LogFile("log.txt");
log4.Write(67);
Console.WriteLine("end of Main");LogFile: file opened
end of Main
LogFile: file closedPart 3
Bytes, a position, and the rules for opening a file
new FileStream(path, FileMode, FileAccess, FileShare): the mode answers both questions.
| Can create | File missing | File exists |
|---|---|---|
Create | creates it | empties it (overwrites) |
CreateNew | creates it | throws IOException |
OpenOrCreate | creates it | opens it, Position 0 |
Append | creates it | opens it, Position at the end (Write only) |
| Needs the file | File missing | File exists |
|---|---|---|
Open | throws FileNotFoundException | opens it, Position 0 |
Truncate | throws FileNotFoundException | opens it and cuts it to 0 bytes |
test.txt holds HELLO. Open it with FileAccess.Write, write a then b, close. Predict the file.
| FileMode | Right after opening | File after writing ab |
|---|---|---|
Append | Position 5, Length 5 | HELLOab |
Create | Position 0, Length 0 | ab |
CreateNew | IOException | unchanged |
Same file, same two bytes. Now Open, OpenOrCreate, Truncate.
| FileMode | Right after opening | File after writing ab |
|---|---|---|
Open | Position 0, Length 5 | abLLO |
OpenOrCreate | Position 0, Length 5 | abLLO |
Truncate | Position 0, Length 0 | ab |
Read only : CanRead=True CanWrite=False CanSeek=True
Read write: CanRead=True CanWrite=True CanSeek=Truefs.WriteByte(65);
fs.WriteByte(66);Position=0 Length=0
Position=1 Length=1
Position=2 Length=2
File now holds: ABCanRead / CanWrite follow the FileAccess; CanSeek is true for a filePosition starts at 0, like the cursor at the start of a fileWriteByte writes one byte at Position and moves it forward by 1Length growdata.txt holds HELLO. Each case opens its own stream, then does one thing.
| Opened with | Then |
|---|---|
Open, Write | fs.ReadByte() |
Open, Read | fs.WriteByte(65) |
Append, Read | nothing |
Append, Write | fs.Seek(0, SeekOrigin.Begin) |
ReadByte on a Write-only stream:
NotSupportedException
WriteByte on a Read-only stream:
NotSupportedException
Append with FileAccess.Read:
ArgumentException
Append, then Seek back to 0:
IOExceptionNone: nobody else may open the fileRead: others may open it for readingWrite: others may open it for writingReadWrite: others may read and writeInheritable: a child process can inherit the handleFileShare.Read: second reader opened, first byte = 72
FileShare.Read: a writer was refused (IOException)
FileShare.None: second reader was refused (IOException)Read may return fewer bytes than asked, so loop until nothing is left; it returns 0 at the end. The file holds C# streams: predict the bytes.
data = new byte[fs.Length];
int numBytesToRead = (int)fs.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
int n = fs.Read(data, numBytesRead, numBytesToRead);
if (n == 0) break; // end of the file
numBytesRead += n;
numBytesToRead -= n;
}Read 10 bytes:
67 35 32 115 116 114 101 97 109 115
copy.txt holds: C# streamsfs.Seek(offset, SeekOrigin.Begin); // or Current, or Endp: the new Position, o: the offset you passb is 0 for Begin, the current Position for Current, the Length for EndNew empty file. fs.Seek(20, SeekOrigin.Begin); fs.WriteByte(65); then read every byte from 0. Predict the Length and the bytes.
Position=21 Length=21
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 65 Part 4
Turn bytes into lines and values, or compress them
Write and WriteLine add text, Flush pushes the buffered text down to the file, Close flushes and closes. Predict each size.
var w = new StreamWriter(fs); // fs: note.txt
w.Write("Hello"); // size?
w.Flush(); // size?
w.WriteLine(" OS"); // size?
w.Close(); // size?After Write : file size = 0
After Flush : file size = 5
After WriteLine: file size = 5
After Close : file size = 10lines.txt holds Streams, and, Directories. Peek looks without consuming, Read consumes one character, ReadLine one line (null at the end).
Console.WriteLine("Peek: " + sr.Peek() + " = '" + (char)sr.Peek() + "'");
Console.WriteLine("Read: " + (char)sr.Read());
Console.WriteLine("Peek: " + (char)sr.Peek());
Console.WriteLine("ReadLine: " + sr.ReadLine());Peek: 83 = 'S'
Read: S
Peek: t
ReadLine: treamssr.BaseStream.Seek(0, SeekOrigin.Begin);
string str = sr.ReadLine();
while (str != null)
{
Console.WriteLine(str);
str = sr.ReadLine();
}1: Streams
2: and
3: Directories
ReadLine returned null after line 3Characters read: 25
File size: 25 bytesValues are stored in binary form, not as text. Predict the file size: an int, a double, a bool, a double.
dataout = new BinaryWriter(
new FileStream("data", FileMode.Create));
dataout.Write(20); // int
dataout.Write(101.50); // double
dataout.Write(true); // bool
dataout.Write(12.2 * 15.5); // double
dataout.Close();File size: 21 bytes
First 4 bytes (the int 20): 20 0 0 0
Byte 12 (the bool true): 1| Method | What it does |
|---|---|
File.WriteAllText / ReadAllText | write / read a whole file as one string |
File.WriteAllLines / ReadAllLines | write / read an array of lines |
File.WriteAllBytes / ReadAllBytes | write / read an array of bytes |
File.Exists / File.Delete | check for a file / delete it |
File.Move | move or rename: there is no Rename method |
The decorator wraps the FileStream and is itself a stream; CompressionMode.Decompress gives the bytes back. Predict the size on disk.
using (FileStream fs = File.Create("data.bin"))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress))
ds.Write(original, 0, original.Length); // 1000 bytes: ABCABC...Original bytes : 1000
data.bin on disk: 15 bytes
Bytes after decompress: 1000 (first 65, last 65)Part 5
Create, list, measure, copy and search folders
Directory | DirectoryInfo | |
|---|---|---|
| Kind | static class | one object per folder |
| Call | Directory.Exists(path) | new DirectoryInfo(path).Exists |
| Gives | strings (paths) | DirectoryInfo and FileInfo objects |
CreateDirectory, Move, DeleteGetDirectories, GetFiles, GetFileSystemEntriesGetCurrentDirectory, GetDirectoryRoot, GetLogicalDrives, GetCreationTime, GetLastWriteTimeConsole.WriteLine("Root: " + Directory.GetDirectoryRoot(curDir));
Console.WriteLine("Drives: " + string.Join(" ", Directory.GetLogicalDrives()));
File.WriteAllText("lab3/test/a.txt", "abc");
Directory.Delete("lab3/test"); // inside try / catch: predict!Root: D:\
Drives: C:\ D:\
Delete(test) failed: IOExceptionBuilt by MakeSampleTree() in every directory program. Sizes are real file lengths.
site
about.txt 48 bytes
work.txt 13 bytes
images
logo.bin 300 bytes
wiki
home.txt 19 bytes
week1.txt 25 bytes
old
week0.txt 13 bytesabout.txt holds "Streams move bytes between a program and a file."week1.txt holds "Week 1: what is a stream?"Directory.GetFileSystemEntries("site", "w*");
Directory.GetFiles("site", "w*", SearchOption.AllDirectories);GetFileSystemEntries(site, w*):
site\wiki
site\work.txt
GetFiles(site, w*, AllDirectories):
site\work.txt
site\wiki\week1.txt
site\wiki\old\week0.txtFileInfo.Length is one file's size in bytes. Predict the size of the sample tree, then of its top folder alone (TopDirectoryOnly).
Name, Extension, FullName, Directory, and Length (in bytes)CreationTime, LastAccessTime, LastWriteTimeCreate, AppendText, Delete, Open, OpenRead, CopyToforeach (FileInfo fi in dirInfo.GetFiles("*", SearchOption.AllDirectories))
size += fi.Length;The directory size: 418 bytes
Top level files only: 61 bytesDirectoryCopy("site", "site-2", true) prints each folder it creates and each file it copies. Predict the order.
var dir = new DirectoryInfo(source);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!Directory.Exists(dest))
Directory.CreateDirectory(dest);
foreach (FileInfo file in dir.GetFiles())
file.CopyTo(Path.Combine(dest, file.Name), false);
if (copySubDirs)
foreach (DirectoryInfo subdir in dirs)
DirectoryCopy(subdir.FullName,
Path.Combine(dest, subdir.Name), copySubDirs);create site-2
copy site-2\about.txt
copy site-2\work.txt
create site-2\images
copy site-2\images\logo.bin
create site-2\wiki
copy site-2\wiki\home.txt
copy site-2\wiki\week1.txt
create site-2\wiki\old
copy site-2\wiki\old\week0.txt
Copying finished
Files in site-2: 6Part 6
About 30 minutes: predict first, then run it
About 6 minutes
File.WriteAllText("data.txt", "STREAM");
using var fs = new FileStream("data.txt", FileMode.Open, FileAccess.ReadWrite);
fs.ReadByte(); fs.ReadByte();
fs.Seek(2, SeekOrigin.Current);
fs.WriteByte(33);
fs.Seek(-1, SeekOrigin.End);
fs.ReadByte(); fs.ReadByte();
fs.Seek(8, SeekOrigin.Begin);
fs.WriteByte(90);1) ReadByte() = 83, Position = 1
2) ReadByte() = 84, Position = 2
3) Seek(2, Current): Position = 4
4) WriteByte(33): Position = 5, Length = 6
5) Seek(-1, End): Position = 5
6) ReadByte() = 77, Position = 6
7) ReadByte() = -1, Position = 6
8) Seek(8, Begin), WriteByte(90): Position = 9, Length = 9
Bytes: 83 84 82 69 33 77 0 0 90About 5 minutes
FileMode and FileAccess?FileMode.CreateNew is used twice in a row on new.txt. What happens each time?hello.txt holds HELLO. Open with Open, Write, then WriteByte((byte)'J'). The file?FileMode.Truncate with FileAccess.Read. What happens, and what does the file hold?| Case | Answer |
|---|---|
| (a) | Append with Write: 3 runs give 3 lines |
| (b) | created, then IOException: the file exists |
| (c) | JELLO: Open keeps the data, J overwrites H |
| (d) | ArgumentException: Truncate needs write access; still JELLO |
About 4 minutes
bw.Write(7);
bw.Write(true);
bw.Write(3.5);
bw.Write(false);
bw.Write(1000);Size: 18 bytes
7 True 3.5 False 1000
Last 4 bytes: 232 3 0 0About 5 minutes
Windows, Linux, macOS, FreeBSD, Android to os.txt with a StreamWriter, one per lineStreamReader.ReadLine until it returns nullstring line = sr.ReadLine();
while (line != null)
{
count++;
if (line.Length > longest.Length)
longest = line;
line = sr.ReadLine();
}Lines: 5
Longest: Windows (7 characters)
File size: 41 bytesAbout 6 minutes
PrintTree(DirectoryInfo dir, string indent)/, then its files with their sizes, then call itself for each subfolder with a deeper indentsite: print the total, then the size of site/wiki alonestatic long PrintTree(DirectoryInfo dir, string indent)
{
long total = 0;
Console.WriteLine(indent + dir.Name + "/");
foreach (FileInfo f in dir.GetFiles())
{
Console.WriteLine(indent + " " + f.Name
+ " (" + f.Length + ")");
total += f.Length;
}
foreach (DirectoryInfo sub in dir.GetDirectories())
total += PrintTree(sub, indent + " ");
return total;
}site/
about.txt (48)
work.txt (13)
images/
logo.bin (300)
wiki/
home.txt (19)
week1.txt (25)
old/
week0.txt (13)
Total: 418 bytes
wiki: 57 bytesAbout 5 minutes
DirSearch("site") from the source, searching for stream
foreach (var directory in Directory.GetDirectories(sDir))
{
foreach (var filename in Directory.GetFiles(directory))
{
// read it, lower-case it,
// add it if it contains "stream"
}
DirSearch(directory);
}DirSearch report?FileSearch(string sDir): search the files of sDir first, then call itself for each subfolderFileSearch report?foreach (var filename in Directory.GetFiles(sDir))
using (var sr = new StreamReader(filename))
if (sr.ReadToEnd().ToLower().Contains(SearchText))
FilesFound.Add(filename);
foreach (var directory in Directory.GetDirectories(sDir))
FileSearch(directory);DirSearch: files containing the word stream
site\wiki\week1.txt
FileSearch: files containing the word stream
site\about.txt
site\wiki\week1.txtIDisposable gets disposed: prefer usingFileMode answers "missing?" and "old data?"; FileAccess limits what the stream may do; Open overwrites in placeFlush or CloseMahmoud Abas|Streams and Directory Operations in C#