windows 稀疏文件 (sparse file) 的一个实用场景——解决 SetEndOfFile 占据磁盘空间引入的性能问题
前言
之前写过一篇文章说明文件空洞:《[apue] 文件中的空洞》,其中提到了 windows 稀疏文件是制造空洞的一种方式,但似乎没什么用处,如果仅仅处理占用磁盘空间的场景,使用SetEndOfFile
就足够了。
后来在实际工作中,发现稀疏文件在解决一个性能问题方面,有着不可替代的作用,下面且听我一一道来。
问题现象
公司的文件下载产品,为了预防在下载过程中因磁盘空间不足而失败,在 windows 上采取预分配的策略,在下载任务开始前就占据了相当于文件长度的磁盘空间。由于数据源也包括从 P2P 处获取的数据,导致写入时并不是顺序的,存乱序写入的情况,这些信息都存储在数据库中,当应用重启时,会从数据库加载块信息,继续对未落盘的块进行网络请求。
整个逻辑看起来没有问题,然而实测在距离当前写入位置较远的地方写入块时,会发现落盘速度极慢。
下面用一个简单的例子验证这一点,这个 demo 创建一个大文件,通常十几个 GB,具体长度由用户输入决定:
#include <iostream>
#include <Windows.h>
int main(int argc, char* argv[])
{
if (argc < 3)
{
std::cout << "Usage: sparsefile file length (in GB)\n";
return 1;
}
int ret = 0;
HANDLE file_handle = INVALID_HANDLE_VALUE;
LARGE_INTEGER pos = { 0 };
do
{
file_handle = CreateFileA(argv[1], (GENERIC_READ | GENERIC_WRITE),
FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
if (file_handle == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFile failed, error " << GetLastError() << std::endl;
ret = 2;
break;
}
pos.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024; // unit in GB
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 3;
break;
}
if (!::SetEndOfFile(file_handle))
{
std::cout << "SetEndOfFile failed, error " << GetLastError() << std::endl;
ret = 4;
break;
}
} while (0);
if (ret == 0)
std::cout << "create file with length " << pos.QuadPart << " success, path " << argv[1] << std::endl;
if (file_handle)
CloseHandle(file_handle);
return ret;
}
执行以下命令创建文件:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 10
SetEndOfFile failed, error 112
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967296 success, path movie.mp4
分区 D: 目前仅有 5GB 空间,所以创建 10GB 的文件会失败,错误码 112 即磁盘空间不足;创建 4GB 的可以成功:
文件属性中,文件大小与占用空间是一致的,没有空洞。
在完成预分配后,立即在尾部写入 1 个字符,以模拟真实的使用场景:
...
pos.QuadPart--;
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 30;
break;
}
DWORD bytes = 0;
c_timer t;
if (!::WriteFile(file_handle, " ", 1, &bytes, NULL) || bytes != 1)
{
std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
ret = 31;
break;
}
int elapse = t.get_interval();
std::cout << "write file elapse " << elapse << " ms" << std::endl;
...
注意需要前移文件指针,并在写入前后记录耗时,c_timer 封装了 windows 高精度计时器,可以精确到毫秒。下面是程序输出:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 19502 ms
create file with length 4294967295 success, path movie.mp4
写入 4G 文件末尾 1 个字节,消耗了将近 20s,这还是 SSD,如果换作机械硬盘,耗时会更久。这就是 SetEndOfFile
占据磁盘空间产生的性能问题。
问题分析
网上搜索了一番,大概明白这个 20s 耗时是怎么回事了,简单说明一下:当使用 SetEndOfFile
预分配磁盘空间时,文件系统会将原始块分配给文件,这些块可能包含用户之前删除的文件数据,如果不写入新内容就读取,可能会读到用户隐私!黑客完全可以利用这个漏洞盗取用户数据,从而导致严重的安全问题。
为此,windows 在应用写入原始块时,会用零填充文件指针到上次写入位置之间的区域 (没有上次写入位置就从文件头开始),保证这些内容被清空,从而避免原始信息泄漏。上例中的耗时大部分时间用于填充整个 4GB 文件了,速度当然快不起来。
解决方案
目前有两种解决方案,下面分别说明
提权 + SetFileValidData
这种就是官方提出的解决方案,其实是给 service 打的补丁,MSDN 上写的很详细了:
You can use the SetFileValidData function to create large files in very specific circumstances so that the performance of subsequent file I/O can be better than other methods. Specifically, if the extended portion of the file is large and will be written to randomly, such as in a database type of application, the time it takes to extend and write to the file will be faster than using SetEndOfFile and writing randomly. In most other situations, there is usually no performance gain to using SetFileValidData, and sometimes there can be a performance penalty.
需要注意的是,这个函数需要用户拥有 SeManageVolumePrivilege
权限。以管理员身份运行时,默认是没有这个权限的:
D:\test\sparsefile\Release>whoami /priv
特权信息
----------------------
特权名 描述 状态
========================================= ================================== ======
SeIncreaseQuotaPrivilege 为进程调整内存配额 已禁用
SeMachineAccountPrivilege 将工作站添加到域 已禁用
SeSecurityPrivilege 管理审核和安全日志 已禁用
SeTakeOwnershipPrivilege 取得文件或其他对象的所有权 已禁用
SeLoadDriverPrivilege 加载和卸载设备驱动程序 已禁用
SeSystemProfilePrivilege 配置文件系统性能 已禁用
SeSystemtimePrivilege 更改系统时间 已禁用
SeProfileSingleProcessPrivilege 配置文件单一进程 已禁用
SeIncreaseBasePriorityPrivilege 提高计划优先级 已禁用
SeCreatePagefilePrivilege 创建一个页面文件 已禁用
SeBackupPrivilege 备份文件和目录 已禁用
SeRestorePrivilege 还原文件和目录 已禁用
SeShutdownPrivilege 关闭系统 已禁用
SeDebugPrivilege 调试程序 已禁用
SeSystemEnvironmentPrivilege 修改固件环境值 已禁用
SeChangeNotifyPrivilege 绕过遍历检查 已启用
SeRemoteShutdownPrivilege 从远程系统强制关机 已禁用
SeUndockPrivilege 从扩展坞上取下计算机 已禁用
SeManageVolumePrivilege 执行卷维护任务 已禁用
SeImpersonatePrivilege 身份验证后模拟客户端 已启用
SeCreateGlobalPrivilege 创建全局对象 已启用
SeIncreaseWorkingSetPrivilege 增加进程工作集 已禁用
SeTimeZonePrivilege 更改时区 已禁用
SeCreateSymbolicLinkPrivilege 创建符号链接 已禁用
SeDelegateSessionUserImpersonatePrivilege 获取同一会话中另一个用户的模拟令牌 已禁用
为此需要给进程提权:
...
if (!EnablePrivilege(SE_MANAGE_VOLUME_NAME, TRUE))
{
std::cout << "EnablePrivilege failed, error " << GetLastError() << std::endl;
ret = 10;
break;
}
...
EnablePrivilege
封装了 OpenProcessToken
、LookupPrivilegeValue
、AdjustTokenPrivileges
几个调用,文章末尾有完整的代码实现,提权代码需要放在CreateFile
之前。接着设置文件有效数据长度:
...
if (!::SetFileValidData(file_handle, pos.QuadPart))
{
std::cout << "SetFileValidData failed, error " << GetLastError() << std::endl;
ret = 10;
break;
}
...
这段代码需要插入到 SetEndOfFile
与 WriteFile
之间。以管理员身份启动控制台后有如下输出:
D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
write file elapse 0 ms
create file with length 4294967295 success, path movie.mp4
尾部写入数据的耗时大大降低了。如果不以管理员身份启动,会报下面的错误:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
EnablePrivilege failed, error 1300
提权失败。如果未提权或 EnablePrivilege
位于 CreateFile
之后,则报下面的错误:
D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
SetFileValidData failed, error 1314
权限不足。
稀疏文件
如果无法获取管理员身份进行提权,则需要借助 NTFS 稀疏文件,在 WriteFile
之前加入下面的代码即可:
...
DWORD temp = 0;
if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
{
std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
ret = 12;
break;
}
...
以普通用户身份运行:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 0 ms
create file with length 4294967295 success, path movie.mp4
耗时也大大降低了。稀疏文件依赖于文件系统的支持,可查询某个 Volume 是否支持稀疏文件:
CHAR szVolName[MAX_PATH], szFsName[MAX_PATH];
DWORD dwSN, dwFSFlag, dwMaxLen, nWritten;
BOOL bSuccess;
HANDLE hFile;
bSuccess = GetVolumeInformation(NULL,
szVolName,
MAX_PATH,
&dwSN,
&dwMaxLen,
&dwFSFlag,
szFsName,
MAX_PATH);
if (!bSuccess)
{
printf("errno:%d", GetLastError());
return -1;
}
printf("vol name:%s \t fs name:%s sn: %d.\n", szVolName, szFsName, dwSN);
if (dwFSFlag & FILE_SUPPORTS_SPARSE_FILES)
printf("support sparse file.\n");
else
printf("no support sparse file.\n");
或查询某个文件是否为稀疏文件:
// HANDLE hFile;
BY_HANDLE_FILE_INFORMATION stFileInfo;
GetFileInformationByHandle(hFile, &stFileInfo);
if(stFileInfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)
printf("is sparse file.\n");
else
printf("no sparse file.\n");
也可以通过 fsutil 命令快速确认:
PS D:\test\sparsefile\Release> fsutil.exe sparse
---- 支持 SPARSE 命令 ----
queryflag 查询稀疏
queryrange 查询范围
setflag 设置稀疏
setrange 设置稀疏范围
PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
该文件被设为稀疏
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0xffff0000 长度: 0x10000
fsutil 的 sparse 子命令查询文件是否稀疏 (queryflag)、以及有效数据的范围 (queryrange)。
这里 queryrange 返回的长度 0x10000 对应的空间占用是 64KB,查看文件属性:
也是 64KB。看起来即使写入 1 个字节,windows 也会分配一个 64K 的块并将其标记为修改。
带来的新问题
两种方案相比较,稀疏文件方式无需获取管理员权限,看起来似乎更“亲民”一些,不过也有它自己的问题:无法真正占据磁盘空间。考察上例中 4GB 文件的属性,占用空间仅 64KB,此时再生成一个 4GB 的文件,仍能成功。然而在实际写入过程中,注定有一个文件因磁盘空间不足而失败,甚至两个都失败。修改 demo 以演示这个场景:
...
long long file_size = pos.QuadPart;
pos.QuadPart = 0;
if (::SetFilePointerEx(file_handle, pos, NULL, FILE_BEGIN) == 0)
{
std::cout << "SetFilePointerEx failed, error " << GetLastError() << std::endl;
ret = 20;
break;
}
char buf[4096] = { 1 };
c_timer t;
DWORD bytes = 0;
for (int i = 0; i < file_size; i += 4096)
{
if (!::WriteFile(file_handle, buf, 4096, &bytes, NULL) || bytes != 4096)
{
std::cout << "WriteFile failed, error " << GetLastError() << ", written " << bytes << std::endl;
ret = 21;
break;
}
}
int elapse = t.get_interval();
std::cout << "write file elapse " << elapse << " ms" << std::endl;
...
写入整个文件,每次 4KB,执行此程序的同时,通过 dd 开启另外一个 4GB 文件的写入:
$ dd if=/dev/zero of=./movie1.mp4 bs=1M count=1024
dd: error writing './movie1.mp4': No space left on device
335+0 records in
334+0 records out
350224384 bytes (350 MB, 334 MiB) copied, 2.08525 s, 168 MB/s
因磁盘剩余空间不足 8GB,最终 dd & demo 都会报错退出:
PS D:\test\sparsefile\Release>.\sparsefile movie.mp4 4
WriteFile failed, error 112, written 0
write file elapse 16420 ms
这表明稀疏文件即使占据了空间,也会受磁盘实际剩余空间的影响,真是占了个寂寞!
换句话,稀疏文件使 SetEndOfFile
的磁盘空间占用能力"消失"了,不过后者的剩余空间检查能力还在。当剩余空间不足 4GB 时,demo 会直接在 SetEndOfFile
处失败:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
SetEndOfFile failed, error 112
所以上例不能采用 dd 预先写入 4GB 的方式进行测试,两个程序必需一先一后启动。
行文至此,正好验证一个说法:windows 稀疏文件会对零进行压缩,从而节省空间占用。如果这种说法为真,当写入的数据也是零,稀疏文件占用的空间也会大大小于文件大小,实际情况会怎样?修改一行代码进行验证:
...
char buf[4096] = { 0 };
...
将缓存区默认值从 1 改为 0,此时写入的数据全为零:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
write file elapse 11690 ms
create file with length 0 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没有任何空洞,查看文件属性:
确实如此。这个实验说明:稀疏文件并不是对零进行压缩,而是标记了哪些块有写入,从而记录有有效数据区间,和 linux ext4 稀疏文件的实现应该是异曲同工的。
方案对比
总结一下目前的两个方案的缺点
- SetFileValidData:需要提权
- 稀疏文件:无法占据磁盘空间
看起来都挺致命的,难道就没有十全十美的方案了吗?在一次偶然的测试中,发现稀疏文件也能占据空间,只要关闭稀疏文件尾部 1 字节的写入:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967295 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没有文件空洞,查看文件属性:
这样看起来能占据磁盘空间了?使用 dd 灌一些数据:
$ dd if=/dev/zero of=./movie.mp4 bs=1M count=1 seek=4095
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.0284056 s, 36.9 MB/s
在 4095M 位置写入 1M,结果露馅了:
看来还是占不了空间,之前显示空间占用 4G 是虚的靠不住的。
新的探索
看了下开源的种子下载神器 Transmission,当它采用 prealloc 模式时,在 windows 上底层使用的就是稀疏文件方式:
bool tr_sys_file_preallocate(tr_sys_file_t handle, uint64_t size, int flags, tr_error** error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
if ((flags & TR_SYS_FILE_PREALLOC_SPARSE) != 0)
{
DWORD tmp;
if (!DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &tmp, nullptr))
{
set_system_error(error, GetLastError());
return false;
}
}
return tr_sys_file_truncate(handle, size, error);
}
不过在占用空间方面,它使用的是 SetFileInformationByHandle
:
bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error** error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
FILE_END_OF_FILE_INFO info;
info.EndOfFile.QuadPart = size;
bool ret = SetFileInformationByHandle(handle, FileEndOfFileInfo, &info, sizeof(info));
if (!ret)
{
set_system_error(error, GetLastError());
}
return ret;
}
这个和 SetEndOfFile
有何区别,修改代码进行验证:
...
FILE_END_OF_FILE_INFO info;
info.EndOfFile.QuadPart = atoll(argv[2]) * 1024 * 1024 * 1024; // unit in GB
if (!SetFileInformationByHandle(file_handle, FileEndOfFileInfo, &info, sizeof(info)))
{
std::cout << "SetFileInformationByHandle failed, error " << GetLastError() << std::endl;
ret = 5;
break;
}
DWORD temp = 0;
if (!::DeviceIoControl(file_handle, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &temp, NULL))
{
std::cout << "DeviceIoControl failed, error " << GetLastError() << std::endl;
ret = 6;
break;
}
...
这段代码替换整个 SetEndOfFile
及之后的代码。再次运行 demo:
PS D:\test\sparsefile\Release> .\sparsefile movie.mp4 4
create file with length 4294967296 success, path movie.mp4
PS D:\test\sparsefile\Release> fsutil.exe sparse queryflag .\movie.mp4
该文件被设为稀疏
PS D:\test\sparsefile\Release> fsutil.exe sparse queryrange .\movie.mp4
分配的范围[1]: 偏移: 0x0 长度: 0x100000000
看起来没空洞了,但用 dd 测下尾部写入 1MB 后,结果就和之前一样了:
看起来没什么改善。
结语
本文尝试说明 SetEndOfFile
占用空间存在的一个性能问题,并讲解了两种解决方案,分别是SetFileValidData
和稀疏文件,以及它们的局限性;随后尝试破解稀疏文件局限性但失败了;最后验证了 Transmission 开源库基于SetFileInformationByHandle
的方案也不可行。
附录中罗列了一些如何高效的拷贝稀疏文件的方法,关键在于遍历稀疏文件中的有效数据区间,感兴趣的读者可以参考附录 8。
代码
本期测试代码上传到了 github:https://github.com/goodpaperman/sparsefile
各种接口的调用尽量做成了选项,方便组合进行测试,参数不足时会展示 Usage:
PS D:\test\sparsefile\Release> .\sparsefile.exe movie.mp4
Usage: sparsefile file length(in GB) [set-file-end-of-file-info] [set-file-valid-data] [sparse-file] [write-file-mode 0|1|2] [fill-char]
文件路径和大小是必选项,5 个可选项分别控制:
set-file-end-of-file-info
:使用SetFileInformationByHandle
方式,默认为 0 使用SetEndOfFile
方式set-file-valid-data
:使用SetFileValidData
方式,此时需要以管理员身份启动控制台,默认为 0sparse-file
:使用稀疏文件,默认为 1write-file-mode
:写文件模式,默认为 0
- 0:不写
- 1:写文件末尾 1 字节
- 2:间隔 1M 写 4KB 数据
fill-char
:写文件时填充的字符,默认为空 ('')
通过设置参数,可以验证本文的各种方案:
.\sparsefile movie.mp4 4
,SetEndOfFile
+ 稀疏文件 的方式.\sparsefile movie.mp4 4 0 0 1 1 ' '
,在末尾写入数据耗时小,且空间占用失败.\sparsefile movie.mp4 4 0 0 1 1
,末尾写入零时对空间占用无影响.\sparsefile movie.mp4 4 0 0 1 2
,写入全零块时文件占用空间并未压缩
.\sparsefile movie.mp4 4 0 0 0
,仅SetEndOfFile
的方式.\sparsefile movie.mp4 4 0 0 0 1 ' '
,在末尾写入数据耗时大.\sparsefile movie.mp4 4 0 0 0 1
,在末尾写入零耗时小
.\sparsefile movie.mp4 4 1
,SetFileInformationByHandle
+ 稀疏文件 的方式.\sparsefile movie.mp4 4 1 0 1 1 ' '
,在末尾写入数据耗时小,且空间占用失败
.\sparsefile movie.mp4 4 1 0 0
,仅SetFileInformationByHandle
的方式.\sparsefile movie.mp4 4 1 0 0 1 ' '
,在末尾写入数据耗时大
.\sparsefile movie.mp4 4 0 1 0
,SetEndOfFile
+SetFileValidData
+ 提权 的方式.\sparsefile movie.mp4 4 0 1 0 1 ' '
,在末尾写入数据耗时小,且空间占用成功
可执行文件已经编译为了静态链接并上传到 git,理论上不需要装 VS 也能运行,配置是 Release x86 & x64 两个平台,方便没有编译环境的同学直接上手。
参考
[1]. 什么是稀疏文件(Sparse File)
[2]. 建希文件
[3]. windows 高精度计时器
[4]. Windows环境下提升进程的权限
[5]. SetFileValidData function
[6]. Windows 下的文件预分配与 SetFileValidData 函数
[8]. 稀疏文件简介
windows 稀疏文件 (sparse file) 的一个实用场景——解决 SetEndOfFile 占据磁盘空间引入的性能问题的更多相关文章
- 第17章 内存映射文件(3)_稀疏文件(Sparse File)
17.8 稀疏调拨的内存映射文件 17.8.1 稀疏文件简介 (1)稀疏文件(Sparse File):指的是文件中出现大量的0数据,这些数据对我们用处不大,但是却一样的占用空间.NTFS文件系统对此 ...
- 用windows自带的fsutil来创建1G稀疏文件(sparse file)
fsutils file createnew a.dat 1073741824 fsutil sparse setflag a.dat fsutil sparse setrange a.dat 0 ...
- mongo 固定集合,大文件存储,简单优化 + 三招解决MongoDB的磁盘IO问题
1.固定集合 > db.createCollection(, max:});//固定集合 必须 显式创建. 设置capped为true, 集合总大小xxx字节, [集合中json个数max] { ...
- 安装Windows SDK7.1时发生的一个错误(附解决办法)
A problem occurred while installing selected Windows SDK components. Installation of the "Micro ...
- linux查找系统中占用磁盘空间最大的文件
Q:下午有一客户磁盘空间占用很大,使用df查看磁盘剩余空间很小了,客户想知道是哪些文件占满了文件. Q1:在Linux下如何查看系统占用磁盘空间最大的文件? Q2:在Linux下如何让文件夹下的文件让 ...
- SQL Server ->> Sparse File(稀疏文件)
Sparse File(稀疏文件)不是SQL Server的特性.它属于Windows的NTFS文件系统的一个特性.如果某个大文件中的数据包含着大量“0数据”(这个应该从二进制上看),这样的文件就可以 ...
- 背水一战 Windows 10 (98) - 关联启动: 使用外部程序打开一个文件, 使用外部程序打开一个 Uri
[源码下载] 背水一战 Windows 10 (98) - 关联启动: 使用外部程序打开一个文件, 使用外部程序打开一个 Uri 作者:webabcd 介绍背水一战 Windows 10 之 关联启动 ...
- 无法打开包括文件:“windows.h”: No such file or directory
VS2012 出现如下错误: 无法打开包括文件:"windows.h": No such file or directory 解决办法,将 C:\Program Files ...
- 【解决】 无法打开包括文件:“windows.h”: No such file or directory
vs编译时错误: 无法打开包括文件:“windows.h”: No such file or directory 出现这种错误什么都不用配置(环境变量),最好办法是将VS安装在C盘,让开发工具自动包含 ...
- Java基础面试操作题: File IO 文件过滤器FileFilter 练习 把一个文件夹下的.java文件复制到另一个文件夹下的.txt文件
package com.swift; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File ...
随机推荐
- UML之属性与参数的多重性
在UML中,多重性是指一个条目潜在的数量范围.多重性可被用于属性.操作参数.关联关系.UML元模型也使用多重性对元模型元素之间的关系进行约束.多重性总是包含基数值,它是相关条目在现实世界中的确切数量. ...
- [rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(01):为窗口设置布局(column、row)
前言 本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发. iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动. 本合集是基于新版本的关于分 ...
- redis-cluster 集群增加节点
分布式存储机制-槽 [1]Redis Cluster 在设计中没有使用一致性哈希(Consistency Hashing),而是使用数据分片(Sharding)引入哈希槽[2]Redis Cluste ...
- MyBatis的深入原理-架构设计以及实例分析
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...
- 学Shiro完结版-1
第一章 Shiro简介--<跟我学Shiro> 1.1 简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spr ...
- js脚本实现文本文件格式批量转换
问题: 在Windows环境下,从某些软件中导出的文本格式的数据,选择了默认的ANSI格式.双击打开数据文件后,一切正常,没乱码问题.但是为什么自己的代码里,先按照ANSI格式打开,在转换为UTF8, ...
- uni-app选中状态并改变颜色
思路 定义一个数组来记录被点击的元素 arr 数组通过indexOf来来查找 如果有,激活类就是true 没有: 激活类为false 这一步最关键的是查找的内容就是显示出来的index, 点击的时候传 ...
- Q:oracle中blog中截取部分字符串
blog报文中获取对应标签字符串 将xxx替换成需要查询的标签 to_char(substr(C_INPUT,instr(C_INPUT,'<xxx>')+length('<xxx& ...
- presto集成iceberg
一.Presto服务下新建catelog cd /usr/local/service/presto/etc/catalog vim iceberg.properties connector.name= ...
- Spark异常总结
1.Spark读写同一张表报错问题Cannot overwrite a path that is also being read from 问题描述:Spark SQL在执行ORC和Parquet格式 ...