dushenda

李德胜大大粉丝

dushenda

三种大气质量和一个公式的计算方法

简介

​ 整理的文献为 Revised optical air mass tables and approximation formula

​ 文章一开始介绍了一个由 Karsten 在1965年发表的并且广泛被世界所采用的关于大气质量的近似公式,并且讨论了一些由于各个学科对于不同的物理量符号和术语的不同使得读者经常由此而困惑。

​ 其后介绍了一个计算大气光学质量的近似公式,然后说明了在公式中存在的一种不定情况,之后又对这个近似公式用非线性最小二乘法进行修正得到了一组新的系数。后面又根据索引文献[1]文中也多次提到这篇文献,很多都是从这篇文献里面来的。

​ 还有从一篇《基于遥感与地面监测数据的城市气溶胶定量反演研究》,作者是王耀庭,南京师范大学博士论文。

一个通用的计算公式

$$ m(\gamma)=\frac{m_{abs}(\gamma)}{m_{abs}(90^{\circ})} $$

$$ m_{abs}(\gamma)=\rho_{0}\int^{\infty}_{0} \frac{\rho}{\rho_{0}}([1-[1+2\delta_{0}(1-\frac{\rho}{\rho_{0}})]]\times [\frac{\cos \gamma}{1+\frac{h}{R}}]^{2})^{-\frac{1}{2}}dh $$ h是相对于海平面的平均高度;

ρ = ρ(h),是在高度h处的大气质量;

表格1

​ 根据以上的式子 (1),(2) 和已知的参数表1。要计算这个定积分,那就还需要知道 ρ, 也就是 ρ(h) 在高度 h 处的大气密度,但是我在文献中找不到,这是个问题,不知道是不是需要再去别的地方找这个 ρ,看完了这篇文章之后,知道了这个 ρ 还是没有找到,但是文章已经给出了计算得到的结果的表格。

近似计算公式和不同的系数

f(γ) = [sinγ+a(γ+b)c]−1 γ是高度角,单位是;f(γ)是用近似公式计算的m(γ);a, b, c是式子的常数,a = 0.1500,b = 3.885c = 1.253;

​ a,b,c这三个常数决定于最小二乘法的相对误差,也就是用前面的计算公式计算数据之后,用最小二乘法进行拟合,使用(3)的形式来计算三个常数。

​ 文献后面又介绍了两个不同的参数组合,一个是根据非线性最小二乘法计算的 a = 0.50572, b = 6.07995°, c = 1.6364;一个是根据 Bemporad 的经典大气质量表确定的,a = 0.6556, b = 6.379°, c = 1.757[1],其中文献的表中的 r(γ) 是根据公式(4)计算的相对误差,用来衡量计算大气质量的相对误差。 $$ r(\gamma)=\frac{f(\gamma)-m(\gamma)}{m(\gamma)} $$

积分问题和解决

​ 对于公式(2),积分会在 γ = 0h 接近于0的地方不定,在这种情况下,这个积分可以通过执行一个特殊的程序来进行计算,在参考文献[1]中有写这个程序。但是在计算的时候有个错误会混入,在地平线上的值 36.2648 会比实际的小 5% 左右。

​ 举例而言,在 Link 和 Neuzil[3] 文章的表中所给出的地平线上的在1962年美国的标准大气的大气质量是38.16,这跟 1959 年 Karsen 用的 ARDC 模型十分接近。Snider 和 Goldman[4] 给出的关于 1962 年的模型的38.10也是高度相似。Treve[5] 使用1959年的 ARDC 模型,得到了在地平线上的相对大气质量分别是 $0.55μm $ 的38.11和在 0.70μm 的38.08。

​ 还有就是采用一种新的标准来却确定式子 (2) 中的参数会优于旧的模型,也就是最新的国际标准化组织的大气模型 (ISO Standrad Atmophere) 代替 ARDC 模型大气(由国际民航组织 ICAO 提出的),这个仅有的变化也就是名义地球半径变为 R = 6.356766 × 106m

一个计算公式

$$ m=\frac{1}{cos \frac{\pi \theta_0}{180^\circ}+0.15\times(93.885-\theta_0)^{-1.253}} $$

​ 其中m是需要计算的大气质量,θ0 是天顶角。

我要做的工作

​ 在这篇文章里面,我要做的就是编写一个程序,根据文献中的大气质量近似公式(3),并且用不同的参数组带入,将表格中自变量太阳高度角γ作为自变量带入近似公式计算,再与表格中所给的大气质量数进行作差比较,即验证这个算法是否真的符合实际,如果误差较小,则可以用到我们的项目中去。

表格2

​ 上面的这张表格也就说明了在文章计算的数据中天顶角 γ 的取值变化,也就是计算的时候自变量所采用的值。过计算得到了一些结果。

结果

​ 图1 标准大气质量和用其他公式计算的大气质量

​ 图2 四种计算方法与标准大气质量

​ 图3 三种拟合系数计算的大气质量

​ 图4 误差曲线

分析

​ 从上面的图中可以看到,用三种不同的系数计算的相对大气质量以及三组拟合系数的误差曲线,从图中可以看到,三者在天顶角大于 30° 之后都是差不多的经度,主要就是在30°之前的差异。而且可以看到在起始点的时候,第一组和第三组都有很大的误差,特别是第三组,误差都接近于4%,回想文章中提到的积分会在 γ = 0 和 h 接近于 0 的地方不定,需要查阅参考文献[1]来寻找解决方法。但是我看到这个计算的第二组拟合系数表现的很好,不知道是否可以用第二组数据来计算,或者是这三组数据都是在不同的情况下表现的经度水平会不一样。但是有个问题,我们没有找到拟合系数1的这条曲线,在下面会进行说明,实际上它是和其他公式计算的这条曲线重合了

​ 对比三种方法和一个计算公式,发现计算公式的误差在几个计算方法折中的位置,在角度 >10° 之后,这个计算值的偏差与第二组拟合系数计算的误差一样,都是非常小的。

​ 可以看看在高度角大于10°时候的表现。

​ 图 5 在高度角大于10°时候计算大气质量的误差

​ 这里没有找到拟合系数1这条曲线,是因为他的变化与公式计算的是一模一样的,两条线是重合的。

​ 图 6 拟合系数1和公式计算的天顶角大于10°的误差曲线

​ 这说明,其实拟合系数1也就是将天顶角计算的公式做了稍微的变化,就得到了太阳高度角的,本质上,这两个公式是一模一样的,只是取得系数不同罢了。

新给的数据的计算

​ 在之后使用已经写好的这几种计算方法来计算新的数据值,数据可以在 ‘1.xlsx’ 表格中找到。

​ 图 7 根据所给的数据计算的三天的大气质量数值

​ 可以看到,其实一天内的天顶角并不是全是 0~90° 的,这几天维持在 40° 以下,这时候,查阅标准数据也是差不多在这样的数据范围。

参考文献

[1] Kasten,“A New Table and Approximation Formula for the Relative Optical Air Mass”,Arch.Meteorol.Geophys.Bioklimatol.Ser.B 14,206-223(1965). [2] R.A Miner,K.S.W.Chamption,and H.L.Pond,The ARDC Model Atmosphere,1959,Air Force Surveys in Geophysics 11(AFCRL,1959) [3] F.Link and L.Neuzil,Table of Light Trajectories in the Terrestrial Atmosphere(Hermann,Paris,1969) [4] D.E Snier and A. Goldman,Refractive Effects in Remote Sensing of Atmosphere with Infrared Transmission Spectroscopy,(Ballistic Research Labratories,June 1975) [5] Y. M. Treve, New Values of the Optical Air Mass and the Refraction and Comparison with Previous Tables,” in Proceed-ings, Second Tropospheric Refraction Effects Technical ReviewMeeting, Technical Documentary Rep. ESD-TDR 64-103, May1964 (National Technical Information Service Order AD-442626), pp.5-391. [6] International Organization for Standardization,Standard Atmosphere,International Standard ISO253(1972) [7] S.L.Valley,Handbook of Geophysics and Space Physics (AFCRL,1965), pp.23. [8] W.H.Jefferys,M.J.Fitzpatrick,B.E.McArthur,andJ.E. McCartney, GaussFit:A System for Least Squares and RobustEstimation (U. Texas at Austin, 1989). [9] A.T.Young,Observational Technique and Data Reduction,” inle to Methods of Experimental Physics(Vol. 12, Astrophysics; Partrmly A:Optical and Infrared),N,Carleton,Ed.(Academic, New York, 1974),p.150.

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
% 主函数
clear;clc;
%x的步长选取
x1=0:0.1:20;
x2=20.2:0.2:30;
x3=30.5:0.5:55;
x4=56:1:90;
x=[x1,x2,x3,x4];
%定义Latxe字符
gamma=texlabel('gamma');flambda=texlabel('f(lambda)');
txt = texlabel('f(lambda)=[sin gamma+a(gamma+b)^{-c}]^{-1}');
%输入标准数据,画标准数据图像
data=xlsread('datain.xlsx');
datax=data(:,1);px=datax;
datay=data(:,2);py=datay;
subplot(2,1,1)
plot(datax,datay);grid on;box off;
xlabel(['高度角',gamma]);ylabel({'标准的相对大气质量';flambda});
% print('Standard','-deps');
%调用三个计算函数
airMass1=massCal1(x);
airMass2=massCal2(x);
airMass3=massCal3(x);
airMass4=massCal4(x);
subplot(2,1,2)
plot(datax,airMass4);
xlabel(['高度角',gamma]);ylabel({'其他公式计算的相对大气质量';flambda});
suptitle('相对大气质量与高度角的关系');grid on;box off

figure(2);
set(figure(2),'PaperSize',[42,60]);
suptitle(['\fontsize{14}',txt]);
subplot(3,1,1)
plot(x,airMass1);grid on;box off;
text(60,30,'a=0.1500,b=3.885,c=1.253');
xlabel(['高度角',gamma]);ylabel({'计算的相对大气质量';flambda});
subplot(3,1,2)
plot(x,airMass2);grid on;box off;
text(60,30,'a=0.50572,b=6.07995,c=1.6364');
xlabel(['高度角',gamma]);ylabel({'计算的相对大气质量';flambda});
subplot(3,1,3)
plot(x,airMass3);grid on;box off;
text(60,30,'a=0.6556,b=6.379,c=1.757');
xlabel(['高度角',gamma]);ylabel({'计算的相对大气质量';flambda});
set(gcf, 'position', [1920/4 1080/4 1920/2 1080/1.5]);
% print('Calculate','-deps');
%画三个函数的误差曲线
datay=datay';
delta1 = (airMass1-datay)./datay*100;
delta2 = (airMass2-datay)./datay*100;
delta3 = (airMass3-datay)./datay*100;
delta4 = (airMass4-datay)./datay*100;
figure(3)
plot(x,delta1);grid on;box off;hold on
plot(x,delta2,'--');grid on;box off;hold on
plot(x,delta3,':');grid on;box off;hold on
plot(x,delta4);grid on;box off;
xlabel(['高度角',gamma]);ylabel('三种大气质量的误差(%)');
legend('第1组拟合系数','第2组拟合系数','第3组拟合系数','其他公式计算');
title('几种计算方法的误差');
% print('Error','-deps');
figure(4)
plot(datax,datay);grid on;box off;hold on
plot(x,airMass1);grid on;box off;hold on
plot(x,airMass2);grid on;box off;hold on
plot(x,airMass3);grid on;box off;hold on
plot(x,airMass4);grid on;box off;
xlabel(['高度角',gamma]);ylabel('相对大气质量');
legend('实际的','方法1','方法2','方法三','其他方法');
title('几种方法和实际的相对大气质量');

[~,Date,~] = xlsread('1.xlsx','A2:A105');
Date = datetime(Date,'InputFormat','dd/MM/yyyy');
Date.Format = 'yyyy-MM-dd';
Time = days(xlsread('1.xlsx','B2:B105'));
Time.Format = 'hh:mm:ss';
datetime = Date+Time;
datetime.Format = 'yyyy-MM-dd hh:mm:ss';
xxx=xlsread('1.xlsx','C2:C105');
xx=90-xxx;
airMassx1=massCal1(xx);
airMassx2=massCal2(xx);
airMassx3=massCal3(xx);
airMassx4=massCal4(xx);
output=[airMassx1,airMassx2,airMassx3,airMassx4];
xlswrite('output.xlsx',output);

figure
datetime = datenum(datetime);
plot(datetime,airMassx1);grid on;box off;hold on
plot(datetime,airMassx2);grid on;box off;hold on
plot(datetime,airMassx3);grid on;box off;hold on
plot(datetime,airMassx4);grid on;box off;hold on
dateFormat = 'yy-mm-dd HH:MM:SS';
datetick('x',dateFormat)
%plot(px,py);grid on;box off;
legend('计算方法1','计算方法2','计算方法3','其他方法计算的');
xlabel('时间');ylabel('大气质量');
title('新给的数据的高度角计算的值')

p = (x>10);
x = x(p);
delta1 = delta1(p);
delta2 = delta2(p);
delta3 = delta3(p);
delta4 = delta4(p);
figure
plot(x,delta1);hold on
plot(x,delta2);hold on
plot(x,delta3);hold on
plot(x,delta4);grid on;box off;
xlabel('高度角');ylabel('大气质量计算偏差');
title('高度角 >10° 时大气质量计算偏差');
legend('拟合系数1','拟合系数2','拟合系数3','公式计算');

figure
subplot(2,1,1)
plot(x,delta1);box off;grid on
xlabel('高度角');ylabel('大气质量计算偏差');
title('拟合系数1计算');
subplot(2,1,2)
plot(x,delta4);box off;grid on
xlabel('高度角');ylabel('大气质量计算偏差');
title('公式计算');
suptitle('高度角 >10° 时大气质量计算偏差')
1
2
3
4
5
6
7
8
9
% 计算方法1
function y=massCal1(gamma)
%函数调用格式:airMass=massCal1(gamma)
%输入参数说明:gamma是天顶角,单位是°
%输出参数说明:airMass是大气质量
a=0.1500;b=3.885;c=1.253;
% gamma=gamma*pi/180;%b=b*pi/180;
y=(sin(gamma*pi/180)+a*(gamma+b).^(-c)).^(-1);
end
1
2
3
4
5
6
7
8
% 计算方法2
function y=massCal2(gamma)
%函数调用格式:airMass=massCal2(gamma)
%输入参数说明:gamma是天顶角,单位是°
%输出参数说明:airMass是大气质量
a=0.50572;b=6.07995;c=1.6364;
y=(sin(gamma*pi/180)+a*(gamma+b).^(-c)).^(-1);
end
1
2
3
4
5
6
7
8
% 计算方法3
function y=massCal3(gamma)
%函数调用格式:airMass=massCal(gamma)
%输入参数说明:gamma是天顶角,单位是°
%输出参数说明:airMass是大气质量
a=0.6556;b=6.379;c=1.757;
y=(sin(gamma*pi/180)+a*(gamma+b).^(-c)).^(-1);
end

考拉兹猜想

考拉兹猜想定义

考拉兹函数定义如下

$$ f(x)=\left\{ \begin{array}{**lr**} 3n+1&x为奇数且x\neq1\\ n/2&x为偶数\\ 1&x=1 \end{array} \right. $$ 通过对 x 取不同的值,发现最后都会收敛到 1。求该函数构成算法的上下界。

当然,下界是很容易求出来的,如果输入 n ,下降最快的也就是每次下降 $\frac{1}{2}$,这个下降速度对于的计算时间是 log n 。对于上界,用 MATLAB 带入一些数值计算。得到的结果如下:

部分 n0 的步长与 n 值变化

7~10
11~14
15~18
19~22
23~26
27~30
31~34
35~38
39~42
43~46
47~50
51~54
55~58
242~245

n0 与计算次数分布曲线

次数与初值

n0=1000
n0=5000
n0=10000

论文笔记-场地自动化定标方法研究及应用

辐射定标的意义

卫星获取地物图像时候,由于存在各种各样的干扰,造成观测值与实际值的偏差,定标就是来(尽可能)消除这些偏差,使得卫星得到的图像与真实的物体之间(尽可能)没有这些偏差,即反映客观事物。

辐射定标

辐射校正

遥感辐射定标方法

因为卫星所在环境为外太空,会受到强烈的电磁干扰和强辐射,所以器件的老化程度是很快的,这也就是卫星存在使用寿命的一个原因,受限于载荷的使用寿命。那么要延长载荷的工作年限,就需要实时测出其老化程度,对于传感器的灵敏度做出评估,改变具体参数使得其能够继续工作。比如之前对于输入为 10 就能输出 10 ,现在只能输出 5,就乘以系数 2 就可以完成跟之前一样的功能。这样器件就还可以接着使用。

阅读全文 »

引子

先来看两段代码,这两段代码很简单。main是主函数,调用sum进行求和,其中sum函数参数是指针类型的,也就是说在sum函数进行改变会引起原来的值的改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//main.cpp
#include<iostream>

using namespace std;

int sum(int* a,int* b);

int main(int argc, const char** argv) {
int k=-2;
int j=-5;
int* a;
int* b;
a = &k;
b = &j;
cout<<"k+j="<<sum(a,b)<<endl;
cout<<"k="<<k<<"\t"<<"j="<<j<<endl;
return 0;
}

1
2
3
4
5
6
7
8
//sum.cpp
#include<math.h>

int sum(int *a,int *b)
{
*a = abs(*a);*b=abs(*b);
return (*a+*b);
}

如果需要在Linux上面执行出结果,那么打上下面的代码就可以了:

1
2
g++ main.c sum.cpp -o a.out
./a.out
输出的结果是:
1
2
k+j=7
k=2 j=5

可以看到运行正确了,这个结果也是预料之中的,k 变成了 2,j 变成了 5,这个是在sum里面被改变了值,也会影响到原址的值。

程序到可执行文件

程序到可执行文件需要经过几个阶段:

预编译阶段

预编译阶段是把原始的代码文件的需要预编译头加进来,即对于#include的文件进行原样复制,#include文件可以由命令locate得到

1
2
3
4
5
6
7
8
9
10
11
ubuntu@ubuntu:~/VSCode/CSAPP深入理解计算机系统$ locate iostream
/usr/include/c++/7/iostream
/usr/lib/x86_64-linux-gnu/libboost_iostreams.so.1.65.1
/usr/share/doc/libboost-iostreams1.65.1
/usr/share/doc/libboost-iostreams1.65.1/changelog.Debian.gz
/usr/share/doc/libboost-iostreams1.65.1/copyright
/usr/share/lintian/overrides/libboost-iostreams1.65.1
/var/lib/dpkg/info/libboost-iostreams1.65.1:amd64.list
/var/lib/dpkg/info/libboost-iostreams1.65.1:amd64.md5sums
/var/lib/dpkg/info/libboost-iostreams1.65.1:amd64.shlibs
/var/lib/dpkg/info/libboost-iostreams1.65.1:amd64.triggers
或者是对于C语言的库使用man
1
man stdio
1
2
3
4
5
6
7
8
9
10
11
12
13
STDIO(3)                   Linux Programmer's Manual                  STDIO(3)

NAME
stdio - standard input/output library functions

SYNOPSIS
#include <stdio.h>

FILE *stdin;
FILE *stdout;
FILE *stderr;

DESCRIPTION
预编译命令如下:
1
2
g++ -E main.cpp -o main.i
cat main.i
这个命令是把main.cppmain.i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//部分代码,在Linux试一下就知道
namespace std __attribute__ ((__visibility__ ("default")))
{

# 60 "/usr/include/c++/7/iostream" 3
extern istream cin;
extern ostream cout;
extern ostream cerr;
extern ostream clog;


extern wistream wcin;
extern wostream wcout;
extern wostream wcerr;
extern wostream wclog;




static ios_base::Init __ioinit;


}
# 2 "main.cpp" 2


# 3 "main.cpp"
using namespace std;

int sum(int* a,int* b);

int main(int argc, const char** argv) {
int k=-2;
int j=-5;
int* a;
int* b;
a = &k;
b = &j;
cout<<"k+j="<<sum(a,b)<<endl;
cout<<"k="<<k<<"\t"<<"j="<<j<<endl;
return 0;
}
可以看到,这就是把#include里面的东西原样复制到main.cpp代码文件里面,整合到一起,就叫做预编译过程。 ### C++编译阶段 C++编译阶段就是把预编译好的文件编译成汇编代码,个人觉得这个过程跟翻译差不多,把一种语言翻译成另一种语言,命令如下。
1
2
g++ -S main.i
cat main.s
上面的g++命令默认会有-o main.s添加上,即会产生main.s文件产生。 产生的main.s文件如下,由于太长,截取了部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
	.file	"main.cpp"
.text
.section .rodata
.type _ZStL19piecewise_construct, @object
.size _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
.zero 1
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.LC0:
.string "k+j="
.LC1:
.string "k="
.LC2:
.string "\t"
.LC3:
.string "j="
.text
.globl main
.type main, @function
main:
.LFB1493:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $56, %rsp
.cfi_offset 3, -24
movl %edi, -52(%rbp)
movq %rsi, -64(%rbp)
movq %fs:40, %rax
movq %rax, -24(%rbp)
xorl %eax, %eax
movl $-2, -48(%rbp)
movl $-5, -44(%rbp)
leaq -48(%rbp), %rax
movq %rax, -40(%rbp)
leaq -44(%rbp), %rax
movq %rax, -32(%rbp)
leaq .LC0(%rip), %rsi
leaq _ZSt4cout(%rip), %rdi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
movq %rax, %rbx
movq -32(%rbp), %rdx
movq -40(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call _Z3sumPiS_@PLT
movl %eax, %esi
movq %rbx, %rdi
call _ZNSolsEi@PLT
movq %rax, %rdx
movq

从上面的代码看出,这是个汇编代码,也就完成了main.imain.s的转化。

汇编代码转化为rof文件

rof是relocatable object file的简称,称为可重定向目标文件。

1
g++ -c main.s

输出的文件为main.o,这个文件不是文本文件,是一个二进制文件。

文本文件:由ASCII码组成的文件,可以由文本编辑器直接打开 二进制文件:除了文本文件之外的所有文件,如图片格式文件就属于二进制文件,这种文件需要专门的解码软件打开,如果使用文本编辑器打开就是乱码的。

链接器链接生成eof

eof是executable object file的简称,称为可执行目标文件。

按照前面的步骤(预编译C++编译 → ​rof文件生成)得到sum.o文件。然后进行:

1
g++ main.o sum.o

这时候会产生一个新的文件为a.out,这个名字可以自己取,也就是把默认的那个语句加上,如:

1
g++ main.o sum.o -o main.out
当然,在 Linux 里面文件的后缀名是不重要的,.x也可,只不过.out是在Unix操作系统上第一个实现的可执行文件的后缀名,就保留了下来。

之后就运行:

1
./a.out
得到结果
1
2
k+j=7
k=2 j=5
这个结果跟之前的一致。执行命令的时候,Linux 会使用它的加载器将可执行文件a.out的代码和数据复制到内存,然后将控制转移到这个程序的开头。

小结

总结

可执行文件格式分析

以下答案均不代表正确答案,仅代表作者观点

  • 16.线程可以被时钟中断抢占吗?如果可以,在什么情形下可以?如果不可以,为什么不可以?

    用户级线程不能被时钟剥夺,除非整个进程的时间片用完。内核级线程可以单独地被剥夺。在后一种情况下,如果线程运行过久,时钟将中断该当前进程,因而当前线程也被中断。内核可以自由地从同一个进程中选取其他线程运行。

现代操作系统-进程与线程1~15

以下答案都是作者作答,不代表正确答案,望周知。


  1. 如图,对于每一个进程都有四种状态,照理应该有6种切换方式,但是为什么只有四种,为什么另外两种不行?

img 答:从逻辑上来说,就绪态和运行态是类似的,唯一的不同是在于是否得到CPU资源。如果从就绪态可以转换到阻塞态,那么出现的事件也就是需要等待输入,但是,就绪态就是因为已经出现了有效的输入只不过没有得到CPU资源而产生的等待,所以不可能出现就绪态向阻塞态的转变;从阻塞态到运行态是得到了有效的输入和CPU计算资源才能完成的转化,但是得到有效的输入肯定会位于得到资源的前面,因为没有有效的输入都不能进行计算,而当出现有效输入的时候,就已经是就绪态了,即使有效输入和CPU资源获取的间隔很小,但是始终存在,所以肯定会经过就绪态。

阅读全文 »

现代操作系统-内存管理1

空闲内存的管理

动态分配内存的时候(在进程需要动态扩张的时候),需要操作系统对其进行管理。

  • 使用位图的存储器管理

    从上图可以看出,在内存中运行有A,B,C,D,E五个进程,阴影部分为空闲区。b)是对应的位图,首先堆内存进行分段,1表示有进程在运行,0表示处于空闲区。c)是空闲区链表的存储方式,其中P表示有进程在运行,0,5这样的数字说的是运行的段是从0到5。 使用位图存储器就需要合适的选取刻度值,即对于整个内存的存储,如果刻度选的过小,就会导致位存储器过大,那么在查找和存储就会造成困扰。

  • 使用链表的存储器管理 如果使用一种链表式的存储,如c)所示,那么会显著减小存储,不这是一个单向链表,在查找的时候会比较慢。在进程状态改变的时候(如从内存中被导出等)就需要进行相应的链表操作。

覆盖与交换

覆盖与交换技术都是因为程序实际的需求的内存大小大于物理内存的大小产生的解决方案。他们都是与外存(如磁盘、硬盘等)进行程序与数据的交换。但是区别是交换是由操作系统完成的,交换的最小单元是程序,覆盖是由编程语言或者程序员完成的,导入导出的最小单元是程序中的模块,需要有相应的运行时支持。所以可以发现,交换的单个容量相对较大,并且不需要程序员过多的操心,覆盖的单个的容量相对较小,以程序的模块为单位。

技术 优点 缺点
交换技术 由操作系统完成,不需要程序员操心,自动化 交换力度过大,可能导致交换产生的开销过大
覆盖技术 交换力度小,产生的开销小 由程序员完成,不方便且易出错

交换技术

交换过程

上图所示就是操作系统的内存交换过程,首先知道交换技术是进程在内存和外存之间的导入导出操作,主要原因是内存的容量不够,其中的A,B,C,D均为进程。

  1. 运行进程A,进程A的程序和数据从头开始放
  2. 之后运行进程B,此时进程A处于就绪状态,依旧存在于内存之中
  3. 加载C进程,和前面的方法类似
  4. 将进程A导出到外存,留下A部分的空闲区,也叫做洞
  5. 加载D进程,这时候还是从头开始检测,发现空闲区可以容纳进程D
  6. 杀死进程B
  7. 从外存导入进程A,此时是空闲区刚好容纳A进程的大小

内存紧缩

上述就是交换过程,其中有一个问题,那就是一个进程被导出之后留下了空闲区,空闲区的存在可能容纳不了下一个加载进程的大小。

这时候,就需要将进程B与进程A挪一挪,将其变成下面的状态。

这样的拷贝移动的过程称为内存紧缩。当然这种技术很不错,但是有一个问题,那就是开销,之前说到交换技术的最大的缺点是开销问题,开销是因为进程过于庞大,频繁移动他们需要很大的资源。

例如,一台16GB内存的计算机可以每8ns复制8个字节,那么其紧缩全部内存需要花费16s

这其实是很长的时间了,可以想象,经常使用内存紧缩是不合适的。

动态分配

前面说到的进程好像就是一个进程导入内存就可以运行了,但是在很多情况下,进程需要接收输入,并且会进行一些处理,如果是数据增长,那么解决方法较为简单,因为数据位于数据段,可以直接由编程语言从堆中分配内存,还有一种情况是进程空间需要扩大,那么就需要操作系统在导入进程的时候预留一部分空间给进程,以应对其扩张。分配的大小是由操作系统需要的大小分配,即进程中所带的信息决定的,当其意外的超出了规定的大小并且磁盘上也没有多余的交换空间之后,那么该进程就会被挂起直到有一些空闲的空间。

覆盖技术

一个典型的SATA磁盘的峰值传输率高达每秒几百兆,这就意味着需要好几秒才能换出或者换入一个1GB的程序。

所以交换技术的开销问题确实是一个很致命的点。所以就出现了将程序分割,称为模块,将模块进行交换的技术,即覆盖技术

虚拟内存

从上面的交换技术和覆盖技术中可以看出,他们各自互补各自的优缺点。如果有一种技术,能够像覆盖那样导入导出小的模块,又能像交换那样由操作系统完成就优势互补了,这就是虚拟内存技术产生的原因了。

虚拟内存的思想是和覆盖类似,只不过将模块变成了页,页是对于程序的等大小的划分,这些页被映射到物理内存,并且由硬件直接运行。

从某个角度来说,虚拟内存是对基址寄存器和界限寄存器的一种综合。

由于涉及操作系统对页的自动操作,所以对于页的讨论还很多,留在下一篇再说。

CsvHelper 使用手册

导引

安装

在包管理控制台

1
PM> Install-Package CsvHelper

.NET CLI 控制台

1
>dotnet add package CsvHelper

读一个 CSV 文件

首先创建一个这样的 CSV 文件

1
2
3
ID,Name
1,one
2,two

做一个类的定义如下

1
2
3
4
5
public class Foo
{
public int ID{get;set;}
public string Name{get;set;}
}

如果我们创建的类的属性名能够匹配目标 CSV 文件的表头,那么我们就无需任何配置的读取这个文件。

1
2
3
4
5
using (var reader = new StreamReader("Path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var records = csv.GetRecords<Foo>();
}

这个 GetRecords<T> 方法将会返回一个 IEnumerable<T>将会yield records。这也就意味着当你在反复查询记录的时候一次只能返回一条,即仅仅只有文件的一小部分会被读到内存中。不过要小心的是,如果你做了任何关于 LINQ 投影的事情,就像调用 .ToList(),整个文件都会被读入到内存中。 CsvReader只能向前走,所以当你想要运行任何 LINQ 查询来防范你的数据,你需要知道如果这样做的话,整个文件会被加载到内存中。

加入我们对前面的 CSV 文件做了一点点改变,使它与之前的属性不是完全匹配了。

1
2
3
id,name
1,one
2,two

在此次改动中,我们把名字都用小写字母替代了。由于我们之前设置的属性名能是驼峰式的,这样我们就可以改变属性头与表格头的匹配方式了。

1
2
3
4
5
6
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.PrepareHeaderForMatch = (string header,int index) => header.ToLower();
var records = csv.GetRecords<Foo>();
}

使用配置 PrepareHeaderForMatch,我们就能够实现不同名称之间的配对。头名和属性名都包含在 PrepareHeaderForMatch函数中。当 reader 需要使用属性名来设置头名的时候,他们将会匹配。你还能够使用这个函数来做一些其他的事情,比如说空格或者其他的一些字符。

那我们再来看看如果我们去掉 CSV 文件的头名怎么破吧。

1
2
1,one
2,two

首先我们需要告诉 reader 文件中已经没有头记录了,配置如下

1
2
3
4
5
6
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.HasHeadRecord = false;
var record = csv.GetRecords<Foo>();
}

CsvReader 将会使用类中属性的位置作为索引点。但是这有一个问题,你不能再依靠 .NET 中类成员的顺序了。解决方法就是将这个属性映射到 CSV 文件的特定位置。

一种方法就是用属性映射。

1
2
3
4
5
6
7
8
public class Foo
{
[Index(0)]
public int Id{get;set;}

[Index(1)]
public string Name{get;set;}
}

这个 IndexAttribute允许你指定你想要使用属性的位置。

你还可以使用名字作为映射,让我们使用前面的小写头部的例子来看看怎么使用名字匹配。

1
2
3
4
5
6
7
8
public class Foo
{
[Name("id")]
public int Id{get;set;}

[Name("name")]
public string Name{get;set;}
}

有许多的属性你同样可以使用。

如果我们无法操作我们做匹配的这个类来增加我们需要的属性怎么办?在这个例子中,我们会使用 ClassMap做一次流利的匹配。

1
2
3
4
5
6
7
8
public class FooMap:ClassMap<Foo>
{
public FooMap()
{
Map(M => m.Id).Name("id");
Map(m => m.Name).Name("name");
}
}

为了来使用映射,我们需要注册配置。(也就是写一下配置方法)

1
2
3
4
5
6
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}

推荐创造一个类映射,因为这样的话使用 CsvHelper 会更加强大。

写一个 CSV 文件

现在让我们来看一看怎么写一个 CSV 文件吧,这跟读基本上是一样的,只是顺序相反。

跟之前读文件一样,我们用一样的类定义。

1
2
3
4
5
public class Foo
{
public int Id{get;set;}
public string Name{get;set;}
}

然后我们创建一些这样的记录(Records)

1
2
3
4
5
var records = new List<Foo>
{
new Foo{Id=1,Name="one"},
new Foo{Id=2,Name="two"},
};

我们可以无需配置的把这些记录写到文件中。

1
2
3
4
5
using (var writer = new StreamWriter("Path\\to\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}

这个 WriteRecords方法将会把所有的记录都写到文件中,在你完成写数据之后,你应该调用 writer.Flush()来确保写入器内部缓冲区中的所有数据都已被刷新到文件中。在 using块中的缓存器会自动被清空,因此我们并不需要刻意的去处理这个块。使用 using块来包含 IDisposable对象是一种比较好的方式,这个对象会在 using块退出之后 自己做出相应的处理(在我们这个例子中会自动清除缓存)。

记得我们是不能在 .NET 里面依赖属性的顺序的吗?如果我们写一个有标头的类的时候,这并不重要,我们只需要在后面使用标头即可。如果我们想要将标头安置在 CSV 文件的相应位置的时候,我们就需要指定索引来保证它的顺序,所以当你在写入 CSV 文件数据的时候设置索引是一个好习惯。

1
2
3
4
5
6
7
8
public class FooMap:classMap<Foo>
{
public FooMap()
{
Map(m=>m.Id).Index(0).Name("id");
Map(m=>m.Name).Index(1).Name("name");
}
}

举例说明

预备知识

下面是使用 CsvHelper 需要知道预备知识。

这里有一些关于 .NET 的基础知识是你使用 CsvHelper 前需要知道的,微软有一个很棒的文档能够帮助你学习更多。

使用和释放

不论何时你什么时候创建一个 IDisposable 的对象,你都要在使用资源后释放它,大多数类使用非托管资源来是实现 IDisposable,这也就意味着在 System.IO命名空间里的类都需要被释放。

最好的练习释放对象的方法就是使用 using块中写代码,因为在 using 块中,资源会快速自动的被处理。

1
2
3
4
5
using (var stream = new MemoryStream())
{
//在这里使用 stream 对象
}
//stream 对象将会在这里被快速的处理

如果你在后面也需要用到这个对象,并且稍后会释放它,那么 使用using会帮你做一些错误处理,因此使用 using相比于直接调用 Dispose依旧是一个更好的选择。但是关于这个目前仍然有一些争论,因为有人觉得它没有展现出使用意图。

1
2
3
var stream = new MemoryStream();
//之后其他部分的代码
using (stream){ }

读写文件

System.IO.File组件包含有打开文件进行读写的方法。

1
2
3
4
5
6
7
8
9
using (var stream = File.OpenRead("path\\to\\file.csv"))
{

}

using (var stream = File.OpenWrite("path\\to\\file.csv"))
{

}

这些东西同样返回一个 FileStream来为操作文件进行服务。加入我们的数据是文本型的,我们就需要 StreamReaderStreamWriter来读写文本。

1
2
3
4
5
6
7
8
9
10
11
using (var stream = File.OpenRead("path\\to\\file.csv"))
using (var reader = new StreamReader(stream))
{

}

using (var stream = File.OpenRead("path\\to\\file.csv"))
using (var writer = new StreamWriter(stream))
{

}

StreamReaderStreamWriter都有一种简便写法。

1
2
3
4
5
6
7
8
using (var reader = new StreamReader("path\\to\\file.csv"))
{

}
using (var writer = new StreamWriter("path\\to\\file.csv"))
{

}

因为 CsvHelper 并不知道你文件数据的具体编码,所以当你有一个特殊的编码的时候。你需要再你的流(stream)里面指定它。

1
2
3
4
5
6
7
8
using (var reader = new StremaReader("path\\to\\file.csv"),Encoding.UTF8)
{

}
using (var writer = new StreamWriter("path\\to\\file.csv",Encoding.UTF8))
{

}

CsvReaderCsvWriter的构造函数分别是 TextReaderTextWriterTextReaderTextWriter都是读写文本的一个抽象类。StreamReaderStreamWriter都继承自 TextReaderTextWriter,因此我们也把它们和CsvReader,CsvWriter一起使用。

1
2
3
4
5
6
7
8
9
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
}

using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer))
{
}

读 CSV 文件

获取类记录

将 CSV 插入到对应类的对象中。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

获取动态记录

将 CSV 插入到 dynamic对象中,由于无法判断属性的类型,所以动态对象上的所有属性都是字符串。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var records = csv.GetRecords<dynamic>();
}
}

获取匿名类型的记录

如果你需要将 CSV 插入到匿名类型的对象中,仅仅只要提供匿名类型的定义即可。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var anonymousTypeDefinition = new
{
Id = default(int),
Name = string.Empty
};
var records = csv.GetRecords(anonymousTypeDefinition);
}
}

枚举类型记录

将 CSV 转换为一个类对象,该对象可在枚举的每次迭代中重用。每个枚举将生成给定的记录,但只生成映射的成员。如果你提供一个映射却没有映射其中的一个成员,那么该成员就不会得到当前行的数据。值得注意的是,对于你在工程中调用的任何方法会被强制返回一个 IEnumerable 的值,就像方法 ToList() ,你会得到一个与所有记录的实例与你在 CSV 中最后一条记录相对应的的列表。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var record = new Foo();
var records = csv.EnumerateRecords(record);
foreach (var r in records)
{
// r is the same instance as record.
}
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

手动读取

因为一些原因,不去配置一个与你的类定义一一对应的映射会变得更加容易,只需要再多纪杭代码就可手动实现行的读取。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
var records = new List<Foo>();
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var record = new Foo
{
Id = csv.GetField<int>("Id"),
Name = csv.GetField("Name")
};
records.Add(record);
}
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

读取多样的数据集

因为某些原因,CSV 里面会包含多类型的混合数据集。就像这样读就没有什么问题了,当你检索数据的时候,你需要相应的改变类的类型。

数据

1
2
3
4
5
FooId,Name
1,foo

BarId,Name
07a0fca2-1b1c-4e44-b1be-c2b05da5afc7,bar

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.IgnoreBlankLines = false;
csv.Configuration.RegisterClassMap<FooMap>();
csv.Configuration.RegisterClassMap<BarMap>();
var fooRecords = new List<Foo>();
var barRecords = new List<Bar>();
var isHeader = true;
while (csv.Read())
{
if (isHeader)
{
csv.ReadHeader();
isHeader = false;
continue;
}

if (string.IsNullOrEmpty(csv.GetField(0)))
{
isHeader = true;
continue;
}

switch (csv.Context.HeaderRecord[0])
{
case "FooId":
fooRecords.Add(csv.GetRecord<Foo>());
break;
case "BarId":
barRecords.Add(csv.GetRecord<Bar>());
break;
default:
throw new InvalidOperationException("Unknown record type.");
}
}
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Bar
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Name("FooId");
Map(m => m.Name);
}
}

public sealed class BarMap : ClassMap<Bar>
{
public BarMap()
{
Map(m => m.Id).Name("BarId");
Map(m => m.Name);
}
}

读取多种记录类型

如果你的 CSV 文件中每行都有不同的记录类型,你应该读基于行的类型。

数据

1
2
A,1,foo
B,07a0fca2-1b1c-4e44-b1be-c2b05da5afc7,bar

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.RegisterClassMap<FooMap>();
csv.Configuration.RegisterClassMap<BarMap>();
var fooRecords = new List<Foo>();
var barRecords = new List<Bar>();
while (csv.Read())
{
switch (csv.GetField(0))
{
case "A":
fooRecords.Add(csv.GetRecord<Foo>());
break;
case "B":
barRecords.Add(csv.GetRecord<Bar>());
break;
default:
throw new InvalidOperationException("Unknown record type.");
}
}
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Bar
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(1);
Map(m => m.Name).Index(2);
}
}

public sealed class BarMap : ClassMap<Bar>
{
public BarMap()
{
Map(m => m.Id).Index(1);
Map(m => m.Name).Index(2);
}
}

写 CSV 文件

写类对象

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
};

using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

输出

1
2
Id,Name
1,one

写动态类对象

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Main()
{
var records = new List<dynamic>();

dynamic record = new ExpandoObject();
record.Id = 1;
record.Name = "one";
records.Add(record);

using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);

writer.ToString().Dump();
}
}

输出

1
2
Id,Name
1,one

写匿名类型对象

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
void Main()
{
var records = new List<object>
{
new { Id = 1, Name = "one" },
};

using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}

输出

1
2
Id,Name
1,one

配置

类映射

映射属性

映射到属性。这个将会把类的属性映射到 CSV 数据的标头名字上,映射需要在配置中被注册,例子等价于一点也不使用类映射,用标头来匹配类名。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
}
}
通过名称映射

通过标头名映射到属性,如果你的属性名不匹配你的类名,那么你就可以通过名字来映射到属性。

数据

1
2
Column1,Column2
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Name("ColumnA");
Map(m => m.Name).Name("ColumnB");
}
}
通过可替换的名映射

多标头名映射至属性,如果你有一个可以改变的标头名,你就可以指定多种头名。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Name("TheId", "Id");
Map(m => m.Name).Name("TheName", "Name");
}
}
映射复制名

映射已经复制标头名的属性,有时候你复制了头名,这时候会通过标题名称索引来处理。name 索引是标头名称出现次数的索引,而不是标头位置的索引。

数据

1
2
Id,Name,Name
1,first,last

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string FirstName { get set; }
public string LastName { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.FirstName).Name("Name").NameIndex(0);
Map(m => m.LastName).Name("Name").NameIndex(1);
}
}
通过索引映射

通过标头的索引位置映射属性,如果你的数据不包含标头,你就可以使用索引来映射数据。不能通过 .NET 类的属性的顺序来,所以如果你没有使用名称进行映射,确保你指定了索引。

数据

1
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(0);
Map(m => m.Name).Index(1);
}
}
自动映射

自动映射,如果你没有提供映射配置,你可以直接调用在类中的自动配置,组件会自动帮你创建一个映射。这对于你有比较多数量的属性而言是一个很好的方式,因为他会自动帮你正确的设置好,你需要做的就是对代码做一些小改变。

数据

1
2
Id,The Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
AutoMap();
Map(m => m.Name).Name("The Name");
}
}
忽略属性

忽略映射属性,当你使用自动映射类方法的时候,每个属性都会被映射,如果那里有你不想要映射的属性,你就能够直接忽视他们了。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDirty { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
AutoMap();
Map(m => m.IsDirty).Ignore();
}
}
常数值

对于特定的属性设置常值,你能够设置常值属性而不是映射到域。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
var records = csv.GetRecords<Foo>();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDirty { get; set; }
}

public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
Map(m => m.IsDirty).Constant(true);
}
}
类型转换

使用指定的类型转换,如果你需要从非标准的 .NET 类型转换或者转换到非标准的 .NET 数据,你能够提供一个类型转化来使用属性。

数据

1
2
Id,Name,Json
1,one,"{ ""Foo"": ""Bar"" }"

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.GetRecords<Foo>().ToList().Dump();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public Json Json { get; set; }
}

public class Json
{
public string Foo { get; set; }
}

public class JsonConverter<T> : DefaultTypeConverter
{
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
{
return JsonConvert.DeserializeObject<T>(text);
}

public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
{
return JsonConvert.SerializeObject(value);
}
}

public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
Map(m => m.Json).TypeConverter<JsonConverter<Json>>();
}
}
内联类型转化

转化到一个内联类型,如果你不想要写一个满的 ITypeConverter实现,你能够创建一个可以实现功能的函数。

数据

1
2
Id,Name,Json
1,one,"{ ""Foo"": ""Bar"" }"

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.GetRecords<Foo>().ToList().Dump();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public Json Json { get; set; }
}

public class Json
{
public string Foo { get; set; }
}

public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
Map(m => m.Json).ConvertUsing(row => JsonConvert.DeserializeObject<Json>(row.GetField("Json")));
}
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" }
};

using (var writer = new StreamWriter("path\\to\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.WriteRecords(records);

writer.ToString().Dump();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public Json Json { get; set; }
}

public class Json
{
public string Foo { get; set; }
}

public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
Map(m => m.Json).ConvertUsing(o => JsonConvert.SerializeObject(o));
}
}

输出

1
2
Id,Name,Json
1,one,"{""Id"":1,""Name"":""one"",""Json"":null}"
映射选项

属性一定要存在才能映射,如果你有数据不确定是不是有标头,你需要制作映射选项。

数据

1
2
Id,Name
1,one

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.GetRecords<Foo>().ToList().Dump();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public DateTimeOffset? Date { get; set; }
}

public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name);
Map(m => m.Date).Optional();
}
}
验证

验证一个域值,如果你想要确保你的数据符合一些标准,你能够验证它。

数据

1
2
Id,Name
1,on-e

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.Configuration.RegisterClassMap<FooMap>();
csv.GetRecords<Foo>().ToList().Dump();
}
}

public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public DateTimeOffset? Date { get; set; }
}

public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id);
Map(m => m.Name).Validate(field => !field.Contains("-"));
}
}

属性

大部分的配置都能通过使用属性类映射被完成,完整的可获得的属性列表

数据

1
2
3
Identifier,name,IsBool,Constant
1,one,yes,a
2,two,no,b

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void Main()
{
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
csv.GetRecords<Foo>().ToList().Dump();
}
}

public class Foo
{
[Name("Identifier")]
public int Id { get; set; }

[Index(1)]
public string Name { get; set; }

[BooleanTrueValues("yes")]
[BooleanFalseValues("no")]
public bool IsBool { get; set; }

[Constant("bar")]
public string Constant { get; set; }

[Optional]
public string Optional { get; set; }

[Ignore]
public string Ignored { get; set; }
}

类型转换

待补…

数据表

使用 CsvHelper 去加载数据表是非常频繁的事情,因此我直接将其集成了一个功能。

CsvDataReader 实现 IDataReader方法。,这也就意味着它仅有前向数据读取的所有功能,所以真的不必要去直接使用这个类而不用 CsvReaderCsvDataReader要求有 CsvReader的实例并且在内部使用来完成其功能。

使用 CsvHelper 来加载一个 DataTable是比较简单的,默认的,表格中的所有列将会以字符串的形式被加载。当读取器已经做好了实例化的准备,你只需要在创建 CsvDataReader实例前做一些配置即可。

1
2
3
4
5
6
7
8
9
10
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
// Do any configuration to `CsvReader` before creating CsvDataReader.
using (var dr = new CsvDataReader(csv))
{
var dt = new DataTable();
dt.Load(dr);
}
}

如果你想要指定行和行的类型,数据表也可以进行自动的类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
// Do any configuration to `CsvReader` before creating CsvDataReader.
using (var dr = new CsvDataReader(csv))
{
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Name", typeof(string));

dt.Load(dr);
}
}

应用程序接口(API)

命名空间(CsvHelper Namespace)

类(Classes)
BadDataException Represents errors that occur due to bad data.
CsvDataReader Provides a means of reading a CSV file forward-only by using CsvReader.
CsvFieldReader Reads fields from a System.IO.TextReader .
CsvHelperException Represents errors that occur in CsvHelper.
CsvParser Parses a CSV file.
CsvReader Reads data that was parsed from CsvHelper.IParser .
CsvSerializer Defines methods used to serialize data into a CSV file.
CsvWriter Used to write CSV files.
Factory Creates CsvHelper classes.
FieldValidationException Represents a user supplied field validation failure.
HeaderValidationException Represents a header validation failure.
MissingFieldException Represents an error caused because a field is missing in the header while reading a CSV file.
ObjectResolver Creates objects from a given type.
ParserException Represents errors that occur while parsing a CSV file.
ReaderException Represents errors that occur while reading a CSV file.
ReadingContext CSV reading state.
RecordBuilder Builds CSV records.
ReflectionExtensions Extensions to help with reflection.
ValidationException Represents a user supplied validation failure.
WriterException Represents errors that occur while writing a CSV file.
WritingContext CSV writing state.
接口(Interfaces)
IFactory Defines methods used to create CsvHelper classes.
IFieldReader Defines methods used to read a field in a CSV file.
IObjectResolver Defines the functionality of a class that creates objects from a given type.
IParser Defines methods used the parse a CSV file.
IReader Defines methods used to read parsed data from a CSV file.
IReaderRow Defines methods used to read parsed data from a CSV file row.
ISerializer Defines methods used to serialize data into a CSV file.
IWriter Defines methods used to write to a CSV file.
IWriterRow Defines methods used to write a CSV row.
枚举(Enums)
Caches Types of caches.