数据基础---不同软件中的数据类型
发布日期:2021-07-24 12:00:33 浏览次数:1 分类:技术文章

本文共 24395 字,大约阅读时间需要 81 分钟。

python中的数据类型

数据的组成

python中数据包含三部分: 身份类型

身份-例如给变量a赋值,a=1,id(a)可得到a的身份,为长整型,代表内存地址。

数据类型(基础)

python中不需要声明数据类型,在给变量赋值;时自动生成数据类型。

python中的数据类型包括:
数值型(int,long,float,complex)、
布尔型(boolean)、
字符串(string)、
列表(list)、
元组(tuple)、
字典(dict)
python中通过第三方库可以引入更复杂的数据类型,如矩阵,但都是通过这些基本类型衍生出来的。
判断一个变量类型
if type(x)==aa:,如if type(x)==dict

数值型

Python中取整的几种方法:

1、向下取整
向下取整直接用内建的 int() 函数即可:
2、四舍五入
对数字进行四舍五入用 round() 函数:
round函数很简单,对浮点数进行近似取值,保留几位小数。比如

round(10.0/3, 2)3.33round(20/7)3

第一个参数是一个浮点数,第二个参数是保留的小数位数,可选,如果不写的话默认保留到整数。

1、round的结果跟python版本有关
我们来看看python2和python3中有什么不同:

$ pythonPython 2.7.8 (default, Jun 18 2015, 18:54:19) [GCC 4.9.1] on linux2Type "help", "copyright", "credits" or "license" for more information.>>> round(0.5)1.0$ python3Python 3.4.3 (default, Oct 14 2015, 20:28:29) [GCC 4.8.4] on linuxType "help", "copyright", "credits" or "license" for more information.>>> round(0.5)0

如果我们阅读一下python的文档,里面是这么写的:

在python2.7的doc中,round()的最后写着,“Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0.” 保留值将保留到离上一位更近的一端(四舍六入),如果距离两端一样远,则保留到离0远的一边。所以round(0.5)会近似到1,而round(-0.5)会近似到-1。但是到了python3.5的doc中,文档变成了“values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice.” 如果距离两边一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2。

所以如果有项目是从py2迁移到py3的,可要注意一下round的地方(当然,还要注意/和//,还有print,还有一些比较另类的库)。

2、特殊数字round出来的结果可能未必是想要的

>>> round(2.675, 2)2.67

python2和python3的doc中都举了个相同的栗子,原文是这么说的:

NoteThe behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.

简单的说就是,round(2.675, 2) 的结果,不论我们从python2还是3来看,结果都应该是2.68的,结果它偏偏是2.67,为什么?这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串1和0后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离2.67要更近一点点,所以保留两位小数时就近似到了2.67。

以上。除非对精确度没什么要求,否则尽量避开用round()函数。近似计算我们还有其他的选择:

使用math模块中的一些函数,比如math.ceiling(天花板除法)。python自带整除,python2中是/,3中是//,还有div函数。字符串格式化可以做截断使用,例如 "%.2f" % value(保留两位小数并变成字符串……如果还想用浮点数请披上float()的外衣)。当然,对浮点数精度要求如果很高的话,请用嘚瑟馍,不对不对,请用decimal模块。

3、向上取整

向上取整需要用到 math 模块中的 ceil() 方法:
4、分别取整数部分和小数部分
有时候我们可能需要分别获取整数部分和小数部分,这时可以用 math 模块中的 modf() 方法,该方法返回一个包含小数部分和整数部分的元组:

import mathmath.modf(3.25)#(0.25, 3.0)math.modf(3.75)#(0.75, 3.0)math.modf(4.2)#(0.20000000000000018, 4.0)有人可能会对最后一个输出结果感到诧异,按理说它应该返回 (0.2, 4.0) 才对。这里涉及到了另一个问题,即浮点数在计算机中的表示,在计算机中是无法精确的表示小数的,至少目前的计算机做不到这一点。上例中最后的输出结果只是 0.2 在计算中的近似表示。Python 和 C 一样, 采用 IEEE 754 规范来存储浮点数。

列表

关于列表更详细的用法可参考:,像插入、删除、索引、获取等

append和extend的区别
列表的合并 append()向原列表的尾部追加一个列表,该列表作为一个元素占一个索引位 extend()向原列表的尾部追加另一个列表中的元素,另一列表有几个元素就占几个索引位 +看起来和extend()一个效果,但是生成了一个新的列表来存放 +=和extend一个效果。
更详细的说明可
追加元素的方式

alist.append(blist)alist.extend(blist)

不能使用```alist=alist.append(blist),否则报错,具体解释,可

列表中的元素相加减
Python中的列表中的元素不能直接相加减。
最佳的方式是将列表转换成Python中的科学计算包numpy包的array类型,再进行加减。

import numpy as npa = np.array([1,2,3,4])b = np.array([7,8,9,10])s = a + b

xrange与range的区别

range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。

range(5)#[0, 1, 2, 3, 4]range(1,5)#[1, 2, 3, 4] range(0,6,2)#[0, 2, 4]

xrange 用法与 range 完全相同,所不同的是生成的不是一个list对象,而是一个生成器。

xrange(0,6,2)#xrange(0, 6, 2)list(xrange(0,6,2))#[0, 2, 4]

要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。

xrange 和 range 这两个基本上都是在循环的时候用。range会直接生成一个list对象:而xrange则不会直接生成一个list,而是每次调用返回其中的一个值:旧的值会被收回,内存释放。
python3中已经用xrange默认代替range了,也就是说(使用range其实就是使用python2中的xrange)
List中字符串转换成浮点数

strlst = ['12', '345', '678']floatlst = [float(_s) for _s in strlst]

如何判断一个变量是一个列表

if type(x)==list
python getitem()方法
alist[5] 和 alist.__getitem__(5) 是完全等效的

元组

字典

可迭代

a = [1,2,3]b = [4,5,6]c = [4,5,6,7,8]x=zip(a,b,c)list(x)#[(1, 4, 4), (2, 5, 5), (3, 6, 6)]
sorted(dict1.items(), key=lambda d: d[0])sorted(dict1.items(), key=lambda d: d[1])

基础数据类型的转换

subset = data_set[['data_date', 'data_1', 'data_2']]tuples = [tuple(x) for x in subset.values]

数据的可变与不可变

变量被创建时存于内存之中,变量被赋值指的是变量指向了该内存地址。数据的不可变指的是内存中的数据不会变,当变量被赋予新值时,变量指向了新的地址。而列表和字典这两种可变类型,当变量被赋新值时,变量指向的地址不变,地址中的内容变化。

变量类型转换

对有些操作如字符串拼接会自动类型转换进行输出,而有些类型不同类型混合操作则会出错,如a=“1”,b=a+3此时就需要类型转换

如果需要对list这些包含大量元素的对象中的元素进行数据类型转换,可以使用map函数。
Python map() 函数
描述
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表或迭代器。
语法
map() 函数语法:
map(function, iterable, …)
参数
function – 函数,有两个参数
iterable – 一个或多个序列
返回值
Python 2.x 返回列表。
Python 3.x 返回迭代器。
实例

以下实例展示了 map() 的使用方法:def square(x):  # 计算平方数    return x ** 2map(square, [1, 2, 3, 4, 5])  # 计算列表各个元素的平方[1, 4, 9, 16, 25]map(lambda x: x ** 2, [1, 2, 3, 4, 5])  # 使用 lambda 匿名函数[1, 4, 9, 16, 25]# 提供了两个列表,对相同位置的列表数据进行相加map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])[3, 7, 11, 15, 19]

python里map的用法有点像spark中map,reduce的用法。更详细的用法可。

排序

sorted() 和 list.sort()
list.sort()
sort() 函数用于对原列表进行排序,如果指定参数,则使用指定的比较函数。
list.sort([func])
该方法没有返回值,但是会对列表的对象进行排序。

aList = [123, 'xyz', 'zara', 'abc', 'xyz']aList.sort()print( "List : "%s)%(aList)#List :  [123, 'abc', 'xyz', 'xyz', 'zara']

使用key函数

#python list 排序 def my_key1(x):    return x % 10aList = [4, 5, 1, 2, 12, 34, 56, 9 ,80]aList.sort() #默认按升序排列print(aList) aList.sort(reverse = True) #按降序排列print(aList) aList.sort(key = my_key1) #根据key函数,按照个位数进行升序排列print(aList) def my_key2(x):    return x[1]aList = [(4,'ab'), (56,'c'), (1,'bb'), (102, 'a')]aList.sort(key = my_key2) #按照每个元组的第2分量,即字符串排序print(aList)

sorted()

只要是可迭代对象都可以用sorted 。

sorted(itrearble, cmp=None, key=None, reverse=False)

=号后面是默认值 默认是升序排序的, 如果想让结果降序排列,用reverse=True

最后会将排序的结果放到一个新的列表中, 而不是对iterable本身进行修改。
1, 简单排序
sorted('123456')字符串
['1', '2', '3', '4', '5', '6']
sorted([1,4,5,2,3,6])列表
[1, 2, 3, 4, 5, 6]
sorted({1:'q',3:'c',2:'g'})字典, 默认对字典的键进行排序
[1, 2, 3]
sorted({1:'q',3:'c',2:'g'}.keys())对字典的键
[1, 2, 3]
sorted({1:'q',3:'c',2:'g'}.values())对字典的值
['c', 'g', 'q']
sorted({1:'q',3:'c',2:'g'}.items())对键值对组成的元组的列表
[(1, 'q'), (2, 'g'), (3, 'c')]
2, 对元素指定的某一部分进行排序,关键字排序
s = ['Chr1-10.txt','Chr1-1.txt','Chr1-2.txt','Chr1-14.txt','Chr1-3.txt','Chr1-20.txt','Chr1-5.txt']
我想要按照-后的数字的大小升序排序。要用到key
sorted(s, key=lambda d : int(d.split('-')[-1].split('.')[0]))
['Chr1-1.txt', 'Chr1-2.txt', 'Chr1-3.txt', 'Chr1-5.txt', 'Chr1-10.txt', 'Chr1-14.txt', 'Chr1-20.txt']
这就是key的功能,制定排序的关键字,通常都是一个lambda函数,当然你也可以事先定义好这个函数。如果不讲这个关键字转化为整型,结果是这样的:
sorted(s, key=lambda d : d.split('-')[-1].split('.')[0])
['Chr1-1.txt', 'Chr1-10.txt', 'Chr1-14.txt', 'Chr1-2.txt', 'Chr1-20.txt', 'Chr1-3.txt', 'Chr1-5.txt']
这相当于把这个关键字当做字符串了,很显然,在python中,'2' > '10'你可以定制你想要的key, 如 key = lambda x : len(x) 按照序列的长度去排序。key= lambda x : (x[1], x[0])按二个元素,再第一个 等等。。。
3,cmp不怎么用,因为key和reverse比单独一个cmp效率要高。
如果进行降序排列,只需要加上reverse=True
总结: sorted 和list.sort 都接受key, reverse定制。但是区别是。list.sort()是列表中的方法,只能用于列表。而sorted可以用于任何可迭代的对象。list.sort()是在原序列上进行修改,不会产生新的序列。所以如果你不需要旧的序列,可以选择list.sort()。 sorted() 会返回一个新的序列。旧的对象依然存在。
如果你有一个字典,键是正负都有的只有一个小数点的数字字符串, 你想按数字从小到大排列键,首先把键列表转化为浮点型。对浮点型数据用sorted排序,然后再转化为只有一个小数点的数字字符串:
for i in ['%.1f'%k for k in sorted(float(j) for j in fb_RA_11.keys())]:

判断一个元素是否存在于list、tuple或dict中

in 和 not in

if 元素 in list:
if 元素(key) in dict:

判断不在则用not in

Python处理缺失值 (null)

python中获取特殊值的方法

inf = float("inf")ninf = float("-inf")nan = float("nan")

用 pandas 模块的函数

pandas 中读入数据中的缺失值

删除含缺失值的样本
具体处理方法:

  • df.isnull()#是缺失值返回True,否则范围False
  • df.isnull().sum()#返回每列包含的缺失值的个数
  • df.dropna()#直接删除含有缺失值的行
  • df.dropna(axis = 1)#直接删除含有缺失值的列
  • df.dropna(how = 'all')#只删除全是缺失值的行
  • df.dropna(thresh = 4)#保留至少有4个缺失值的行
  • df.dropna(subset = ['C'])#删除含有缺失值的特定的列

填充缺失值

数值型数值(Numerical Data)

方法一:fillna()函数

  • df.fillna(0):用0填充
  • df.fillna(method='pad'):用前一个数值填充
  • df.fillna(df2.mean()):用该列均值填充

想准确的判断NaN,那么就用math下的isnan函数吧:

math.isnan(a)

方法二:Imputer

from sklearn.preprocessing import Imputerimr = Imputer(missing_values='NaN', strategy='mean', axis=0)#均值填充缺失值imr = imr.fit(df)imputed_data = imr.transform(df.values)

用 numpy 模块的函数

import numpy as npinfT = float("inf")ninfT = float("-inf")nanT = float("nan")print(np.isinf(infT))#Trueprint(np.isinf(ninfT))#Trueprint(np.isnan(nanT))#True

用 math模块的函数

math.isinf和math.isnan

import mathinfT = float("inf")ninfT = float("-inf")nanT = float("nan")print(math.isinf(infT))#Trueprint(math.isinf(ninfT))#Trueprint(math.isnan(nanT))#True

mysql中的数据类型

MySQL支持多种类型,大致可以分为三类:数值、日期/时间和字符串(字符)类型。

数值类型
MySQL支持所有标准SQL数值数据类型。
这些类型包括严格数值数据类型(INTEGER、SMALLINT、DECIMAL和NUMERIC),以及近似数值数据类型(FLOAT、REAL和DOUBLE PRECISION)。
关键字INT是INTEGER的同义词,关键字DEC是DECIMAL的同义词。
BIT数据类型保存位字段值,并且支持MyISAM、MEMORY、InnoDB和BDB表。
作为SQL标准的扩展,MySQL也支持整数类型TINYINT、MEDIUMINT和BIGINT。下面的表显示了需要的每个整数类型的存储和范围。

类型 大小 范围(有符号) 范围(无符号) 用途
TINYINT 1 字节 (-128,127) (0,255) 小整数值
SMALLINT 2 字节 (-32 768,32 767) (0,65 535) 大整数值
MEDIUMINT 3 字节 (-8 388 608,8 388 607) (0,16 777 215) 大整数值
INT或INTEGER 4 字节 (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值
BIGINT 8 字节 (-9 233 372 036 854 775 808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值
FLOAT 4 字节 (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度浮点数值
DOUBLE 8 字节 (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 双精度浮点数值
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值

日期和时间类型

表示时间值的日期和时间类型为DATETIME、DATE、TIMESTAMP、TIME和YEAR。
每个时间类型有一个有效值范围和一个"零"值,当指定不合法的MySQL不能表示的值时使用"零"值。
TIMESTAMP类型有专有的自动更新特性,将在后面描述。

类型 大小(字节) 范围 格式 用途
DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值
TIME 3 ‘-838:59:59’/‘838:59:59’ HH:MM:SS 时间值或持续时间
YEAR 1 1901/2155 YYYY 年份值
DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
TIMESTAMP 4 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 YYYYMMDD HHMMSS 混合日期和时间值,时间戳

字符串类型

字符串类型指CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM和SET。该节描述了这些类型如何工作以及如何在查询中使用这些类型。

类型 大小 用途
CHAR 0-255字节 定长字符串
VARCHAR 0-65535 字节 变长字符串
TINYBLOB 0-255字节 不超过 255 个字符的二进制字符串
TINYTEXT 0-255字节 短文本字符串
BLOB 0-65 535字节 二进制形式的长文本数据
TEXT 0-65 535字节 长文本数据
MEDIUMBLOB 0-16 777 215字节 二进制形式的中等长度文本数据
MEDIUMTEXT 0-16 777 215字节 中等长度文本数据
LONGBLOB 0-4 294 967 295字节 二进制形式的极大文本数据
LONGTEXT 0-4 294 967 295字节 极大文本数据

CHAR和VARCHAR类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。

BINARY和VARBINARY类类似于CHAR和VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值。
BLOB是一个二进制大对象,可以容纳可变数量的数据。有4种BLOB类型:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。它们只是可容纳值的最大长度不同。
有4种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT。这些对应4种BLOB类型,有相同的最大长度和存储需求。

字符数据

BINARY(N)

VARBINARY(N)
BINARY和VARBINARY存储的是二进制的字符串,而非字符型字符串。也就是说,BINARY和VARBINARY没有字符集的概念,对其排序和比较都是按照二进制值进行对比。BINARY(N)和VARBINARY(N)中的N指的是字节长度,对于BINARY(10),其可存储的字节固定为10。
CHAR(N)
VARCHAR(N)
CHAR(N)和VARCHAR(N)中N指的是的字符长度。对于CHAR(10),其可存储的字节视字符集的情况而定。

日期时间数据

日期和时间类型 字节 最小值 最大值 零值表示
date 4 1000-01-01 9999-12-31 0000-00-00
datetime 8 1000-01-01 00:00:00 9999-12-31 23:59:59 0000-00-00 00:00:00
timestamp 4 19700101080001 2038年的某个时刻 0000000000000000
time 3 -838:59:59 838:59:59 00:00:00
year 1 1901 2155 0000
每种日期时间类型都有一个有效值范围,拆除这个范围系统就会进行错误提示,并将以零值(上表为零值展示表)进行存储 **timestamp** 1.若想将timestamp查询的值返回为数字,应在timestamp列后添加"+0"。 2.系统会为timestamp字段自动创建默认值"current_timestamp(系统日期)"。如果有第二个timestamp,则默认值为零值。 3.MySQL规定timestamp类型字段只能有一列默认值为current_timestamp。 4.时区相关。当插入日期时,会先转换成本地时区后存放;从数据库取出时,需要将日期转换为本地时区后显示。 5.timestamp受MySQL版本和服务器SQLMode影响较大。本文是以MySQL5.0为例。 **year** 1.当应用只需要记录年份时,year比date更省空间 2.year有两种格式:yy和yyyy。yyyy的范围是1901~2155,yy的范围是1970~2069 00~69表示2000~2069,70~99表示1970~1999。 **datetime** 1.不严格语法:任何标点符号都可以做日期部分或时间部分的间隔符 eg:'38[11&23 11&30+12' 等同于 '38-11-23 11:30:12' 多能够正常插入。 2.没有间隔符的字符串,如果是合法的,也将成功保存。eg:19961119083028将会被成功保存为 1996-11-19 08:30:28 3.输入错误的时间将会报错并保存为零值。eg:1998-13-23 11:30:12 没有13月份,所以这是个错误的时间,将会报错,并保存为零值 ## 数值型数据 **DECIMAL** DECIMAL从MySQL 5.1引入,列的声明语法是DECIMAL(M,D)。在MySQL 5.1中,参量的取值范围如下: ·M是数字的最大数(精度)。其范围为1~65(在较旧的MySQL版本中,允许的范围是1~254),M 的默认值是10。 ·D是小数点右侧数字的数目(标度)。其范围是0~30,但不得超过M。 说明:float占4个字节,double占8个字节,decimail(M,D)占M+2个字节。 如DECIMAL(5,2) 的最大值为9 9 9 9 . 9 9,因为有7 个字节可用。 M 与D 对DECIMAL(M, D) 取值范围的影响 类型说明取值范围(MySQL < 3.23)取值范围(MySQL >= 3.23) MySQL < 3.23 MySQL >=3.23 DECIMAL(4, 1) -9.9 到 99.9 -999.9 到 9999.9 DECIMAL(5,1) -99.9 到 999.9 -9999.9 到 99999.9 DECIMAL(6,1) -999.9 到 9999.9 -99999.9 到 999999.9 DECIMAL(6,2) -99.99 到 999.99 -9999.99 到 99999.99 DECIMAL(6,3) -9.999 到 99.999 -999.999 到 9999.999 在MySQL 3.23 及以后的版本中,DECIMAL(M, D) 的取值范围等于早期版本中的DECIMAL(M + 2, D) 的取值范围。 当数值在其取值范围之内,小数位多了,则直接截断小数位。 若数值在其取值范围之外,则用最大(小)值对其填充。 ## 正负数问题 **SIGNED [INTEGER]** **UNSIGNED [INTEGER]** 拿tinyint字段来举例,unsigned后,字段的取值范围是0-255,而signed的范围是-128 - 127。 那么如果我们在明确不需要负值存在的情况下,通常是不要设置signed来支持负数的。 因为只支持正数会让存储空间大一倍(当然我这种表达可能不准确)。 严格讲,在性能上是有细微的差别的。 unsigned的性能更好。

java中的数据类型

##HashMap

来源:
###初识HashMap
之前的List,讲了ArrayList、LinkedList,最后讲到了CopyOnWriteArrayList,就前两者而言,反映的是两种思想:

  • (1)ArrayList以数组形式实现,顺序插入、查找快,插入、删除较慢
  • (2)LinkedList以链表形式实现,顺序插入、查找较慢,插入、删除方便

那么是否有一种数据结构能够结合上面两种的优点呢?有,答案就是HashMap。

HashMap是一种非常常见、方便和有用的集合,是一种键值对(K-V)形式的存储结构,下面将还是用图示的方式解读HashMap的实现原理,

###四个关注点在HashMap上的答案

关 注 点 结 论
HashMap是否允许空 Key和Value都允许为空
HashMap是否允许重复数据 Key重复会覆盖、Value允许重复
HashMap是否有序 无序,特别说明这个无序指的是遍历HashMap的时候,得到的元素的顺序基本不可能是put的顺序
HashMap是否线程安全 非线程安全

###添加数据

首先看一下HashMap的一个存储单元Entry:

static class Entry
implements Map.Entry
{ final K key; V value; Entry
next; int hash; ...}

LinkedList是一个双向链表,从HashMap的Entry看得出,Entry组成的是一个单向链表,因为里面只有Entry的后继Entry,而没有Entry的前驱Entry。用图表示应该是这么一个数据结构:

K key
V value
Entry<K,V>next
int hash

接下来,假设我有这么一段代码:

1 public static void main(String[] args)2 {3     Map
map = new HashMap
();4 map.put("111", "111");5 map.put("222", "222");6 }

看一下做了什么。首先从第3行开始,new了一个HashMap出来:

1 public HashMap() {2     this.loadFactor = DEFAULT_LOAD_FACTOR;3     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);4     table = new Entry[DEFAULT_INITIAL_CAPACITY];5     init();6 }

注意一下第5行的init()是个空方法,它是HashMap的子类比如LinkedHashMap构造的时候使用的。DEFAULT_INITIAL_CAPACITY为16,也就是说,HashMap在new的时候构造出了一个大小为16的Entry数组,Entry内所有数据都取默认值,如图示为:

看到new出了一个大小为16的Entry数组来。接着第4行,put了一个Key和Value同为111的字符串,看一下put的时候底层做了什么:

1 public V put(K key, V value) { 2     if (key == null) 3         return putForNullKey(value); 4     int hash = hash(key.hashCode()); 5     int i = indexFor(hash, table.length); 6     for (Entry
e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value;10 e.value = value;11 e.recordAccess(this);12 return oldValue;13 }14 }15 16 modCount++;17 addEntry(hash, key, value, i);18 return null;19 }
1 static int hash(int h) {2     // This function ensures that hashCodes that differ only by3     // constant multiples at each bit position have a bounded4     // number of collisions (approximately 8 at default load factor).5     h ^= (h >>> 20) ^ (h >>> 12);6     return h ^ (h >>> 7) ^ (h >>> 4);7 }
1 static int indexFor(int h, int length) { 2     return h & (length-1); 3 }

看一下put方法的几个步骤:

1、第2行~第3行就是HashMap允许Key值为空的原因,空的Key会默认放在第0位的数组位置上
2、第4行拿到Key值的HashCode,由于HashCode是Object的方法,因此每个对象都有一个HashCode,对这个HashCode做一次hash计算。按照JDK源码注释的说法,这次hash的作用是根据给定的HashCode对它做一次打乱的操作,防止一些糟糕的Hash算法产生的糟糕的Hash值,至于为什么要防止糟糕的Hash值,HashMap添加元素的最后会讲到
3、第5行根据重新计算的HashCode,对Entry数组的大小取模得到一个Entry数组的位置。看到这里使用了&,移位加快一点代码运行效率。另外,这个取模操作的正确性依赖于length必须是2的N次幂,这个熟悉二进制的朋友一定理解,因此注意HashMap构造函数中,如果你指定HashMap初始数组的大小initialCapacity,如果initialCapacity不是2的N次幂,HashMap会算出大于initialCapacity的最小2的N次幂的值,作为Entry数组的初始化大小。这里为了讲解方便,我们假定字符串111和字符串222算出来的i都是1
4、第6行~第14行会先判断一下原数据结构中是否存在相同的Key值,存在则覆盖并返回,不执行后面的代码。注意一下recordAccess这个方法,它也是HashMap的子类比如LinkedHashMap用的,HashMap中这个方法为空。另外,注意一点,对比Key是否相同,是先比HashCode是否相同,HashCode相同再判断equals是否为true,这样大大增加了HashMap的效率,对HashCode不熟悉的朋友可以看一下我的这篇文章讲讲
5、第16行的modeCount++是用于fail-fast机制的,每次修改HashMap数据结构的时候都会自增一次这个值
然后就到了关键的addEntry方法了:

void addEntry(int hash, K key, V value, int bucketIndex) {    Entry
e = table[bucketIndex]; table[bucketIndex] = new Entry
(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length);}
Entry(int h, K k, V v, Entry
n) { value = v; next = n; key = k; hash = h;}

假设new出来的Entry地址为0x00000001,那么,put(“111”, “111”)用图表示应该是这样的:

每一个新增的Entry都位于table[1]上,另外,里面的hash是rehash之后的hash而不是Key最原始的hash。看到table[1]上存放了111---->111这个键值对,它持有原table[1]的引用地址,因此可以寻址到原table[1],这就是单向链表。 再看一下put(“222”, “222”)做了什么,一张图就可以理解了:
新的Entry再次占据table[1]的位置,并且持有原table[1],也就是111---->111这个键值对。

至此,HashMap进行put数据的过程就呈现清楚了。不过还有一个问题,就是HashMap如何进行扩容,再看一下addEntry方法:

1 void addEntry(int hash, K key, V value, int bucketIndex) {2 Entry
e = table[bucketIndex];3 table[bucketIndex] = new Entry
(hash, key, value, e);4 if (size++ >= threshold)5 resize(2 * table.length);6 }

看到第4行~第5行,也就是说在每次放置完Entry之后都会判断是否需要扩容。这里不讲扩容是因为HashMap扩容在不正确的使用场景下将会导致死循环,这是一个值得探讨的话题,也是我工作中实际遇到过的一个问题,因此下一篇文章将会详细说明为什么不正确地使用HashMap会导致死循环。

###删除数据
有一段代码:

1 public static void main(String[] args)2 {3     Map
map = new HashMap
();4 map.put("111", "111");5 map.put("222", "222");6 map.remove("111");7 }

第6行删除元素,看一下删除元素的时候做了什么,第4行~第5行添加了两个键值对就沿用上面的图,HashMap删除指定键值对的源代码是:

1 public V remove(Object key) { 2     Entry
e = removeEntryForKey(key); 3 return (e == null ? null : e.value); 4 }
1 final Entry
removeEntryForKey(Object key) { 2 int hash = (key == null) ? 0 : hash(key.hashCode()); 3 int i = indexFor(hash, table.length); 4 Entry
prev = table[i]; 5 Entry
e = prev; 6 7 while (e != null) { 8 Entry
next = e.next; 9 Object k;10 if (e.hash == hash &&11 ((k = e.key) == key || (key != null && key.equals(k)))) {12 modCount++;13 size--;14 if (prev == e)15 table[i] = next;16 else17 prev.next = next;18 e.recordRemoval(this);19 return e;20 }21 prev = e;22 e = next;23 }24 25 return e;26 }

分析一下remove元素的时候做了几步:

1、根据key的hash找到待删除的键值对位于table的哪个位置上
2、记录一个prev表示待删除的Entry的前一个位置Entry,e可以认为是当前位置
3、从table[i]开始遍历链表,假如找到了匹配的Entry,要做一个判断,这个Entry是不是table[i]:
(1)是的话,也就是第14行~第15行,table[i]就直接是table[i]的下一个节点,后面的都不需要动
(2)不是的话,也就是第16行~第17行,e的前一个Entry也就是prev,prev的next指向e的后一个节点,也就是next,这样,e所代表的Entry就被踢出了,e的前后Entry就连起来了

remove(“111”)用图表示就是:

整个过程只需要修改一个节点的next的值即可,非常方便。
##修改数据
修改元素也是put,看一下源代码:

1 public V put(K key, V value) { 2     if (key == null) 3         return putForNullKey(value); 4     int hash = hash(key.hashCode()); 5     int i = indexFor(hash, table.length); 6     for (Entry
e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value;10 e.value = value;11 e.recordAccess(this);12 return oldValue;13 }14 }15 modCount++;16 addEntry(hash, key, value, i);17 return null;18 }

这个其实前面已经提到过了,第6行~第14行就是修改元素的逻辑,如果某个Key已经在数据结构中存在的话,那么就会覆盖原value,也就是第10行的代码。

###插入数据
所谓"插入元素",在我的理解里,一定是基于数据结构是有序的前提下的。像ArrayList、LinkedList,再远点说就是数据库,一条一条都是有序的。

而HashMap,它的顺序是基于HashCode,HashCode是一个随机性很强的数字,所以HashMap中的Entry完全是随机存放的。HashMap又不像LinkedHashMap这样维护了插入元素的顺序,所以对HashMap这个数据结构谈插入元素是没有意义的。

所以,HashMap并没有插入的概念。

###再谈HashCode的重要性
前面讲到了,HashMap中对Key的HashCode要做一次rehash,防止一些糟糕的Hash算法生成的糟糕的HashCode,那么为什么要防止糟糕的HashCode?

糟糕的HashCode意味着的是Hash冲突,即多个不同的Key可能得到的是同一个HashCode,糟糕的Hash算法意味着的就是Hash冲突的概率增大,这意味着HashMap的性能将下降,表现在两方面:

1、有10个Key,可能6个Key的HashCode都相同,另外四个Key所在的Entry均匀分布在table的位置上,而某一个位置上却连接了6个Entry。这就失去了HashMap的意义,HashMap这种数据结构性高性能的前提是,Entry均匀地分布在table位置上,但现在确是1 1 1 1 6的分布。所以,我们要求HashCode有很强的随机性,这样就尽可能地可以保证了Entry分布的随机性,提升了HashMap的效率。

2、HashMap在一个某个table位置上遍历链表的时候的代码:

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

看到,由于采用了"&&"运算符,因此先比较HashCode,HashCode都不相同就直接pass了,不会再进行equals比较了。HashCode因为是int值,比较速度非常快,而equals方法往往会对比一系列的内容,速度会慢一些。Hash冲突的概率大,意味着equals比较的次数势必增多,必然降低了HashMap的效率了。

###HashMap的table为什么是transient的
一个非常细节的地方:

transient Entry[] table;

看到table用了transient修饰,也就是说table里面的内容全都不会被序列化,不知道大家有没有想过这么写的原因?

在我看来,这么写是非常必要的。因为HashMap是基于HashCode的,HashCode作为Object的方法,是native的:

public native int hashCode();

这意味着的是:HashCode和底层实现相关,不同的虚拟机可能有不同的HashCode算法。再进一步说得明白些就是,可能同一个Key在虚拟机A上的HashCode=1,在虚拟机B上的HashCode=2,在虚拟机C上的HashCode=3。

这就有问题了,Java自诞生以来,就以跨平台性作为最大卖点,好了,如果table不被transient修饰,在虚拟机A上可以用的程序到虚拟机B上可以用的程序就不能用了,失去了跨平台性,因为:

1、Key在虚拟机A上的HashCode=100,连在table[4]上

2、Key在虚拟机B上的HashCode=101,这样,就去table[5]上找Key,明显找不到

整个代码就出问题了。因此,为了避免这一点,Java采取了重写自己序列化table的方法,在writeObject选择将key和value追加到序列化的文件最后面:

private void writeObject(java.io.ObjectOutputStream s)        throws IOException{Iterator
> i = (size > 0) ? entrySet0().iterator() : null;// Write out the threshold, loadfactor, and any hidden stuffs.defaultWriteObject();// Write out number of bucketss.writeInt(table.length);// Write out size (number of Mappings)s.writeInt(size); // Write out keys and values (alternating)if (i != null) { while (i.hasNext()) { Map.Entry
e = i.next(); s.writeObject(e.getKey()); s.writeObject(e.getValue()); } }}

而在readObject的时候重构HashMap数据结构:

private void readObject(java.io.ObjectInputStream s)         throws IOException, ClassNotFoundException{// Read in the threshold, loadfactor, and any hidden stuffs.defaultReadObject();// Read in number of buckets and allocate the bucket array;int numBuckets = s.readInt();table = new Entry[numBuckets];    init();  // Give subclass a chance to do its thing.// Read in size (number of Mappings)int size = s.readInt();// Read the keys and values, and put the mappings in the HashMapfor (int i=0; i

一种麻烦的方式,但却保证了跨平台性。

这个例子也告诉了我们:尽管使用的虚拟机大多数情况下都是HotSpot,但是也不能对其它虚拟机不管不顾,有跨平台的思想是一件好事。

###HashMap和Hashtable的区别
HashMap和Hashtable是一组相似的键值对集合,它们的区别也是面试常被问的问题之一,我这里简单总结一下HashMap和Hashtable的区别:

1、Hashtable是线程安全的,Hashtable所有对外提供的方法都使用了synchronized,也就是同步,而HashMap则是线程非安全的

2、Hashtable不允许空的value,空的value将导致空指针异常,而HashMap则无所谓,没有这方面的限制

3、上面两个缺点是最主要的区别,另外一个区别无关紧要,我只是提一下,就是两个的rehash算法不同,Hashtable的是:

private int hash(Object k) {    // hashSeed will be zero if alternative hashing is disabled.    return hashSeed ^ k.hashCode();}

这个hashSeed是使用sun.misc.Hashing类的randomHashSeed方法产生的。HashMap的rehash算法上面看过了,也就是:

static int hash(int h) {    // This function ensures that hashCodes that differ only by    // constant multiples at each bit position have a bounded    // number of collisions (approximately 8 at default load factor).    h ^= (h >>> 20) ^ (h >>> 12);    return h ^ (h >>> 7) ^ (h >>> 4);}

R中的数据类型

R中对象

在R语言中,一定要记住:

Everything in R is an object; Every object in R has a class;一切皆对象,每个对象都有类;

R中所有对象都有2个内在属性:类型和长度;

类型:是对象元素的基本种类,如:数值型、字符型、复数型和逻辑性(FALSE或TRUE)。
还有不常用的类型,但是并不表示数据,例如函数或表达式;
**长度:**是对象中元素的个数。

> x=1> mode(x)[1] "numeric"> length(x)[1] 1

R的数据类型

“numeric” is a class corresponding to numeric vectors. The other vector basic classes are “logical”, “integer”, “complex”, “character”, “raw”, “list” and “expression”.

R没有标量,它通过使用各种类型的向量来存储数据。常见的数据类型(class)有:

-----------------------------------------------------------------------------------------------          类型 说明-----------------------------------------------------------------------------------------------1 字符(charactor) 常常被引号包围2 数字(numeric) 实数向量3 整数(integer) 整数向量4 逻辑(logical) 逻辑向量(TRUE=T, FALSE=F,NA)5 复数(complex) 复数6 列表(list)-----------------------------------------------------------------------------------------------

–无论什么数字,缺失值总是用NA(不可用)来表示,空值用null表示。

–大数字可以用指数表示 N=2.1e23;
–字符型用双引号,引用双引号时要用\转意;用单引号时,引用单引号要用\转义;

R的对象类别

数据类型组成了对象,包括:向量、因子、数组、矩阵、数据框、时间序列(ts)、列表等;

下表给出了表示数据的对象的类别概览:

---------------------------------------------------------------------------------------------------------------------对象 类型 是否允许同一个对象中有多种类型?---------------------------------------------------------------------------------------------------------------------向量		向量是一个变量;因子		因子是一个分类变量;数组		数组是一个k维的数据表;矩阵		矩阵是数组的一个特例,其维数k=2;数据框		数据框是由一个或几个向量和(或)因子构成,他们必须是等长的,但可以是不同的数据类型;时间序列(ts)	ts表示时间序列列表---------------------------------------------------------------------------------------------------------------------

**注意:**数组或者矩阵中的所有元素必须是同一种类型的;

转载地址:https://blog.csdn.net/qingqing7/article/details/78465519 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:数据基础---mysql数据库操作(一)---基础操作
下一篇:编程基础---不同编程语言学习---不同编程语言中文件存取相关操作

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月01日 11时07分12秒