ASP.NET Core – Byte, Stream, Directory, File 基础
前言
以前的文章: Stream 基础和常用 和 IO 常用.
这篇主要是做一个整理, 方便要用的时候 warm up.
之前有讲过 Bit, Byte 的基本概念: Bit, Byte, ASCII, Unicode, UTF, Base64
这里大概复述一下,
计算机最小的单元是 bit, 1 bit 表示一个二进制 (binary), 0 或 1.
byte 是第 2 小的单元, 1 byte = 8 bits. 在 .NET 中经常需要和 bytes 打交道.
二进制是机器看的, 人看的是字符, 所以中间就有了二进制和字符的转换. 那就是 ASCII, Unicode, UTF-8 等等.
.NET 中的 byte, char, string
1 byte = 8 bits, 虽然 8 bits 是二进制, 但是在 Visual Studio debug 的时候显示的是十进制, 0-255 数.
bytes[] 就是一堆 byte 在里面咯.
char 是字符, 1 char = 2 bytes, 可以承载大部分 Unicode 字符, 但不是全部哦. 参考: string and 4-byte Unicode characters 和 How are 4 bytes characters represented in C#
string 是 char 的上层封装. 写业务代码基本上不会碰 char, 一律用 string.
最常见的操作就是在 bytes[] 二进制 和 string 字符串之间做转换. 用到的编码可以是 ASCII, UTF-8 等等
.NET 中的 short, int, long
参考:
MS Dosc – Integral numeric types
short 是 Int16, 16 表示 16 bits, 也就是 2 bytes. 最多能表达 2^16 = 65536 种状态,
由于需要 cover negative number 复数, 所以可用值得范围是 -32768 – 32767 (2^15 = 32768, 减掉 1 位 for zero 所以最大值是 32767)
ushort 是 UInt16 也是 16bits, 但是它不支持 negative number, 所以可用值是 0 – 65535 (2^16 = 65536 减掉 1 位 for zero)
int 是 Int32 概念是一样的, 可用值是 2^31= -2147483648 – 2147483647
long 是 Int64 可用值是 -9223372036854775808 – 9223372036854775807 (天文数字)
uint, ulong 和 ushort 同个原理, 不支持 negetive value.
Bytes and String Conversion
string to bytes
var value = "Hello World";
var bytes = Encoding.UTF8.GetBytes(value);
Hello World 字母被转换成了 11 个 bytes, 第一个字大写 H 的 byte 是 72. 这个是十进制的表示, 它的二进制是 01001000。
如果想看二进制可以把 byte convert to string 二进制
var value = "Hello World";
var bytes = Encoding.UTF8.GetBytes(value).Select(b => Convert.ToString(b, 2).PadLeft(8, '0')).ToList();
bytes to string
转回去就用 .GetString(bytes)
由于 UTF-8 兼容 ASCII, 所以在 bytes to string 的时候 UTF-8 可以解的出来 ASCII 的 bytes
var value = "Hello World";
var bytes = Encoding.ASCII.GetBytes(value);
var value1 = Encoding.UTF8.GetString(bytes); // Hello World
但如果是 UTF-32 就不行了哦
重点
一定要记得, 当要处理 bytes 和字符串的时候, 用什么 encode 一定要搞清楚. 不然很容易就搞成乱码一堆了。
Directory 基本操作
有 2 个 class 负责在 .NET 中管理 directory (folder). 一个是 DirectoryInfo, 另一个是 Directory
Directory 是一个静态类, 里面有一些常用的方法, 比如创建, 删除, copy folder 等等
DirectoryInfo 是一个对 folder 的 reference, 如果只是一次性操作, 通常用静态 Directory 就可以了, 但如果要多次对一个 folder 进行操作那么可以用 DirectoryInfo 对象.
Create Folder
var rootPath = Path.Combine(AppContext.BaseDirectory, @"..\..\..\"); // AppContext.BaseDirectory = C:\...\ProjectFolder\bin\Debug\net6.0\
var directoryInfo = Directory.CreateDirectory(Path.Combine(rootPath, "Parent"));
如果 folder 已经存在, 它就什么也不做, 也不会报错, 也不会覆盖, 就返回 folder 的 reference 而已.
Is Folder Exist
查看 folder 是否已经存在
var folderExist = Directory.Exists(Path.Combine(rootPath, "Parent")); // true
Delete Folder
Directory.Delete(Path.Combine(rootPath, "Parent"), recursive: false);
recursive 是指如果这个 folder 里面有其它 folders 或 files 是否也一起删除, false 表示只要有任何东西就不删除 (如果有它会报错)
如果是 true 就表示删除到干干净净.
默认是 false, 所以 empty folder 才可以直接删除, 不然就要声明 true, 挺安全的.
如果 folder not found 它也会报错哦.
Cut & Paste / Move Folder
Directory.Move(Path.Combine(rootPath, "MyFolder"), Path.Combine(rootPath, @"SomeParent\MyFolder"));
要确保 MyFolder, SomeParent 是存在的, 还有 SomeParent 中不可以有 MyFolder, 不然也会报错.
用 DirectoryInfo 的写法也是差不多的, 这里举一个例子就好.
var dir = new DirectoryInfo(Path.Combine(rootPath, "MyFolder"));
dir.MoveTo(Path.Combine(rootPath, @"SomeParent\MyFolder"));
Rename Folder
Directory.Move(Path.Combine(rootPath, "MyFolder"), Path.Combine(rootPath, @"SomeParent\MyRenameFolder"));
rename 其实是通过 move 来实现的, 所以也可以 cut & paste + rename 一起搞.
Get files and subfolders
get files
var fileFullNames = Directory.GetFiles(
Path.Combine(rootPath, @"Parent"),
searchPattern: "*.txt",
searchOption: SearchOption.AllDirectories
);
第一个参数是 folder 路径.
searchPattern 是查找的方式, 它不是正则表达哦, 只是可以用 * 和 ? 通配符而已, * 代表正则的 .* 匹配 whatever 任意多个, 或没有, ? 代表正则的 .? 1 个 whatever 或者没有. 没有声明 searchPattern 就是全部 files 都要.
searchOption 可以指定是否查找 descendant folders, 默认是 TopDirectoryOnly 只查询第一层的 files.
注意, 它的返回值是 array of strings, 而不是 FileInfo 对象哦. 它是 FileInfo.FullName, 从 C dirve 的完整 path 路径包括 file name.
get folders
var subfolderFullNames = Directory.GetDirectories(
Path.Combine(rootPath, @"Parent")
);
get files and folders
var subfolderFullNames = Directory.GetFileSystemEntries(
Path.Combine(rootPath, @"Parent")
);
parameter 控制方式和 GetFiles 是一样的.
Copy & Paste Folder
并没有 Directory.Copy 这样的接口, File.Copy 就有.... 所以要实现 copy & paste folder 是很不友好的. 参考: stackoverflow – Copy the entire contents of a directory in C# 和 MS Docs – How to: Copy directories
做法就是递归 for loop GetFileSystemEntries > Directory.Create > File.Copy.
Stream
Stream 有点像水, stream 里面是装 bytes 的, bytes 就像鱼儿.
当 stream 静止的时候就像池塘, 里面有很多鱼儿. 当 stream 被传输的时候像水流, 鱼儿会从一个水池被导入进另一个水池中.
.NET Stream 结构
ASP.NET Core build-in 了许多 Stream Class 来处理 Bytes
Stream(抽象类) > TextReader(抽象类) > StreamReader(实体类) > MemoryStream(实体类), FileStream(实体类) 等等
顾名思义, MemoryStream 是负责缓存的, FileStream 是文件的.
File 基本操作
有 3 个 class 经常会用来操作 File.
File, FileInfo, FileStream
File 和 FileInfo 就像 Directory 和 DirectoryInfo 的关系. 一次性操作或者多次操作选择而已.
File 和 Directory 不同, File 是有内容的, 它里面就是一堆的 bytes. 要从 File 里读取 bytes 需要通过 FileStream.
FileStream 提供了很多对 bytes 的控制, 比如读多少, 从哪里开始读, 写入的时候先写入内存, 确定后才写入磁盘等等.
Create File
using var fileStream = File.Create(Path.Combine(rootPath, "Text.txt"));
它返回的是 fileStream 而不是 FileInfo 哦, 通常创建后就是要写入嘛.
FileStream 是 IDisposable 所以必须配上 using, 使用完后要释放.
Is File Exist
var isFileExist = File.Exists(Path.Combine(rootPath, "Text.txt"));
Delete File
File.Delete(Path.Combine(rootPath, "Text.txt"));
如果 file 不存在, 它不会报错哦, 这个 Directory 是不同的.
Cut & Paste / Rename / Move File
File.Move(Path.Combine(rootPath, "Text.txt"), Path.Combine(rootPath, "Text123.txt"));
和 Directory 一样通过 move 来实现 rename.
Copy & Paste File
File.Copy(Path.Combine(rootPath, "Text.txt"), Path.Combine(rootPath, "Text123.txt"));
Directory 没有 build-in 的 copy & paste, 但 File 有. 如果 paste 的文件名字已经存在, 会报错哦.
FileInfo 常用属性
var fileInfo = new FileInfo(Path.Combine(rootPath, "Text.txt"));
var isFileExist = fileInfo.Exists; // true var fullName = fileInfo.FullName; // C:\...\Folder\Text.txt
var name = fileInfo.Name; // Text.txt
var extension = fileInfo.Extension; // .txt
var fileLength = fileInfo.Length; // 12 (单位是 bytes) var directoryFullName = fileInfo.DirectoryName; // C:\...Folder
var directoryInfo = fileInfo.Directory; var creationTime = fileInfo.CreationTime; // DateTime
var creationTimeUtc = fileInfo.CreationTimeUtc; // DateTime
var lastAccessTime = fileInfo.LastAccessTime; // DateTime
var lastWriteTime = fileInfo.LastWriteTime; // DateTime
File Open
using var fileStream = File.Open(Path.Combine(rootPath, "Text.txt"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
第一个参数是文件路径.
第二个 FileMode 是指打开的方式, 它有几个选择, 比如 CreateNew, Create, Open, OpenOrCreate 等等. 它们之间差不多但又有点不同, 要用的时候去看一下就可以了.
比如 CreateNew 明确说明要创建, 如果已经有了会报错. Create 则不会报错.
第三个 FileAccess 是指打开的目的, Read, Write, ReadWrite.
第四个 FileShare 是指, 当打开的时候其它访问可以接受吗? 比如当写入的时候, 或许不希望其它访问. 这样容易造成并发混乱.
FileStream
Read 场景:
有一个 Text.txt 的文件, 里面的内容是 abc我你他, 需要把第 2 个字 "b" 读出来.
OpenRead FileStream
var rootPath = Path.Combine(AppContext.BaseDirectory, @"..\..\..\"); // AppContext.BaseDirectory = C:\...\ProjectFolder\bin\Debug\net6.0\
using var fileStream = File.OpenRead(Path.Combine(rootPath, "Text.txt")); // 内容 = "abc我你他"
通过 OpenRead 开启对 File 的读. 返回的 fileStream 是用来控制 bytes 的.
prepare empty buffer (array of bytes)
先准备一个容器 array of bytes.
var secondWordByte = new byte[1];
adjust FileStream position (针头)
调整 FileStream 的针头, 要读第 2 个字出来, 所以是从最开头偏移 1 个 byte. SeekOrigin.Current 表示从当前的针头位置 (目前没有移动过所以它就是起点)
var p1 = fileStream.Position; // 0
fileStream.Seek(offset: 1, SeekOrigin.Current);
var p2 = fileStream.Position; // 1
常见的调整 position 方法有:
fileStream.Seek(offset: 1, SeekOrigin.Current); // 从当下开始偏移
fileStream.Seek(offset: 1, SeekOrigin.Begin); // 从前面开始偏移
fileStream.Seek(offset: 1, SeekOrigin.End); // 从后面开始偏移
read bytes from file stream to buffer
接着读取 bytes 放入准备好的容器
fileStream.Read(secondWordByte, offset: 0, count: 1);
读入容器的时候也可以调整偏移 (目前没有需要), count: 1 表示读 1 个 byte 装入容器.
read 完以后, fileStream 的针头位置就去到第 2 位了, buffer 就有 bytes 了 (注: 它是 copy 过去而不是 cut 哦, 所以 stream 里面依然有 bytes)
fileStream.Seek(offset: 1, SeekOrigin.Current);
fileStream.Read(secondWordByte, offset: 0, count: 1);
var p3 = fileStream.Position; // 2
所有 stream 的操作都是这样的, 读写都是. 一定要有 position 的概念. 读写完成后 position 会自动跑位, 比如当前在 5, 读 3 bytes, 那么它就去到 8, 再写入 2 个 bytes 它就变成 10.
read step: prepare empty buffer > 调整 stream positon > read from stream to 容器 (指定 read how many bytes and offset).
write step: prepare data buffer > 调整 stream position (通常是 set to end) > write from buffer to stream (指定 write how many bytes and offset).
convert byte to string
接着, 把 bytes 转换成字符串, 就可以了. 必须清楚这个 file 里面的 bytes 是用什么编码的, 不然就解不出来了.
var value = Encoding.UTF8.GetString(secondWordByte); // "b"
read all bytes or text
如果只是想简单的读到完, 可以直接调用 ReadAllBytes
var bytes = File.ReadAllBytes(Path.Combine(rootPath, "Text.txt"));
var value = Encoding.UTF8.GetString(bytes); // abc我爱你
甚至可以直接 read all text
var value = File.ReadAllText(Path.Combine(rootPath, "Text.txt"), Encoding.UTF8);
底层原理是一样的, 只是一个封装而已.
Write 的场景:
写和读区别不到, 直接看代码就可以了
var rootPath = Path.Combine(AppContext.BaseDirectory, @"..\..\..\");
using var fileStream = File.OpenWrite(Path.Combine(rootPath, "Text.txt"));
var buffer = Encoding.UTF8.GetBytes("abc我爱你");
fileStream.Seek(0, SeekOrigin.End);
fileStream.Write(buffer);
fileStream.Flush();
唯一特别的是 Flush. 当调用 Write 的时候, buffer 并没有马上被写入到磁盘. 而是在内存中.
调用 Flush 可以立刻让它写入磁盘里. 一般上是不需要去调用它的, 因为在 fileStream displose 的时候它会自动去做写入磁盘这个动作.
clear stream
fileStream.SetLength(0)
巩固记忆:
总之, 记住几个东西, 就不会搞混了.
1 个 stream
1 个 buffer
buffer 就是 bytes[]
stream 里面也是 bytes[]
一般上:
read 的情况, buffer 是空的, 从 stream 读出来装入 buffer. 过程中可以调整双方的 offset. 可以控制读多少 bytes 过去.
write 的情况复杂一点, buffer 是有 bytes 的 (准备写入的 bytes). stream 有可能是空的, 也有可能有 data. 通常会往后继续增加 (append 的概念). 所以会先把 stream 调到 SeekOrigin.End 然后写入. 过程中也是可以调整 offset. 可以控制写多少 bytes 过去.
MemoryStream
它的读写方式和 FileStream 基本上是一样的. 只是没有 Flush 的概念. 因为它只是内存而已, 跟磁盘没有关系.
还有它常配合 StreamWriter 做写入
using var memoryStream = new MemoryStream();
using var streamWriter = new StreamWriter(memoryStream);
using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
csvWriter.WriteField("Hello World");
await csvWriter.FlushAsync(); var rootPath = env.WebRootPath;
var csvFilePath = Path.Combine(rootPath, "google-offline-conversion.csv");
using var fileStream = System.IO.File.Create(csvFilePath);
await fileStream.WriteAsync(memoryStream.ToArray());
await fileStream.FlushAsync();
StreamReader 和 StreamWriter
StreamReader / Writer 适合用在读写 "string" 的地方. 它省去了 encode/decode 的繁琐操作, 代码可读性比较好一些
without StreamReader
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello World")); var buffer = new byte[memoryStream.Length]; // 做一个容器
await memoryStream.ReadAsync(buffer); // read to 容器
var text = Encoding.UTF8.GetString(buffer); // decode to text Console.WriteLine(text);
搞容器, 放进去, 又 decode. 挺繁琐的
with StreamReader
using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello World")); using var streamReader = new StreamReader(memoryStream, encoding: Encoding.UTF8); // declare reader
var text = await streamReader.ReadToEndAsync(); // read text Console.WriteLine(text);
可读性提高了.
StreamWriter 也是同理, 这里就不举例了.
ASP.NET Core – Byte, Stream, Directory, File 基础的更多相关文章
- 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码
前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
第一部分:http://www.cnblogs.com/cgzl/p/7637250.html 第二部分:http://www.cnblogs.com/cgzl/p/7640077.html 之前我介 ...
- 【转载】从头编写 asp.net core 2.0 web api 基础框架 (3)
Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratc ...
- 【转载】从头编写 asp.net core 2.0 web api 基础框架 (1)
工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...
- 从头编写 asp.net core 2.0 web api 基础框架 (2)
上一篇是: http://www.cnblogs.com/cgzl/p/7637250.html Github源码地址是: https://github.com/solenovex/Building- ...
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
第1部分:http://www.cnblogs.com/cgzl/p/7637250.html 第2部分:http://www.cnblogs.com/cgzl/p/7640077.html 第3部分 ...
- 【转载】从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratc ...
- 【转载】从头编写 asp.net core 2.0 web api 基础框架 (2)
Github源码地址是: https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scra ...
- asp.net core系列 59 Ocelot 构建基础项目示例
一.入门概述 从这篇开始探讨Ocelot,Ocelot是一个.NET API网关,仅适用于.NET Core,用于.NET面向微服务/服务的架构中.当客户端(web站点.ios. app 等)访问we ...
随机推荐
- Modbus转Profinet网关模块连PLC与流量计通讯案例
一.案例背景 在饮品加工厂中,会涉及到流量计的使用,然而达到对流量计的精准控制和数据采集需要用到PLC,由于PLC和流量计可能使用不同的通信协议(如Profinet和Modbus),造成两者不能自接进 ...
- 机器学习:详解迁移学习(Transfer learning)
详解迁移学习 深度学习中,最强大的理念之一就是,有的时候神经网络可以从一个任务中习得知识,并将这些知识应用到另一个独立的任务中.所以例如,也许已经训练好一个神经网络,能够识别像猫这样的对象,然后使用那 ...
- JavaScript 探究[] == ![]结果为true,而 {} == !{}却为false
console.log( [] == ![] ) // true console.log( {} == !{} ) // false 在比较字符串.数值和布尔值的相等性时,问题还比较简单.但在涉及到对 ...
- elementui中实现loding实现局部加载,以el-dialog为例
效果 封装loading加载(也可以直接使用,封装为了方便多次调用) 组件定义:loadDiy.js import { Loading } from "element-ui"; e ...
- .NET 8 通用权限框架 前后端分离,开箱即用
前言 推荐一个基于.NET 8 实现的通用权限开发框架Admin.NET,前端使用Vue3/Element-plus开发. 基于.NET 8(Furion)/SqlSugar实现的通用管理平台.整合 ...
- 【Java】实体类转换框架 MapStruct
简单尝试了下发现比Dozer还有BeanUtil还方便小巧 注解的作用是在生成字节码文件时实现具体GetterSetter方法,实际转换时就是赋值操作,嘎嘎快 参考文章: https://juejin ...
- 【FTP】小米手机FTP传输
设置方法 打开[文件管理],右上角按钮选择[远程管理] 点击设置按钮 默认保持唤醒状态 设置FTP账户的用户名密码 Windows访问: 然后开启服务即可: 手机和电脑连接同一个网络内访问 每次接入网 ...
- 【SQL】 牛客网SQL训练Part2 中等难度
查找当前薪水详情以及部门编号dept_no 查找 1.各个部门当前领导的薪水详情以及其对应部门编号dept_no, 2.输出结果以salaries.emp_no升序排序, 3.并且请注意输出结果里面d ...
- 【Java】MultiThread 多线程 Re02 线程通讯
一.等待与唤醒 /** * 线程通讯问题 * Object wait, notify, notifyAll * Condition await signal signAll * CountDownLa ...
- 决定了,今日起开始准备弃用京东JD
估计京东是为了节约开支,然后开始大比例的把快递物流业务进行外包了,这直接导致服务质量的直线下滑,10多年前我选择弃用当当网而选择京东JD就是因为当时当地的当当网快递是用沈阳晚报的快递上门的,快递员连P ...