C# 13 和 .NET 9 全知道 :9 处理文件、流和序列化 (2)
liuian 2025-01-13 15:33 17 浏览
写入文本流
当您打开一个文件以进行读取或写入时,您使用的是 .NET 之外的资源。这些被称为非托管资源,完成使用后必须释放。
为了确定性地控制这些资源何时被释放,我们可以调用 Dispose 方法。当 Stream 类首次设计时,所有清理代码都预期放在 Close 方法中。但后来, IDisposable 的概念被添加到 .NET 中, Stream 必须实现一个 Dispose 方法。后来, using 语句被添加到 .NET 中,可以自动调用 Dispose 。因此,今天,您可以调用 Close 或 Dispose ,它们实际上执行相同的操作。
让我们输入一些代码将文本写入流:
- 使用您喜欢的代码编辑器向 Chapter09 解决方案添加一个新的控制台应用程序 / console 项目,命名为 WorkingWithStreams
- 在项目文件中,添加一个元素以静态和全局方式导入 System.Console 、 System.Environment 和 System.IO.Path 类。
- 添加一个名为 Program.Helpers.cs 的新类文件。
- 在 Program.Helpers.cs 中,添加一个部分 Program 类,包含一个 SectionTitle 和一个 OutputFileInfo 方法,如下代码所示:
// null namespace to merge with auto-generated Program.
partial class Program
{
private static void SectionTitle(string title)
{
ConsoleColor previousColor = ForegroundColor;
ForegroundColor = ConsoleColor.DarkYellow;
WriteLine(#34;*** {title} ***");
ForegroundColor = previousColor;
}
private static void OutputFileInfo(string path)
{
WriteLine("**** File Info ****");
WriteLine(#34;File: {GetFileName(path)}");
WriteLine(#34;Path: {GetDirectoryName(path)}");
WriteLine(#34;Size: {new FileInfo(path).Length:N0} bytes.");
WriteLine("/------------------");
WriteLine(File.ReadAllText(path));
WriteLine("------------------/");
}
}
- 添加一个名为 Viper.cs 的新类文件。
- 在 Viper.cs 中,定义一个名为 Viper 的静态类,包含一个名为 Callsigns 的 string 值的静态数组,如下代码所示:
namespace Packt.Shared;
public static class Viper
{
// Define an array of Viper pilot call signs.
public static string[] Callsigns = new[]
{
"Husker", "Starbuck", "Apollo", "Boomer",
"Bulldog", "Athena", "Helo", "Racetrack"
};
}
在 Program.cs 中,删除现有语句,然后导入命名空间以使用 Viper 类,如下代码所示:
using Packt.Shared; // To use Viper.
在 Program.cs 中,添加语句以列举 Viper 呼号,将每个呼号单独写在一行中,存储在一个文本文件中,如以下代码所示:
SectionTitle("Writing to text streams");
// Define a file to write to.
string textFile = Combine(CurrentDirectory, "streams.txt");
// Create a text file and return a helper writer.
StreamWriter text = File.CreateText(textFile);
// Enumerate the strings, writing each one to the stream
// on a separate line.
foreach (string item in Viper.Callsigns)
{
text.WriteLine(item);
}
text.Close(); // Release unmanaged file resources.
OutputFileInfo(textFile);
在流写入助手上调用 Close 将会在底层流上调用 Close 。这反过来会调用 Dispose 以释放非托管文件资源。
- 运行代码并查看结果,如下所示的输出:
**** File Info ****
File: streams.txt
Path: C:\cs13net9\Chapter09\WorkingWithStreams\bin\Debug\net9.0
Size: 68 bytes.
/------------------
Husker
Starbuck
Apollo
Boomer
Bulldog
Athena
Helo
Racetrack
------------------/
- 打开创建的文件,确认它包含呼号列表以及一个空行,因为我们实际上调用了 WriteLine 两次:一次是在将最后一个呼号写入文件时,另一次是在读取整个文件并将其输出到控制台时。
请记住,如果您在命令提示符下使用 dotnet run 运行项目,则路径将是项目文件夹。它将不包括 bin\Debug\net9.0 。
写入 XML 流
有两种方法可以编写 XML 元素,如下所示:
- WriteStartElement 和 WriteEndElement : 当一个元素可能有子元素时使用这一对。
- WriteElementString : 当一个元素没有子元素时使用此项。
现在,让我们尝试将 Viper 飞行员呼号数组的 string 值存储在 XML 文件中:
- 在 Program.cs 的顶部,导入 System.Xml 命名空间,如下代码所示:
using System.Xml; // To use XmlWriter and so on.
在 Program.cs 的底部,添加语句以列举呼号,将每个呼号作为单个 XML 文件中的一个元素,如以下代码所示:
SectionTitle("Writing to XML streams");
// Define a file path to write to.
string xmlFile = Combine(CurrentDirectory, "streams.xml");
// Declare variables for the filestream and XML writer.
FileStream? xmlFileStream = null;
XmlWriter? xml = null;
try
{
xmlFileStream = File.Create(xmlFile);
// Wrap the file stream in an XML writer helper and tell it
// to automatically indent nested elements.
xml = XmlWriter.Create(xmlFileStream,
new XmlWriterSettings { Indent = true });
// Write the XML declaration.
xml.WriteStartDocument();
// Write a root element.
xml.WriteStartElement("callsigns");
// Enumerate the strings, writing each one to the stream.
foreach (string item in Viper.Callsigns)
{
xml.WriteElementString("callsign", item);
}
// Write the close root element.
xml.WriteEndElement();
}
catch (Exception ex)
{
// If the path doesn't exist the exception will be caught.
WriteLine(#34;{ex.GetType()} says {ex.Message}");
}
finally
{
if (xml is not null)
{
xml.Close();
WriteLine("The XML writer's unmanaged resources have been disposed.");
}
if (xmlFileStream is not null)
{
xmlFileStream.Close();
WriteLine("The file stream's unmanaged resources have been disposed.");
}
}
OutputFileInfo(xmlFile);
可选地,在 xmlFileStream 的 Close 方法中右键单击,选择“转到实现”,并注意 Dispose 、 Close 和 Dispose(bool) 方法的实现,如以下代码所示:
public void Dispose() => Close();
public virtual void Close()
{
// When initially designed, Stream required that all cleanup logic
// went into Close(), but this was thought up before IDisposable
// was added and never revisited. All subclasses
// should put their cleanup now in Dispose(bool).
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Note: Never change this to call other virtual methods on Stream
// like Write, since the state on subclasses has already been
// torn down. This is the last code to run on cleanup for a stream.
}
Close 和 Dispose(bool) 方法在 Stream 类中是 virtual ,因为它们被设计为在派生类中被重写,例如 FileStream ,以完成释放非托管资源的工作。
- 运行代码并查看结果,如下所示的输出:
**** File Info ****
The XML writer's unmanaged resources have been disposed.
The file stream's unmanaged resources have been disposed.
File: streams.xml
Path: C:\cs13net9\Chapter09\WorkingWithStreams\bin\Debug\net9.0
Size: 320 bytes.
/------------------
<?xml version="1.0" encoding="utf-8"?>
<callsigns>
<callsign>Husker</callsign>
<callsign>Starbuck</callsign>
<callsign>Apollo</callsign>
<callsign>Boomer</callsign>
<callsign>Bulldog</callsign>
<callsign>Athena</callsign>
<callsign>Helo</callsign>
<callsign>Racetrack</callsign>
</callsigns>
-------------------/
良好实践:在调用 Dispose 方法之前,检查对象是否不是 null 。
通过使用 using 语句简化处置
您可以通过使用 using 语句来简化需要检查 null 对象并调用其 Dispose 方法的代码。除非您需要更高的控制级别,否则我通常建议使用 using 而不是手动调用 Dispose ,因为这样写的代码更少。
令人困惑的是, using 关键字有两种用法:导入命名空间和生成一个 finally 语句,该语句在实现了 IDisposable 的对象上调用 Dispose 。
编译器将 using 语句块转换为 try - finally 语句,而不使用 catch 语句。您可以使用嵌套的 try 语句,因此如果您确实想捕获任何异常,可以这样做,如以下代码示例所示:
using (FileStream file2 = File.OpenWrite(
Path.Combine(path, "file2.txt")))
{
using (StreamWriter writer2 = new StreamWriter(file2))
{
try
{
writer2.WriteLine("Welcome, .NET!");
}
catch(Exception ex)
{
WriteLine(#34;{ex.GetType()} says {ex.Message}");
}
} // Automatically calls Dispose if the object is not null.
} // Automatically calls Dispose if the object is not null.
您甚至可以通过不明确指定 using 语句的括号和缩进来进一步简化代码,如以下代码所示:
using FileStream file2 = File.OpenWrite(
Path.Combine(path, "file2.txt"));
using StreamWriter writer2 = new(file2);
try
{
writer2.WriteLine("Welcome, .NET!");
}
catch(Exception ex)
{
WriteLine(#34;{ex.GetType()} says {ex.Message}");
}
为了更清楚地说明这一点,让我们回顾一个更简单的例子。您可以使用一个 using 块来确保在作用域结束时调用 Dispose 方法,如以下代码所示:
using (ObjectWithUnmanagedResources thing = new())
{
// Statements that use thing.
}
更多信息:您可以通过以下链接了解前面的代码是如何降低到 try-catch 块的:https://github.com/markjprice/cs13net9/blob/main/docs/ch06-memory.md#ensuring-that-dispose-is-called。如果您还没有阅读,您还应该查看以下在线部分:https://github.com/markjprice/cs13net9/blob/main/docs/ch06-memory.md#releasing-unmanaged-resources。
您还可以使用不带大括号的简化语法,如以下代码所示:
using ObjectWithUnmanagedResources thing = new();
// Statements that use thing.
// Dispose called at the end of the container scope e.g. method.
在前面的代码示例中,没有通过大括号定义显式块,因此定义了一个隐式块,该块在包含作用域的末尾结束。
更多信息:您可以通过以下链接了解更多内容:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using 和 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#1314-the-using-statement。
压缩流
XML 相对冗长,因此它占用的字节空间比纯文本更多。让我们看看如何使用一种常见的压缩算法 GZIP 来压缩 XML。
在 .NET Core 2.1 中,微软引入了 Brotli 压缩算法的实现。在性能上,Brotli 类似于 DEFLATE 和 GZIP 中使用的算法,但输出密度约高 20%。
让我们比较这两种压缩算法:
- 添加一个名为 Program.Compress.cs 的新类文件。
- 在 Program.Compress.cs 中,编写语句使用 GZipStream 或 BrotliStream 的实例创建一个包含与之前相同的 XML 元素的压缩文件,然后在读取并输出到控制台时解压缩,如以下代码所示:
using Packt.Shared; // To use Viper.
using System.IO.Compression; // To use BrotliStream, GZipStream.
using System.Xml; // To use XmlWriter, XmlReader.
partial class Program
{
private static void Compress(string algorithm = "gzip")
{
// Define a file path using the algorithm as file extension.
string filePath = Combine(
CurrentDirectory, #34;streams.{algorithm}");
FileStream file = File.Create(filePath);
Stream compressor;
if (algorithm == "gzip")
{
compressor = new GZipStream(file, CompressionMode.Compress);
}
else
{
compressor = new BrotliStream(file, CompressionMode.Compress);
}
using (compressor)
{
using (XmlWriter xml = XmlWriter.Create(compressor))
{
xml.WriteStartDocument();
xml.WriteStartElement("callsigns");
foreach (string item in Viper.Callsigns)
{
xml.WriteElementString("callsign", item);
}
}
} // Also closes the underlying stream.
OutputFileInfo(filePath);
// Read the compressed file.
WriteLine("Reading the compressed XML file:");
file = File.Open(filePath, FileMode.Open);
Stream decompressor;
if (algorithm == "gzip")
{
decompressor = new GZipStream(
file, CompressionMode.Decompress);
}
else
{
decompressor = new BrotliStream(
file, CompressionMode.Decompress);
}
using (decompressor)
using (XmlReader reader = XmlReader.Create(decompressor))
while (reader.Read())
{
// Check if we are on an element node named callsign.
if ((reader.NodeType == XmlNodeType.Element)
&& (reader.Name == "callsign"))
{
reader.Read(); // Move to the text inside element.
WriteLine(#34;{reader.Value}"); // Read its value.
}
// Alternative syntax with property pattern matching:
// if (reader is { NodeType: XmlNodeType.Element,
// Name: "callsign" })
}
}
}
使用 decompressor 对象的代码没有使用简化的 using 语法。相反,它利用了 using 块可以省略其大括号以表示单个“语句”的事实,就像 if 语句一样。请记住, if 语句即使在块内只执行一个语句时也可以有显式的大括号,如以下代码所示:
if (c = 1)
{
// Execute a single statement.
}
if (c = 1)
// Execute a single statement.
using (someObject)
{
// Execute a single statement.
}
using (someObject)
// Execute a single statement
在前面的代码中, using (XmlReader reader = XmlReader.Create(decompressor)) 和整个 while (reader.Read()) { ... } 块等价于单个语句,因此我们可以去掉大括号,代码按预期工作。
- 在 Program.cs 中,添加对 Compress 的调用,使用 gzip 和 brotli 算法的参数,如以下代码所示:
SectionTitle("Compressing streams");
Compress(algorithm: "gzip");
Compress(algorithm: "brotli");
运行代码,并使用 gzip 和 brotli 算法比较 XML 文件和压缩 XML 文件的大小,如下所示的输出:
**** File Info ****
File: streams.gzip
Path: C:\cs13net9\Chapter09\WorkingWithStreams\bin\Debug\net9.0
Size: 151 bytes.
/------------------
-?
z?{??}En?BYjQqf~???????Bj^r~Jf^??RiI??????MrbNNqfz^1?i?QZ??Zd?@H?$%?&gc?t,
?????*????H?????t?&?d??%b??H?aUPbrjIQ"??b;????9
------------------/
Reading the compressed XML file:
Husker
Starbuck
Apollo
Boomer
Bulldog
Athena
Helo
Racetrack
**** File Info ****
File: streams.brotli
Path: C:\cs13net9\Chapter09\WorkingWithStreams\bin\Debug\net9.0
Size: 117 bytes.
/-------------------
??d?&?_????\@?Gm????/?h>?6????? ??^?__???wE?'?t<J??]??
???b?\fA?>?+??F??]
?T?\?~??A?J?Q?q6 ?-??
???
--------------------/
Reading the compressed XML file:
Husker
Starbuck
Apollo
Boomer
Bulldog
Athena
Helo
Racetrack
总结文件大小:
- 未压缩:320 字节
- GZIP 压缩:151 字节
- Brotli 压缩:117 字节
除了选择压缩模式外,您还可以选择压缩级别。您可以在以下链接了解更多信息:https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel。
随机访问句柄的读写
在.NET 的前 20 年中,唯一可以直接与文件交互的 API 是流类的 API。这些 API 非常适合只需顺序处理数据的自动化任务。但是,当人类与数据交互时,他们通常希望跳转并多次返回到同一位置。
随着 .NET 6 及更高版本的发布,出现了一种新的 API,可以以随机访问的方式处理文件,而无需文件流。让我们看一个简单的例子:
- 使用您喜欢的代码编辑器向 Chapter09 解决方案添加一个名为 WorkingWithRandomAccess 的新控制台应用程序 / console 项目:
- 在项目文件中,添加一个元素以静态和全局方式导入 System.Console 类。
- 在 Program.cs 中,删除现有语句,然后获取名为 coffee.txt 的文件的句柄,如以下代码所示:
using Microsoft.Win32.SafeHandles; // To use SafeFileHandle.
using System.Text; // To use Encoding.
using SafeFileHandle handle =
File.OpenHandle(path: "coffee.txt",
mode: FileMode.OpenOrCreate,
access: FileAccess.ReadWrite);
编写一些编码为字节数组的文本,然后将其存储在只读内存缓冲区中到文件,如以下代码所示:
string message = "Café £4.39";
ReadOnlyMemory<byte> buffer = new(Encoding.UTF8.GetBytes(message));
await RandomAccess.WriteAsync(handle, buffer, fileOffset: 0);
要从文件中读取,获取文件的长度,使用该长度分配一个内存缓冲区以存放内容,然后读取文件,如以下代码所示:
long length = RandomAccess.GetLength(handle);
Memory<byte> contentBytes = new(new byte[length]);
await RandomAccess.ReadAsync(handle, contentBytes, fileOffset: 0);
string content = Encoding.UTF8.GetString(contentBytes.ToArray());
WriteLine(#34;Content of file: {content}");
运行代码,并注意文件的内容,如下所示的输出:
Content of file: Café £4.39
编码和解码文本
文本字符可以以不同方式表示。例如,字母表可以使用摩尔斯电码编码为一系列点和划,以便通过电报线路传输。
以类似的方式,计算机内部的文本以位(零和一)存储,表示代码空间中的代码点。大多数代码点表示单个字符,但它们也可以具有其他含义,例如格式化。
例如,ASCII 具有 128 个代码点的编码空间。.NET 使用一种称为 Unicode 的标准在内部编码文本。Unicode 具有超过 100 万个代码点。
有时,您需要将文本移出 .NET,以便供不使用 Unicode 或其变体的系统使用,因此学习如何在编码之间转换是很重要的。
一些计算机常用的文本编码如表 9.7 所示:
编码 | 描述 |
ASCII | 这使用字节的低 7 位编码了一定范围的字符。 |
UTF-8 | 这表示每个 Unicode 代码点作为 1 到 4 字节的序列。 |
UTF-7 | 这被设计为在 7 位通道上比 UTF-8 更高效,但它存在安全性和稳健性问题,因此推荐使用 UTF-8 而不是 UTF-7。 |
UTF-16 | 这表示每个 Unicode 代码点作为一个或两个 16 位整数的序列。 |
UTF-32 | 这将每个 Unicode 代码点表示为一个 32 位整数,因此是一种固定长度的编码,不同于其他 Unicode 编码,它们都是可变长度的编码。 |
ANSI/ISO 编码 | 这提供了对多种代码页的支持,这些代码页用于支持特定语言或语言组。 |
表 9.7:常见文本编码
良好实践:在大多数情况下,UTF-8 是一个良好的默认选项,这就是它被字面上视为默认编码的原因,即 Encoding.Default 。您应该避免使用 Encoding.UTF7 ,因为它不安全。因此,当您尝试使用 UTF-7 时,C# 编译器会警告您。当然,您可能需要使用该编码生成文本以与另一个系统兼容,因此它需要在 .NET 中保持为一个选项。
将字符串编码为字节数组
让我们探索文本编码:
- 使用您喜欢的代码编辑器向 Chapter09 解决方案添加一个新的控制台应用程序 / console 项目,命名为 WorkingWithEncodings 。
- 在项目文件中,添加一个元素以静态和全局导入 System.Console 类。
- 在 Program.cs 中,删除现有语句,导入 System.Text 命名空间,添加语句以使用用户选择的编码对 string 进行编码,循环遍历每个字节,然后将编码值解码回 string 并输出,如以下代码所示:
using System.Text; // To use Encoding.
WriteLine("Encodings");
WriteLine("[1] ASCII");
WriteLine("[2] UTF-7");
WriteLine("[3] UTF-8");
WriteLine("[4] UTF-16 (Unicode)");
WriteLine("[5] UTF-32");
WriteLine("[6] Latin1");
WriteLine("[any other key] Default encoding");
WriteLine();
Write("Press a number to choose an encoding.");
ConsoleKey number = ReadKey(intercept: true).Key;
WriteLine(); WriteLine();
Encoding encoder = number switch
{
ConsoleKey.D1 or ConsoleKey.NumPad1 => Encoding.ASCII,
ConsoleKey.D2 or ConsoleKey.NumPad2 => Encoding.UTF7,
ConsoleKey.D3 or ConsoleKey.NumPad3 => Encoding.UTF8,
ConsoleKey.D4 or ConsoleKey.NumPad4 => Encoding.Unicode,
ConsoleKey.D5 or ConsoleKey.NumPad5 => Encoding.UTF32,
ConsoleKey.D6 or ConsoleKey.NumPad6 => Encoding.Latin1,
_ => Encoding.Default
};
// Define a string to encode
string message = "Café £4.39";
WriteLine(#34;Text to encode: {message} Characters: {message.Length}.");
// Encode the string into a byte array.
byte[] encoded = encoder.GetBytes(message);
// Check how many bytes the encoding needed.
WriteLine("{0} used {1:N0} bytes.",
encoder.GetType().Name, encoded.Length);
WriteLine();
// Enumerate each byte.
WriteLine("BYTE | HEX | CHAR");
foreach (byte b in encoded)
{
WriteLine(#34;{b,4} | {b,3:X} | {(char)b,4}");
}
// Decode the byte array back into a string and display it.
string decoded = encoder.GetString(encoded);
WriteLine(#34;Decoded: {decoded}");
运行代码,按 1 选择 ASCII,并注意在输出字节时,英镑符号 ( £ ) 和带重音的 e ( é ) 不能用 ASCII 表示,因此使用问号代替:
Text to encode: Café £4.39 Characters: 10
ASCIIEncodingSealed used 10 bytes.
BYTE | HEX | CHAR
67 | 43 | C
97 | 61 | a
102 | 66 | f
63 | 3F | ?
32 | 20 |
63 | 3F | ?
52 | 34 | 4
46 | 2E | .
51 | 33 | 3
57 | 39 | 9
Decoded: Caf? ?4.39
重新运行代码并按 3 选择 UTF-8。请注意,UTF-8 需要为每个需要 2 个字节的两个字符额外增加 2 个字节(总共 12 个字节而不是 10 个字节),但它可以编码和解码 é 和 £ 字符:
Text to encode: Café £4.39 Characters: 10
UTF8EncodingSealed used 12 bytes.
BYTE | HEX | CHAR
67 | 43 | C
97 | 61 | a
102 | 66 | f
195 | C3 | ?
169 | A9 | ?
32 | 20 |
194 | C2 | ?
163 | A3 | £
52 | 34 | 4
46 | 2E | .
51 | 33 | 3
57 | 39 | 9
Decoded: Café £4.39
- 重新运行代码并按 4 选择 Unicode(UTF-16)。请注意,UTF-16 每个字符需要 2 个字节,因此总共需要 20 个字节,并且它可以编码和解码 é 和 £ 字符。此编码在.NET 内部用于存储 char 和 string 值。
在文件中编码和解码文本
在使用流助手类时,例如 StreamReader 和 StreamWriter ,您可以指定要使用的编码。当您写入助手时,文本将自动编码,而当您从助手读取时,字节将自动解码。
要指定编码,请将编码作为第二个参数传递给辅助类型的构造函数,如以下代码所示:
StreamReader reader = new(stream, Encoding.UTF8);
StreamWriter writer = new(stream, Encoding.UTF8);
良好实践:通常情况下,您无法选择使用哪种编码,因为您将生成一个供其他系统使用的文件。然而,如果可以选择,请选择一种使用最少字节但能够存储您所需的每个字符的编码。
序列化对象图
对象图是多个相互关联的对象的结构,这些对象可以通过直接引用或通过一系列引用间接关联。
序列化是将实时对象图转换为使用指定格式的字节序列的过程。反序列化是相反的过程。
您可以使用序列化来保存实时对象的当前状态,以便将来可以重新创建它,例如,保存游戏的当前状态,以便您明天可以在同一位置继续。来自序列化对象的流通常存储在文件或数据库中。
有几十种格式可以选择进行序列化,但最常见的两种基于文本的人类可读格式是可扩展标记语言(XML)和 JavaScript 对象表示法(JSON)。还有更高效的二进制格式,如 Protobuf,gRPC 使用的就是这种格式。
良好实践:JSON 更紧凑,最适合用于网页和移动应用。XML 更冗长,但在更多遗留系统中支持更好。使用 JSON 来最小化序列化对象图的大小。当将对象图发送到网页应用和移动应用时,JSON 也是一个不错的选择,因为它是 JavaScript 的原生序列化格式,而移动应用通常在带宽有限的情况下进行调用,因此字节数非常重要。
.NET 有多个类可以进行 XML 和 JSON 的序列化和反序列化。我们将首先查看 XmlSerializer 和 JsonSerializer 。
相关推荐
- vue怎么和后端php配合
-
Vue和后端PHP可以通过HTTP请求进行配合。首先,前端Vue可以使用axios库或者Vue自带的$http对象来发送HTTP请求到后端PHP接口。通过axios库发送POST、GET、PUT等请求...
- Ansible最佳实践之 AWX 使用 Ansible 与 API 通信
-
#头条创作挑战赛#API简单介绍红帽AWX提供了一个类似Swagger的RESTful风格的Web服务框架,可以和awx直接交互。使管理员和开发人员能够在webUI之外控制其...
- PHP8.3 错误处理革命:Exception 与 Error 全面升级
-
亲爱的小伙伴,好久没有发布信息了,最近学习了一下PHP8.3的升级,都有哪些优化和提升,把学到的分享出来给需要的小伙伴充下电。技术段位:高可用性必修目标收益:精准错误定位+异常链路追踪适配场景...
- 使用 mix/vega + mix/db 进行现代化的原生 PHP 开发
-
最近几年在javascript、golang生态中游走,发现很多npm、gomod的优点。最近回过头开发MixPHPV3,发现composer其实一直都是一个非常优秀的工具,但是...
- 15 个非常好用的 JSON 工具
-
JSON(JavaScriptObjectNotation)是一种流行的数据交换格式,已经成为许多应用程序中常用的标准。无论您是开发Web应用程序,构建API,还是处理数据,使用JSON工具可以大...
- php8环境原生实现rpc
-
大数据分布式架构盛行时代的程序员面试,常常遇到分布式架构,RPC,本文的主角是RPC,英文名为RemoteProcedureCall,翻译过来为“远程过程调用”。主流的平台中都支持各种远程调用技术...
- 「PHP编程」如何搭建私有Composer包仓库?
-
在前一篇文章「PHP编程」如何制作自己的Composer包?中,我们已经介绍了如何制作自己的composer包,以及如何使用composer安装自己制作的composer包。不过,这其中有...
- WAF-Bypass之SQL注入绕过思路总结
-
过WAF(针对云WAF)寻找真实IP(源站)绕过如果流量都没有经过WAF,WAF当然无法拦截攻击请求。当前多数云WAF架构,例如百度云加速、阿里云盾等,通过更改DNS解析,把流量引入WAF集群,流量经...
- 【推荐】一款 IDEA 必备的 JSON 处理工具插件 — Json Assistant
-
JsonAssistant是基于IntelliJIDEs的JSON工具插件,让JSON处理变得更轻松!主要功能完全支持JSON5JSON窗口(多选项卡)选项卡更名移动至主编辑器用...
- 技术分享 | 利用PHAR协议进行PHP反序列化攻击
-
PHAR(“PhpARchive”)是PHP中的打包文件,相当于Java中的JAR文件,在php5.3或者更高的版本中默认开启。PHAR文件缺省状态是只读的,当我们要创建一个Phar文件需要修改...
- php进阶到架构之swoole系列教程(一)windows安装swoole
-
目录概述安装Cygwin安装swoolephp7进阶到架构师相关阅读概述这是关于php进阶到架构之swoole系列学习课程:第一节:windows安装swoole学习目标:在Windows环境将搭建s...
- go 和 php 性能如何进行对比?
-
PHP性能很差吗?每次讲到PHP和其他语言间的性能对比,似乎都会发现这样一个声音:单纯的性能对比没有意义,主要瓶颈首先是数据库,其次是业务代码等等。好像PHP的性能真的不能单独拿出来讨论似的。但其实一...
- Linux(CentOS )手动搭建LNMP(Linux+Nginx+Mysql+PHP)坏境
-
CentOS搭建LNMP(Linux+Nginx+Mysql+PHP)坏境由于网上各种版本新旧不一,而且Linux版本也不尽相同,所以自己写一遍根据官网的提示自己手动搭建过程。看官方文档很重要,永远...
- json和jsonp区别
-
JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种非官方跨域数据交互协议。一个是描述信息的格式,一个是信息传递的约定方法。一、...
- web后端正确的返回JSON
-
在web开发中,前端和后端发生数据交换传输现在最常见的形式就是异步ajax交互,一般返回给js都是json,如何才是正确的返回呢?前端代码想要获取JSON数据代码如下:$.get('/user-inf...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
python使用fitz模块提取pdf中的图片
-
《人人译客》如何规划你的移动电商网站(2)
-
Jupyterhub安装教程 jupyter怎么安装包
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- uniapp textarea (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- react-admin (33)
- vscode切换git分支 (35)
- vscode美化代码 (33)
- python bytes转16进制 (35)