Windows Linux Subsystem

说明

WSL是一个兼容层,类似反过来的Wine,但更底层

  • Linux、Windows程序不兼容,是因为二者内核提供的接口不同

    • ls/dir命令
      • 在Linux下调用getdents内核调用
      • 在Windows下调用NtQueryDirectoryFile内核调用
  • WSL提供Linux内核接口,并转换为NT内核接口

    • 在WSL中执行ls仍然调用getdents
    • WSL收到请求,将系统调用转换为NT内核接口 NTQueryDirectoryFile
    • NT内核收到WSL请求,返回执行结果
    • WSL将结果包装后返回
  • 毕相较于真正Linux系统仍然有不足

    • Docker等涉及未实现的内核特性软件如法使用
    • Raw socket相关相关操作容易出错
    • I/O性能相对孱弱

Cygwin对比

wsl_architecture

  • Cygwin提供了完整的POSIX系统调用API(以运行库 Cygwin*.dll形式提供,但是仍然工作在User Mode

    • Cygwin将POSIX系统调用转换为Win32 API(因为其架设在 Win32子系统上),很多内核操作(如:fork)受限于 Win32实现

    • Linux应用程序必须链接到Cynwin*.dll,需要修改源码 重新编译后才能执行,这样应用程序才不会直接请求内核, 而是调用Cygwin运行库,且编译出的可执行文件为 Win32 PE格式封装,只能在Windows下执行

  • WSL中Linux应用程序进程被包裹在Pico Process中,其发出的 所有系统调用会被直接送往Kernel Mode中的 lxcore.syslxss.sys

    • WSL将POSIX系统调用转换为更底层的NP API调用(WSL和 Win32平行,直接架设在NT内核上)

    • 可以直接执行ELF格式封装的Linux可执行程序

启用

  • 控制面板 ->
  • 程序和功能 ->
  • 启用或关闭windows功能 ->
  • 适用于Linux的Windows子系统

其他

使用

进入WSL

除在图形界面中点击图标,以默认参数启动,还可以在terminal (cmd/powershell等)自定义进入WSL参数

  • wsl.exe:打开默认发行版中默认shell
  • distroname.exe:打开指定发行版中默认shell
  • bash.exe:打开默认发行版中bash shell
  • 这些应用程序默认在path中,可以直接执行

版本管理

  • wslconfig.exe可以用于管理多个子系统的发行版

WSL、Windows互操作

文件

  • Windows所有盘符挂载在WSL中/mnt目录下

  • WSL中所有数据存放在%HOME%/AppData/Local/Packages/{linux发行包名}/LocalState/rootfs

    • 不要在Windows下直接修改,造成权限错误

命令

  • 在cmd中直接调用WSL命令
    1
    2
    PS> wsl [-e] ls -al
    // wsl带参数执行
  • 在WSL中调用Windows命令行程序(在$PATH中)

    1
    2
    $ which ipconfig.exe
    $ ipconfig.exe
  • 在WSL中启动Windows应用(在$PATH中)

    1
    $ notepad.exe
  • 通过pipes通信

    1
    2
    $ cat foo.txt | clip.exe
    PS> ipconfig | wsl grep IPv4

端口、环境变量

  • WSL与Windows共享端口(NT内核?)
  • WSL继承Windows的部分环境变量,如:PATH

Terminal推荐

  • wsl-terminal: 专为WSL开发的终端模拟器,基于minttywslbridge,稳定 易用

  • ConEmu:老牌终端模拟器, 功能强大

  • Hyper:基于Electron的跨平台 终端模拟器

WSL-Terminal

  • WSL-Terminal中包含一些快捷工具

    • tools目录中包含一些脚本,可以通过wscripts.exe 执行修改注册列表,添加一些功能
      • 添加WSL中vim、emacs等打开到右键菜单
      • 添加在WSL中打开文件夹到右键菜单
    • run-wsl-file.exe可以用于在快捷执行wsl脚本,只需要 将其选择为文件打开方式
    • vim.exe可以用WSL中vim打开任何文件,当然一般是配合 tools/中脚本在右键注册后使用
  • 配置

    • 配置文件etc/wsl-terminal.conf
    • 主题文件etc/themes/
    • mintty配置文件etc/mintty

其他问题

文件权限问题

  • 在WSL中,windows实现了两种文件系统用于支持不同使用场景

VolFs

VolFs:着力于在windows文件系统上提供完整的linux文件系统特性 ,通过各种手段实现了对InodesDirectory EntriesFile ObjectsFile DescriptorsSpecial File Types 的支持

  • 为支持Inodes,VolFS会把文件权限等信息保存在 NTFS Extended Attributes

    • 在Windows中新建的文件缺少此扩展参数,有些编辑器也会 在保存文件是去掉这些附加参数
    • 所以不要在Windows中修改WSL文件,否则VolFs无法正确 获得文件metadata
  • WSL中/就是VolFs文件系统

DrvFs

DrvFs:着力提供于Windows系统的互操作性,从Windows的文件权限 (即文件->属性->安全选项卡中的权限)推断出文件对应Linux权限

  • 所有windows盘符挂在在WSL中/mnt是都使用DrvFs文件系统

  • 由于DrvFs文件权限继承机制很微妙,结果就是所有文件权限 都是0777

    • 所以ls结果都是绿色的
    • 早期DrvFs不支持metadata,在Build 17063之后支持 文件写入metadata,但是需要重新挂载磁盘
  • 可以通过设置DrvFs metadata设置默认文件权限

    1
    2
    3
    4
    5
    $ sudo umount /mnt/e
    $ sudo mount -t drvfs E: /mnt/e -o metadata
    // 此时虽然支持文件权限修改,但默认权限仍然是*0777*
    $ sudo mount -t drvfs E: /mnt/e -o metadata,uid=1000,gid=1000,umask=22,fmask=111
    // 此时磁盘中默认文件权限为*0644*
  • 更合适的方式是通过/etc/wsl.conf配置DrvFs自动挂载属性

AutoMatically Configuring WSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 # `/etc/wsl.conf`
[automount]
# 是否自动挂载
enabled = true
# 是否处理`/etc/fstab`文件
mountFsTab = true
# 挂载路径
root = /mnt/
# DrvFs挂载选项,若需要针对不同drive配置,建议使用`/etc/fstab`
options = "metadata,umask=023,dmask=022,fmask=001"
[network]
generateHosts = true
generateResolvConf = true
[interop]
# 是否允许WSL载入windows进程
enabled = true
appendWindowsPath = true
  • 如果需要给不同盘符设置不同挂载参数,需要修改 /etc/fstab

    1
    E: /mnt/e drvfs rw,relatime,uid=1000,gid=1000,metadata,umask=22,fmask=111 0 0

系统安装常识

Win10/8系统

在已经安装win10/8系统的机器上安装linux系统,需要注意

  • 电源设置中关闭快速启动,其有可能影响grub开机引导
  • boot中关闭secureboot

U盘启动盘

Win下usbwriter和ultraiso都可以制作,但是

  • usbwriter用于archlinux的制作
    • 打开
    • 启动->写入磁盘映像
    • 写入方式—USB-HDD+
  • ultraiso用于ubuntu和centos的制作

archlinux使用ultraiso和usbwriter制作u盘不同,用usbwriter的 u盘好像有隐藏分区,只能看到一个很小容量的盘,使用ultraiso 制作的启动盘好像没用

bootloader(启动引导器)

双系统根据需求选择

Windows使用EasyBCD引导Linux

EasyBCD

Windows下的一个引导器

NeoGrub是EasyBCD自带的grub(不是已经更加常用的grub2), 可以配置EasyBCD在无法找到其他Linux系统的grub的情况下, 将控制权转移给NeoGrub,使用其引导系统,这个NeoGrub就和 普通的grub类似,可以引导多个系统

  • 有很多文件系统格式无法识别,能够确认可识别的只有ext2, 不能的有xfsext4,其中ext4会被错认为ex2fs, 不能正确读取分区文件,
  • 据说只能识别标准格式的分区,无法识别lvm格式的分区

因此,如果需要使用NeoGrub引导系统,需要注意分区格式问题

分区

  • Ubuntu:”安装启动引导器的设备”设置为sdXY,即磁盘X的 Y分区,这样不会更改默认引导程序,此时会安装grub,但是 没有覆盖磁盘中默认的win引导,重启后会自动进入win,可以 使用easybsd自动检测引导分区,直接就能添加引导条目
  • Centos:选择不安装启动引导器grub(centos无法选择引导器 安装在某个分区),EasyBCD无法自动检测Linux的引导分区, 需要手动配置EasyBCD自带的NeoGrub,并添加此条目

引导文件编写

添加NeoGrub条目之后,其配置文件仍然为空,因此选择此条目之后 会直接进入grub控制台,在此尝试boot其他系统。

  • root (hdX,X表示磁盘代号,<tab>给出的候选分区, 确定boot分区Y

  • root (hdX,Y):指定根目录(中间有空格)

  • kernel /vmlinuz<tab>给出候选的内核文件,确定内核 文件(一般会有一个rescue的文件肯定不是)

  • kernel /vmlinuz---------- ro root=/dev/sdXY ro quite vga=791: 其中X不再是hd后面的数字而是字母,YrootY+1

  • initrd /initramfstab给出候选initrd镜像文件

  • initrd /initramfs---------

  • boot

如果成功进入系统,说明以上的命令可行,记录以上真正有效的 命令,据此修改NeoGrub配置文件

title name
    root (hdX,Y)
    kernel /vmlinuz------------ ro root=/dev/sdXY ro quite vga=791
    initrd /initramfs----------
    boot

可以在EasyBCD中打开配置文件,也可以直接修改C:/NST/menu.ls 文件

Linux使用grub引导Windows

grub可以引导包括win、linux在内的大部分系统,而且大部分教程 都是默认这种方式

  • Ubuntu:”安装启动引导器的设备”设置为sdX,即直接安装在 磁盘的最开始,修改默认引导
  • Centos:选择安装启动引导器
  • Archlinux:要手动安装os-prober(检测已安装系统)、grub, 配置grub启动文件,具体方法参见grub使用或是archlwiki

分区

  • /根分区:唯一必须分区

  • boot分区:一般建议单独给boot分区

    • 根分区位于lvm、RAID,或者是文件系统不能被引导程序 识别,单独的boot分区可以设为其他格式
    • 可以以只读方式挂载boot分区,防止内核文件被破坏
    • 多系统可以共享内核
  • swap分区

    • 如果设置swap分区,一般设置为内存的1~2倍
    • 建议使用交换文件代替,这样的比较灵活,而且如果内存 够用的话可以不启用swap文件,这样提升效率、保护硬盘

      • fallocate -l SIZE /SWAPFILENAME:创建交换文件 ,其中SIZE后面要跟单位(M、G)
      • chmod 600 /SWAPFILENAME:更改交换文件权限
      • mkswap /SWAPFILENAME:设置交换文件
      • swapon /SWAPFILENAME:启用一次交换文件

      或修改/etc/fstab文件,每次开机默认启用交换文件

      /SWAPFILENAME none swap defaults 0 0 /SWAPFILENAME swap swap sw 0 0

      前者是Arch教程,后者是Centos7教程,这两种写法应该 是一样的

PC硬件

SSD硬盘

接口/插槽类型

M.2接口/插槽

M.2曾经称为NGFF

  • M.2接口的SSD有很多长度版本,常用的是42/60/80mm这三种 版本

  • M.2根据接口(金手指)/插槽的缺口位置、针脚数量可以 分为两类

    • B Key(Socket2):缺口偏左,左侧6个针脚宽,对应SSD 金手指(接口)左侧6个针脚(插槽上针脚数-1为5)

    • M Key(Socket3):缺口偏右,右侧5个针脚宽,对应SSD 金手指(接口)右侧5个针脚(插槽上针脚数-1为4)

B&M:大部分M.2SSD跳过了B Key金手指,采用B&M类型的 金手指,这种类型的金手指有偏左、偏右均有一个缺口,兼容 B KeyM Key类型的插槽

SATA串口

mSATA串口

IDE并口

总线标准

PCI-EPCI-Express

  • PCI-E 3.0*2
  • PCI-E 3.0*4:总带宽有32Gbps

SATA3.0

SATA3.0带宽仅有6Gbps,采用此类总线标准的SSD速度较慢

传输协议

AHCI

AHCISATA总线对应的传输协议标准(逻辑设备接口标准), 可以看作是一种SATA的优化驱动

可以在BIOS中开启该协议

NVMe

NVMe是针对PCI-E总线SSD的告诉传输协议

就目前而言,支持NVMe协议的M.2SSD一定采用了PCI-E 3.0*4 总线标准,但反之不一定

总结

M.2SSD

  • B&M金手指
    • SATA总线:<600MB/s
    • PCI-E 3.0*2总线:<1000MB/s
  • M Key金手指
    • PCI-E 3.0*2总线:<1000MB/s
    • PCI-E 3.0*4总线
      • 支持NVMe协议:可以超过2000MB/s
      • 不支持NVMe协议:<1500MB/s

台灯知识

3种常见光源

卤素灯(钨丝灯)

  • 原理:真空中电阻(钨丝)通电发热发光

    • 由爱迪生发明的钨丝灯改良而来
    • 为了增加寿命,灯中添加(最好2000h)
  • 光效:13lm/W(比较好的卤素灯)

  • 色温:2700K(一般),颜色比较发黄

节能灯

  • 原理:汞蒸气激发三基色荧光粉发光

    • 工作时破碎有可能导致汞中毒
    • 品质不好的节能灯有电磁辐射
  • 光效:50-70lm/W

  • 色温:5000~6000K,高色温居多

LED灯

  • 原理

  • 光效:100lm/W

  • 色温:3000~6500K,台灯上一般4500~6000K

总结

卤素灯 节能灯 LED灯
安全性 发热、易碎 汞蒸气、易碎 安全
显色指数 99~100 80(一般) 80+
能耗(800lm计) 17h 83h 125h
色温 2700K 5000~6000K 4500~6000K
价格

台灯国家标准

GB 9473:台灯国家标准,引用以下标准

  • GB 7000:灯具通用安全标准
  • ZB K 74 003:螺口式灯座技术条件
  • ZB K 74 002:插口式灯座技术标准
  • GB 2313:管型荧光灯镇流器
  • SG 286:灯具油漆涂层
  • GB 7003:灯具电镀滑雪覆盖层

包括以下一些要求

  • 台灯处于正常工作位置时,眼睛距台灯基座底平面垂直方向 400mm,离光源中心水平距离600mm处,水平方向应看不到灯罩 内壁及光源

  • 台灯处于正常工作位置时,以光源为中心的垂直投影点为中心, 测量在中心前方1/3圆周上的照度

    • 300mm处:不得<250lx
    • 500mm处:不得<120lx
  • 光照面照度比3.5以下(台灯要照得均匀)

其他常见问题

频闪

低频闪烁才有危害,此问题基本不用担心,基本是炒作;且使用手机 判断是否有频闪不可靠,取决于手机CMOS的工作方式

  • 卤素灯:卤素灯发热发光,直接通220v交流电,因为靠发热, 有余晖效应,其亮度变化很小

  • 节能灯:

  • LED灯:需在直流电下工作,需要把交流电变为恒定直流,电流 大小不变则原则上没有闪烁问题(产品品质有保证)

    • 但是带有调光功能(亮度、色温),LED可能有频闪,因为 其是通过调整LED的发光时间调整亮度(高频开光LED灯)

蓝光

卤素灯蓝光很少,主要是红外部分,不会有蓝光危害问题

LED蓝光

  • 单个LED只能发出单一颜色的光
    • 为了得到白光(复合光),常用的办法是用蓝色LED芯片 产生蓝色光,然后激发荧光粉将部分蓝色光变为黄光,与 剩下的蓝光混合成白光
    • 因此白光LED表面是黄色的。
  • 且LED的色温越高,蓝光成分越高
国标

国家新规GB7000.1-2015规定了蓝光问题:

  • 带有整体式LED或LED模块的灯具应根据IEC/TR 62778进行蓝光 危害评估

  • 对于儿童可移式灯具、小夜灯,在200mm距离处测得蓝光危害 等级不得超过RG1

蓝光危害

  • 蓝光波长短、能量高,(过量情况下)对视网膜有害

  • 影响褪黑素分泌,蓝光过多时,人容易兴奋睡不着

显色

显色指数:光源对物体的显色能力,用太阳光作为参考,其显色指数 为100。显色指数越高,色彩还原能力越强越接近阳光。

台灯选择

颜色要求高

卤素灯,显色指数可以达到99-100,基本就是太阳光。影响卤素灯的 优劣的因素

  • 亮度:国内已经禁止60W的卤素灯(白炽灯)
  • 紫外线透出:材料不好,紫外线容易透出
  • 寿命:冷态灯泡电阻小,瞬间电流很大,材料不好容易坏
  • 欧司朗、飞利浦较好,不推荐国产

普通阅读

LED,省电、美观。不适合的LED灯

  • LED可以看见:容易照成重影,导致视觉疲劳
  • 亮度、色温可调:对频闪介意者不适合
  • 色温高/低:睡觉/熬夜不适合,4500~5000K比较好

GDB

概述

GDB是GNU发布的UNIX程序调试工具,可以帮助完成

  • 自定义运行程序
  • 让程序在指定断点处停止
  • 检查停止程序的内部状态
  • 动态改变程序执行环境

调试准备

  • gdb会根据文件名后缀确认调试程序的语言,设置gdb自身语言 环境,并随之改变语言环境

    • 如果程序由多种语言编译而成,gdb能根据不同语言自动 切换语言环境
  • 可以使用infoshowset命令查看设置当前语言环境

  • 可能会缺少glibc-debuginfo,无法显示全部调试信息

    • OpenSuse安装可能需要添加源

编译选项

  • 调试C++/C程序时,要求在编译时就把调试信息添加到可执行 文件中

    • 使用gcc/g++的-g参数添加源码信息
    • 否则调试时将看不见函数名、变量名,而是全部以运行时 内存地址代替
  • 关闭优化选项

    • 否则优化器会删改程序,使得某些变量不能访问、取值错误

启动GDB调试

  • gdb <exe>:直接gdb启动可执行文件<exe>
  • gdb <exe> core:用gdb同时调试可执行文件、core文件
  • gdb <exe> <PID>:给定进程PID,gdb自动attach该进程, 调试已经运行的程序
    • 也可不指定PID,直接关联源码,在gdb中使用attach命令 手动挂接,调试已运行程序

配置调试环境

源文件

  • gdb启动程序后,gdb会在PATH、当前目录中搜索程序的源文件 -directory/-d添加源文件搜索路径
  • 在gdb交互中使用dir[rectory] <dirname>指定源文件搜索 路径
  • gdb中使用list/l检查gdb是否能列出源码

程序运行环境

在gdb中run程序之前,可能需要设置、查看程序运行环境

  • 程序运行参数

    • set <args>:设置程序运行时参数
    • show <args>:查看已设置参数
  • 环境变量

    • path [<dir>]:查看、添加<dir>PATH
    • show paths:查看PATH
    • set environment var [=value]:设置环境变量
    • show environment var:查看环境变量
  • 工作目录

    • cd <dir>:等价于cd
    • pwd:等价于pwd
  • 输入输出

    • info terminal:显示程序所有终端模式
    • run > outfile:重定向程序输出
    • tty /dev/:指定输入、输出设备

参数

  • -s <file>/-symbols <file>:从指定文件读取符号表
  • -se <file>:从指定文件读取符号表信息,并用于可执行文件
  • -c <file>/-core <file>:调试core dump产生的core文件
  • -d <dir>/-directory <dir>:添加源文件搜索路径
    • 默认搜索路径是PATH

表达式

  • 表达式语法应该是当前所调试语言的语法

  • gdb另外还支持一些通用操作符

    • @:指定存储在堆上、动态分配内存大小的长度

      1
      2
      3
      4
      int *array = (int*)malloc(len * sizeof(int));
      # 源码
      > p *array@len
      # 打印数组`*array`
    • :::指定某个具体文件、函数中的变量

      • 常用于取出被隐藏的全局变量
    • [(type)]<addr>:内存地址addr处为一个type类型 变量

停止点

设置Breakpoint

break

break:在某位置设置断点

1
2
b[reak] [<probe_modifier>] [<location>] [thread <threadno>]
[if <condition>]
  • probo_modifier:命令处于probe point时需要
    • -probe:通用、自动推测探测类型
    • -probe-stapSystemTap探测
    • -probe-dtraceDTrace探测
  • location:设置断点位置
    • 缺省:当前栈帧中的执行位置
    • linespecs冒号分隔绝对位置参数(逐步精确)
      • 其中可以包括:文件名、函数名、标签名、行号
    • +-[n]标识相对当前行位置
    • *addr:在程序运行的内存地址addr
    • explicit:类似于linespecs,通过多个参数给出
  • threadno:设置断点的线程号
    • 缺省断点设置在所有线程上
    • gdb分配的线程编号,通过info threads查询
    • 多线程被gdb停止时,所有运行线程都被停止,方便 查看运行程序总体情况
  • if <condition>:满足条件condition时在断点处停止, 缺省立即停止程序
1
2
3
4
5
6
7
8
9
> break factorial.c:fact:the_top
# factorial.c文件、fact函数、the_top标签
> break +2
# 当前行之后两行
> break *main + 4
# `main`函数4B之后
> break -source factorial.c -function fact
-label the_top
# explicit方式,同linespecs
  • 标签:C++/C中配合goto使用(label_1:
  • <tab>:补全函数名称、函数原型(重载函数)
  • 若指定函数名称不唯一,gdb会列出函数原型供选择

tbreak

tbreak:设置一次性断点,生效后立刻被删除

rbreak

rbreak:对匹配正则表达式的行均设置断点

condition

condition:修改、设置断点n生效条件

1
2
> condition <bpnum> <condition>
# 设置断点`bpnum`生效条件为`condition`

ignore

ignore:设置忽略断点n次数

1
2
> ignore <bpnum> <count>`
# 忽略断点`bpnum` `count`次

设置Watchpoint

watch

watch:为某个表达式设置观察点

  • 观察某个表达式,其值发生变化时停止程序
1
2
> watch [-l|-location] <expr> [thread <threadno>]
[mask <maskvalue>]
  • -l:表示将expression视为地址,观察表达式指向的地址 中的值
  • expr:表达式、*开头表示的内地地址
  • threadno:同break
  • maskvalue:观察值mask,只观察部分bit值
1
2
3
4
> watch foo mask 0xffff00ff
# 观察`foo`
> watch *0xdeadbeef 0xffffff00
# 观察内存地址`0xdeadbeef`处的值
  • 取决于系统,观察点有可能以硬件、软件方式实现,大部分 PowerPC、X86支持硬件观察点
    • 软件观察点通过逐步测试变量实现,程序执行速度慢得多
    • 硬件观察点不会降低程序执行速度
  • mask参数需要架构支持

rwatch

rwatch:为某表达式设置读观察点

  • 当表达式值被读取时停止程序
1
2
> rwatch [-l|location] <expression> [thread <threadno>]
[mask <maskvalue>]

awatch

awatch:为某表达式设置写观察点

  • 当表达式值被修改时停止程序
1
2
> awatch [-l|-location] <expression> [thread <threadno>]
[mask <maskvalue>]

设置Catchpoint

catch

catch:设置捕捉点捕捉事件

1
> cat[ch] <event>
  • 通用事件
    • catch:捕获异常
    • exec:调用系统调用exec(仅HP-UX下有用)
    • fork:调用系统调用fork(仅HP-UX下有用)
    • load [<libname>]:载入共享库(仅HP-UX下有用)
    • unload [<libname>]:卸载共享库(仅HP-UX下有用)
    • throw:抛出异常
    • rethrow:再次抛出异常
    • vfork:调用系统调用vfork时(仅HP-UX下有用)
    • syscall:被系统调用
  • Ada
    • assert:捕获Ada断言失败
    • exception [arg]:捕获Ada和参数匹配的异常
    • handlers:捕获Ada异常
  • 捕捉点:捕捉程序运行时的一些事件,如:载入动态链接库、 异常事件<event>发生时停止程序

tcatch

tcatch:设置一次性捕捉点,程序停止后自动删除

信号处理

gdb可以在调试程序时处理任何信号,可以要求gdb在收到指定信号后 停止正在执行的程序以供调试

1
> handle <signal> <keywords>
  • signal:需要处理的信号
    • 信号可选使用SIG开头
    • 可以定义需要处理的信号范围SIGIO-SIGKILL
    • 可以使用all表示处理所有信号
  • keywords:被调试程序接收到信号后gdb的动作
    • nostop:不停止程序运行,打印消息告知收到信号
    • stop:停止程序运行
    • print:显示信息

      todo

    • noprint:不显示信息
    • noigore:gdb处理信号,交给被调试程序处理
    • nopass/ignore:gdb截留信号,不交给被调试程序处理

维护停止点

clear

clear:清除指定位置断点

1
> clear [<location>]
  • location:指定清除断点位置
    • 缺省:清除当前栈帧中正在执行行断点
    • 其余设置同break

commands

commands:设置断点生效时自动执行命令

  • 利于自动化调试
1
2
3
> commands [bpnum]
> ...command-list...
> end
  • bpnum:断点序号
    • 缺省:最近设置的断点
    • 5-7:指定断点区间

执行

文件

list

list:打印源代码

1
> l[ist] [<location>]
  • location:输入的源代码
    • 缺省/+:显示当前行后源代码
    • -:显示当前行前源代码
    • [[start], [end]]:显示范围内源代码(缺省表当前行)
    • 指定单行类似breaklocation参数
  • 一般默认显示10行,可以通过set listsize <count>设置

search/forward-search:从打印出最后行开始正则搜索源码

1
> search/forward-search <regexp>

reverse-search:从打印出的最后行反向正则搜索源码

1
> reverse-search <regexp>

directory

directory:指定源文件搜索路径

1
> dir[rectory] <dir>
  • dir:源文件路径
    • 可以指定多个搜索路径,linux下:分隔,windows下;
    • 缺省:清除自定义源文件搜索路径信息
  • show查看当前

继续执行

run

run:启动程序开始调试

1
> r[un] [<args>]
  • args
    • 缺省:上次runset args指定的参数
    • 参数中包含的*...将被用于执行的shell扩展 (需清除参数,使用set args置空)
  • 允许输入、输出重定向

continue

continue:继续执行直到之后断点、程序结束

1
> c[ontinue]/fg [<ignore-count>]
  • ignore-count:执行直到之后第ingore-count个断点 (忽略ignore-count-1个断点)
    • 缺省:1

step/next

step/next:单步/单行跟踪

1
> s[tep]/[n]ext [<count>]
  • count:执行代码行数
    • 缺省:1
  • 有函数调用,step进入函数(需要函数被编译有debug信息)

    next不进入函数调用,视为一代代码

stepi/nexti

stepi/nexti:单条intruction(机器指令、汇编语句)跟踪

1
> s[tep]i/n[ext]i [<count>]
  • count:执行指令条数
    • 缺省:1

finish

finish:运行直到当前栈帧/函数返回,并打印函数返回值、存入 值历史

return

return:强制函数忽未执行语句,并返回

1
> return [expr]
  • expr:返回的表达式值
    • 缺省:不返回值

util

until/u:运行程序直到退出循环体

jump

jump:修改程序执行顺序,跳转至程序其他执行处

1
> jump [<location>]
  • location:同break
  • jump不改变当前程序栈中内容,所以在函数间跳转时,函数 执行完毕返回时进行弹栈操作式必然发生错误、结果错误、 core dump,所以最好在同一个函数中跳转
  • 事实上,jump就是改变了寄存器中保存当前代码所在的内存 地址,所以可以通过set $pc更改跳转执行地址

call

call:强制调用函数,并打印函数返回值(void不显示)

1
> call <expr>
  • print也可以调用函数,但是如果函数返回voidprint 显示并存储如历史数据中

查看信息

1
> p[rint] [/<f>]<expr>[=value]
  • f:输出格式
    • x:16进制格式
    • d:10进制格式
    • u:16进制格式显示无符号整形
    • o:8进制格式
    • t:2进制
    • a:16进制
    • c:字符格式
    • f:浮点数格式
    • i:机制指令码
    • s
  • expr:输出表达式、gdb环境变量、寄存器值、函数
    • 输出表达式:gdb中可以随时查看以下3种变量值
      • 全局变量:所有文件可见
      • 静态全局变量:当前文件可见
      • 局部变量:当前scope可见
      • 局部变量会隐藏全局变量,查找被隐藏变量可以使用 ::指定
    • 编译程序时若开启优化选项,会删改程序,使得某些变量 不能访问
    • 输出环境变量、寄存器变量时,需要使用$前缀
    • 函数名称:强制调用函数,类似call
  • value:修改被调试程序运行时变量值
    • 缺省:打印变量值
    • =是C++/C语法,可以根据被调试程序改为相应程序赋值 语法
    • 可以通过set var实现(当变量名为gdb参数时,必须 使用set var
  • 每个print输出的表达式都会被gdb记录,gdb会以$1$2 等方式记录下来,可以使用此编号访问以前的表达式

examine

examine/x:查看内存地址中的值

1
> examine/x /[<n/f/u>] <addr>
  • 输出参数:可以三者同时使用
    • n:查看内存的长度(单元数目
    • f:展示格式,同print
      • u:内存单元长度
      • b:单字节
      • h:双字节
      • w:四字节,默认
      • g:八字节
  • addr:内存地址
1
2
> x/3uh 0x54320
# 从内存地址`0x54320`开始,16进制展示3个双字节单位

display

display:设置自动显示变量,程序停止时变量会自动显示

1
> display/[<fmt>] [<expr>] [<addr>]
  • fmt:显示格式
    • print
  • exprt:表达式
  • addr:内存地址
1
2
3
> display/i $pc
# `$pc`:gdb环境变量,表示指令地址
# 单步跟踪会在打印程序代码时,同时打印出机器指令

undisplay

undisplay:删除自动显示

1
> undisplay [<num>]
  • num:自动显示编号
    • info查看
    • 可以使用a-b表示范围

查看、设置GDB环境

info

停止点

  • locals:打印当前函数中所有局部变量名、值
  • args:打印当前函数参数名、值
  • b[reak][points] [n]:查看断点
  • watchpoints [n]:列出所有观察点
  • catch:打印当前函数中异常处理信息
  • line [<location>]:查看源代码在内存中地址
  • f[rame]:可以打印更详细当前栈帧信息
    • 大部分为运行时内存地址
  • display:查看display设置的自动显示信息

线程

  • threads查看在正在运行程序中的线程信息

信号

  • info signals/handle:查看被gdb检测的信号
  • frame:查看当前函数语言
  • source:查看当前文件程序语言

其他

  • terminal:显示程序所有终端模式
  • registers [reg]:查看寄存器情况
    • 缺省:除浮点寄存器外所有寄存器
    • 还可以通过print实现
  • all-registers:查看所有寄存器情况(包括浮点寄存器)

set

停止点

  • step-mode [on] [off]:开启/关闭step-mode模式
    • 程序不会因为没有debug信息而不停止,方便查看机器码

环境

  • language [lang]:设置当前语言环境
  • args [<args>]:设置被调试程序启动参数
  • environment var [=value]:设置环境变量
  • listsize <count>:设置最大打印源码行数
  • var <var=value>:修改被调试程序运行时变量值
    • 还可以通过print变量修改

print

  • address [on/off]:打开地址输出

    • 即程序显示函数信息时,显示函数地址
    • 默认打开
  • array [on/off]:打开数组显示

    • 打开数组显示后,每个函数占一行,否则以逗号分隔
    • 默认关闭
  • elements <num-of-elements>:设置数组显示最大长度

    • 0:不限制数组显示
  • null-stop [on/off]:打开选项后,显示字符串时遇到结束符 则停止显示

    • 默认关闭
  • pretty [on/off]:打开选项后,美化结构体输出

    • 打开选项后,结构体成员单行显示,否则逗号分隔
  • sevenbit-strings [on/off]:字符是否按照/nnn格式显示

    • 打开后字符串/字符按照/nnn显示

      todo

  • union [on/off]:显示结构体时是否显示其内联合体数据

    • 打开时联合体显示结构体各种值,否则显示...
  • object [on/off]:打开选项时,若指针对象指向其派生类, gdb自动按照虚方法调用的规则显示输出,否则gdb忽略虚函数表

    • 默认关闭
  • static-members [on/off]:是否对象中静态数据成员

    • 默认打开
  • vtbl [on/off]:选项打开,gdb将用比较规则的格式输出 虚函数表

    • 默认关闭

show

执行

  • args:查看被调试程序启动参数
  • paths:查看gdb中PATH
  • environtment [var]:查看环境变量
  • directories:显示源文件搜索路径
  • convenience:查看当前设置的所有环境变量
  • address:查看是否打开地址输出
  • array:查看是否打开数组显示
  • element:查看再打数组显示最大长度
  • pretty:查看是否美化结构体输出
  • sevenbit-strings [on/off]:查看字符显示是否打开
  • union:查看联合体数据输出方式
  • object:查看对象选项设置
  • static-members:查看静态数据成员选项设置
  • vtbl:查看虚函数显示格式选项设置

shell

shell:执行shell命令

1
> shell <shell-cmd>
  • cd:等同> shell cd
  • pwd
  • make <make-args>
  • Linux:使用环境变量SHELL/bin/sh执行命令
  • Windows:使用cmd.exe执行命令

path

path:添加路径至gdb中PATH(不修改外部PAHT

1
> path <dir>

GDB环境

环境变量

可以在gdb调试环境中自定义环境变量保存调试程序中需要的数据

1
2
3
4
5
6
7
8
> set $foo = *object_ptr
# 设置环境变量
> show convenience
# 查看当前设置的所有环境变量
> set $i=0
> print bar[$i++] -> contents
# 环境变量、程序变量交互使用
# 只需要回车重复上条命令,环境变量自动累加,逐个输出变量
  • 环境变量使用$开头(定义时也需要)
  • gdb会在首次使用时创建该变量,在以后使用直接对其赋值
  • 环境变量没有类型,可以定义任何类型,包括结构体、数组

寄存器

寄存器:存放了程序运行时数据

  • ip:程序当前运行指令地址
  • sp:程序当前堆栈地址
1
2
3
4
5
6
> info registers [<reg-name>]
# 输出寄存器值,缺省除浮点外所有
> info all-registers
# 输出所有寄存器值
> print $eip
# 输出寄存器`eip`值

其他

  • disassemble:查看程序当前执行的机器码
    • 此命令会dump当前内存中指令
  • si[gnal] <signal>:产生信号量发给被调试程序
    • signal:取值1-15,即Unix信号量
    • 此命令直接发送信号给被调试程序,而系统信号则是发送给 被调试程序,但由gdb截获

调试设置

delete

delete:删除断点(缺省)、自动输出表达式等

1
2
> delete [breakpoints] [bookmark] [checkpoints]
[display] [mem] [tracepoints] [tvariable] [num]
  • breakpoints:删除断点
  • bookmark:从书签中删除书签
  • checkpoints:删除检查点
  • display:取消程序停止时某些输出信息
  • mem:删除存储区
  • tracepoint:删除指定追踪点
  • tvariable:删除追踪变量

  • num

    • 缺省:删除所有断点/自动输出/书签等
    • 指定的序号
      • info查看
      • 可以使用a-b表示范围

disable

disable:禁用断点(缺省)、输出表达式等

1
2
3
> disable [breakpoints] [display] [frame-filter]
[mem] [pretty-printer] [probes] [type-printer]
[unwinder] [xmethod] [num]

breakpoints

禁用断点

1
> disable [breakpoints] [num]
  • 缺省:禁用所有断点
  • 仅指定的序号(info查看)

display

禁用程序停止时某些输出信息

frame-filter

禁用某些帧过滤器

mem

禁用存储区

pretty-printer

禁用某些打印美化

probes

禁用探测

type-printer

禁用某些类型打印

unwinder

禁用某些unwinder

xmethod

禁用某些xmethod

enable

enable:启用断点(缺省)、输出表达式等

1
2
3
> enable [breakpoints] [display] [frame-filter]
[mem] [pretty-printer] [probes] [type-printer]
[unwinder] [xmethod] [num]

breakpoints

启用断点

1
2
> enable [breakpoints] [num] [once] [delete]`
- `[delete]`:启用断点,生效后自动被删除
  • num:断点序号
    • 缺省:启用所有断点
  • once:启用断点一次
  • delete:启用生效后自动删除
  • count:启用断点count

display

启用程序停止时某些输出信息

frame-filter

启用某些帧过滤器

mem

启用存储区

pretty-printer

启用某些打印美化

probes

启用探测

type-printer

启用某些类型打印

unwinder

启用某些unwinder

xmethod

启用某些xmethod

backtrace

backtrace:打印函数栈

1
> backtrace/bt [-][<n>]
  • -:打印栈底信息
    • 缺省:打印栈顶信息
  • n:打印栈数量
    • 缺省:打印当前函数调用栈所有信息
  • 一般而言,程序停止时,最顶层栈就是当前函数栈

frame

frame:切换当前栈

1
> f[rame] [<n>]
  • n:切换到第n个栈帧
    • 缺省打印当前栈编号、断点信息(函数参数、行号等)

up

up:上移当前栈帧

1
> up [<n>]
  • n:上移n层栈帧
    • 缺省:上移1层

down

down:下移当前栈帧

1
> down [<n>]
  • n:下移n层栈帧
    • 缺省:下移1层

GDB命令大全

aliases

breakpoints

data

files

internals

obscure

running

stack

status

support

tracepoints

user-defined

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
$ gdb tst
# `gdb`启动可执行文件tst
(gdb) l
# `l`:list,列出源码
(gdb)
# 直接回车:重复上次命令
(gdb) break 16
# `break [n]`:在第`n`行设置断点
(gdb) break func
# `break func`:在函数`func`入口处设置断点
(gdb) info break
# `info break`:查看断点信息
(gdb) r
# `r`:run,执行程序,会自动在断点处停止
(gdb) n
# `n`:next,单条语句执行
(gdb) c
# `c`:continue,继续执行(下个断点、程序结束为止)
(gdb) p i
# `p i`:print,打印变量i的值
(gdb) bt
# `bt`:查看函数栈
(gdb) finish
# `finish`:退出**函数**
(gdb) 1
# `q`:quit,退出gdb

Hadoop HDFS

HDFS设计模式

  • 数据读取、写入

    • HDFS一般存储不可更新的文件,只能对文件进行数据追加 ,也不支持多个写入者的操作
    • 认为一次写入、多次读取是最高效的访问模式
    • namenode将metadata存储在内存中,所以文件系统所能 存储的文件总数受限于NameNode的内存
  • 适合模式

    • 每次分析都涉及数据集大部分数据甚至全部,因此读取整个 数据集的大部分数据、甚至全部,因此读取整个数据集的 时间比读取第一条记录时间延迟更重要
    • HDFS不适合要求地时间延迟的数据访问应用,HDFS是为 高数据吞吐量应用优化的,可能会提高时间延迟
  • 硬件:HDFS无需高可靠硬件,HDFS被设计为即使节点故障也能 继续运行,且不让用户察觉

数据块

和普通文件系统一样,HDFS同样也有块的概念,默认为64MB

  • HDFS上的文件也被划分为块大小的多个chunk,作为独立的存储 单元,但是HDFS中小于块大小的文件不会占据整个块空间

  • 对分布式文件系统块进行抽象的好处

    • 文件大小可以大于网络中任意一个磁盘的容量
    • 使用抽象块而非整个文件作为存储单元,简化了存储子系统 的设计
      • 简化存储管理,块大小固定,计算磁盘能存储块数目 相对容易
      • 消除了对元素见的顾虑,文件的元信息(权限等), 不需要和块一起存储,可由其他系统单独管理
  • 块非常适合用于数据备份,进而提供数据容错能力、提高可用性

NameNode

HDFS系统中的管理者

  • 集中存储了HDFS的元信息Metadata

    • 维护文件系统的文件树、全部的文件和文件夹的元数据
    • 管理文件系统的Namespace:创建、删除、修改、列出所有 文件、目录
  • 执行数据块的管理操作

    • 把文件映射到所有的数据块
    • 创建、删除数据块
    • 管理副本的Placement、Replication
  • 负责DataNode的成员管理

    • 接受DataNode的Registration
    • 接受DataNode周期性的Heart Beat

Hadoop上层模块,根据NameNode上的元信息,就可以知道每个数据块 有多少副本、存放在哪些节点上,据此分配计算任务,通过 Move Computation to Data,而不是移动数据本身,减少数据 移动开销,加快计算过程

Metadata的保存

为了支持高效的存取操作,NameNode把所有的元信息保存在主内存, 包括文件和数据块的命名空间、文件到数据块的映射、数据块副本 的位置信息。文件命名空间、文件到数据块的映射信息也会持久化 到NameNode本地文件系统

  • FsImage:命名空间镜像文件,保存整个文件系统命名空间、 文件到数据块的映射信息
  • EditLog:编辑日志文件,是一个Transaction Log文件,记录了 对文件系统元信息的所有更新操作:创建文件、改变文件的 Replication Factor

NameNode启动时,读取FsImage、EditLog文件,把EditLog的所有 事务日志应用到从FsImage文件中装载的旧版本元信息上,生成新的 FsImage并保存,然后截短EditLog

NameNode可恢复性

多个文件系统备份

备份文件系统元信息的持久化版本

  • 在NameNode写入元信息的持久化版本时,同步、atomic写入多个 文件系统(一般是本地磁盘、mount为本地目录的NFS)
Secondary NameNode

运行Secondary NameNode:负责周期性的使用EditLog更新 FsImage,保持EditLog在一定规模内

  • Seconadary NameNode保存FsImage、EditLog文件副本, 每个一段时间从NameNode拷贝FsImage,和EditLog文件进行 合并,然后把更新后的FsImage复制回NameNode

  • 若NameNode宕机,可以启动其他机器,从Secondary NameNode获得FsImage、EditLog,恢复宕机之前的最新的 元信息,当作新的NameNode,也可以直接作为主NameNode

  • Secondary NameNode保存的出状态总是滞后于主节点,需要 从NFS获取NameNode部分丢失的metadata

  • Secondary NameNode需要运行在另一台机器,需要和主 NameNode一样规模的CPU计算能力、内存,以便完成元信息 管理

想要从失效的NameNode恢复,需要启动一个拥有文件系统数据副本的 新NameNode,并配置DataNode和客户端以便使用新的NameNode

  • 将namespace的映像导入内存中
  • 重做编辑日志
  • 接收到足够多的来自DataNode的数据块报告,并退出安全模式

DataNode

HDFS中保存数据的节点

  • 数据被切割为多个数据块,以冗余备份的形式存储在多个 DataNode中,因此不需要再每个节点上安装RAID存储获得硬件上 可靠存储支持。DataNode之间可以拷贝数据副本,从而可以重新 平衡每个节点存储数据量、保证数据可靠性(保证副本数量)

  • DotaNode定期向NameNode报告其存储的数据块列表,以备使用者 通过直接访问DataNode获得相应数据

  • 所有NameNode和DataNode之间的通讯,包括DataNode的注册、 心跳信息、报告数据块元信息,都是由DataNode发起请求,由 NameNode被动应答和完成管理

HDFS高可用性

对于大型集群,NN冷启动需要30min甚至更长,因此Hadoop2.x中添加 对高可用性HA(high-availability)的支持

  • 配置Active-Standby NameNode

    • ANN失效后,SNN就会接管任务并开始服务,没有明显中断
    • ANN、SNN应该具有相同的硬件配置
  • NN之间需要通过高可用的共享存储(JounalNode)实现 Editlog共享

    • JN进程轻量,可以和其他节点部署在同一台机器
    • JN至少为3个,最好为奇数个,这样JN失效$(n-1)/2$个时 仍然可以正常工作
    • SNN接管工作后,将通读共享编辑日志直到末尾,实现与ANN 状态同步
  • DN需要同时向两个NN发送数据块处理报告,因为数据块映射信息 存储在NN内存中

  • 客户端需要使用特定机制处理NN失效问题,且机制对用户透明

  • 如果两个namenode同时失效,同样可以冷启动其他namenode, 此时情况就和no-HA模式冷启动类似

注意:HA模式下,不应该再次配置Secondary NameNode

Note that, in an HA cluster, the Standby NameNode also performs checkpoints of the namespace state, and thus it is not necessary to run a Secondary NameNode, CheckpointNode, or BackupNode in an HA cluster. In fact, to do so would be an error. This also allows one who is reconfiguring a non-HA-enabled HDFS cluster to be HA-enabled to reuse the hardware which they had previously dedicated to the Secondary NameNode.

Failover Controller

故障转移控制器系统中有一个新实体管理者管理namenode之间切换,

  • Failover Controller最初实现基于Zookeeper,可插拔

  • 每个namenode运行着一个Failover Controller,用于监视宿主 namenode是否失效(heart beat机制), 并在失效时进行故障 切换

    • 管理员也可以手动发起故障切换,称为平稳故障转移
  • 在非平稳故障切换时,无法确切知道失效namenode是否已经停止 运行,如网速慢、网络切割均可能激发故障转移,引入fencing 机制

    • 杀死namenode进程
    • 收回对共享存储目录权限
    • 屏蔽相应网络端口
    • STONITH:shoot the other node in the head,断电

联邦HDFS

NameNode在内存中保存文件系统中每个文件、数据块的引用关系, 所以对于拥有大量文件的超大集群,内存将成为系统扩展的瓶颈, 2.x中引入的联邦HDFS可以添加NameNode实现扩展

  • 每个NameNode维护一个namespace volume,包括命名空间的 元数据、命令空间下的文件的所有数据块、数据块池
  • namespace volume之间相互独立、不通信,其中一个NameNode 失效也不会影响其他NameNode维护的命名空间的可用性
  • 数据块池不再切分,因此集群中的DataNode需要注册到每个 NameNode,并且存储来自多个数据块池的数据块

Hadoop文件系统

Hadoop有一个抽象问的文件系统概念,HDFS只是其中的一个实现, Java抽象类org.apche.hadoop.fs.FileSystem定义了Hadoop中的 一个文件系统接口,包括以下具体实现

文件系统 URI方案 Java实现 描述
Local file fs.LocalFileSystem 使用客户端校验和本地磁盘文件系统,没有使用校验和文件系统RawLocalFileSystem
HDFS hdfs hdfs.DistributedFileSystem HDFS设计为与MapReduce结合使用实现高性能
HFTP hftp hdfs.HftpFileSystem 在HTTP上提供对HDFS只读访问的文件系统,通常与distcp结合使用,以实现在运行不同版本HDFS集群之间复制数据
HSFTP hsftp hdfs.HsftpFileSystem 在HTTPS上同以上
WebHDFS Webhdfs hdfs.web.WebHdfsFileSystem 基于HTTP,对HDFS提供安全读写访问的文件系统,为了替代HFTP、HFSTP而构建
HAR har fs.HarFileSystem 构建于其他文件系统之上,用于文件存档的文件系统,通常用于需要将HDFS中的文件进行存档时,以减少对NN内存的使用
hfs kfs fs.kfs.kosmosFileSystem CloudStore(前身为Kosmos文件系统)类似于HDFS(GFS),C++编写
FTP ftp fs.ftp.FTPFileSystem 由FTP服务器支持的文件系统
S3(原生) S3n fs.s3native.NativeS3FileSystem 由Amazon S3支持的文件系统
S3(基于块) S3 fs.sa.S3FileSystem 由Amazon S3支持的文件系统,以块格式存储文件(类似于HDFS),以解决S3Native 5GB文件大小限制
分布式RAID hdfs hdfs.DistributedRaidFileSystem RAID版本的HDFS是为了存档而设计的。针对HDFS中每个文件,创建一个更小的检验文件,并允许数据副本变为2,同时数据丢失概率保持不变。需要在集群中运行一个RaidNode后台进程
View viewfs viewfs.ViewFileSystem 针对其他Hadoop文件系统挂载的客户端表,通常用于联邦NN创建挂载点

文件系统接口

Hadoop对文件系统提供了许多接口,一般使用URI方案选择合适的 文件系统实例进行交互

命令行接口

1
2
3
4
5
6
7
8
$ hadoop fs -copyFromLocal file hdfs://localhost/user/xyy15926/file
# 调用Hadoop文件系统的shell命令`fs`
# `-copyFromLocalFile`则是`fs`子命令
# 事实上`hfds://localhost`可以省略,使用URI默认设置,即
# 在`core-site.xml`中的默认设置
# 类似的默认复制文件路径为HDFS中的`$HOME`

$ hadoop fs -copyToLocal file file

HTTP

  • 直接访问:HDFS后台进程直接服务来自于客户端的请求

    • 由NN内嵌的web服务器提供目录服务(默认50070端口)
    • DN的web服务器提供文件数据(默认50075端口)
  • 代理访问:依靠独立代理服务器通过HTTP访问HDFS

    • 代理服务器可以使用更严格的防火墙策略、贷款限制策略

C

Hadoop提供libhdfs的C语言库,是Java FileSystem接口类的 镜像

  • 被写成访问HDFS的C语言库,但其实可以访问全部的Hadoop文件 系统
  • 使用Java原生接口(JNI)调用Java文件系统客户端

FUSE

Filesystem in Userspace允许把按照用户空间实现的文件系统整合 成一个Unix文件系统

  • 使用Hadoop Fuse-DFS功能模块,任何一个Hadoop文件系统可以 作为一个标准文件系统进行挂载
    • Fuse_DFS使用C语言实现,调用libhdfs作为访问HDFS的接口
  • 然后可以使用Unix工具(lscat等)与文件系统交互
  • 还可以使用任何编程语言调用POSIX库访问文件系统

读文件

  1. 客户端程序使用要读取的文件名、Read Range的开始偏移量、 读取范围的程度等信息,询问NameNode

  2. NameNode返回落在读取范围内的数据块的Location信息,根据 与客户端的临近性(Proximity)进行排序,客户端一般选择 最临近的DataNode发送读取请求

具体实现如下

  1. 客户端调用FileSystem对象open方法,打开文件,获得 DistributedFileSystem类的一个实例

  2. DistributedFileSystem返回FSDataInputStream类的实例, 支持文件的定位、数据读取

    • DistributedFileSystem通过RPC调用NameNode,获得 文件首批若干数据块的位置信息(Locations of Blocks)
    • 对每个数据块,NameNode会返回拥有其副本的所有DataNode 地址
    • 其包含一个DFSInputStream对象,负责管理客户端对HDFS 中DataNode、NameNode存取
  3. 客户端从输入流调用函数read,读取文件第一个数据块,不断 调用read方法从DataNode获取数据

    • DFSInputStream保存了文件首批若干数据块所在的 DataNode地址,连接到closest DataNode
    • 当达到数据块末尾时,DFSInputStream关闭到DataNode 的连接,创建到保存其他数据块DataNode的连接
    • 首批数据块读取完毕之后,DFSInputStream向NameNode 询问、提取下一批数据块的DataNode的位置信息
  4. 客户端完成文件的读取,调用FSDataInputStream实例close 方法关闭文件

写文件

  • 客户端询问NameNode,了解应该存取哪些DataNode,然后客户端 直接和DataNode进行通讯,使用Data Transfer协议传输数据, 这个流式数据传输协议可以提高数据传输效率

  • 创建文件时,客户端把文件数据缓存在一个临时的本地文件上, 当本地文件累计超过一个数据块大小时,客户端程序联系 NameNode,NameNode更新文件系统的NameSpace,返回Newly Allocated数据块的位置信息,客户端根据此信息本文件数据块 从临时文件Flush到DataNode进行保存

具体实现如下:

  1. 客户端调用DistributedFileSystemcreate方法

    • DistributedFileSystem通过发起RPC告诉NameNode在 其NameSpace创建一个新文件,此时新文件没有任何数据块
    • NameNode检查:文件是否存在、客户端权限等,检查通过 NameNode为新文件创建新记录、保存其信息,否则文件创建 失败
  2. DistributedFileSystem返回FSDataOutputStream给客户端

    • 其包括一个DFSOutputStream对象,负责和NameNode、 DataNode的通讯
  3. 客户端调用FSDataOutputStream对象write方法写入数据

    • DFSOutputStream把数据分解为数据包Packet,写入内部 Data Queue
    • DataSteamer消费这个队列,写入本地临时文件中
    • 当写入数据超过一个数据块大小时,DataStreamer请求 NameNode为新的数据块分配空间,即选择一系列合适的 DataNode存放各个数据块各个副本
    • 存放各个副本的DataNode形成一个Pipeline,流水线上的 Replica Factor数量的DataNode接收到数据包之后转发给 下个DataNode
    • DFSOutputStream同时维护数据包内部Ack Queue,用于 等待接受DataNode应答信息,只有某个数据包已经被流水线 上所有DataNode应答后,才会从Ack Queue上删除
  4. 客户端完成数据写入,调用FSDataOutputStreamclose 方法

    • DFSOutputStream把所有的剩余的数据包发送到DataNode 流水线上,等待应答信息
    • 最后通知NameNode文件结束
    • NameNode自身知道文件由哪些数据块构成,其等待数据块 复制完成,然后返回文件创建成功

Hadoop平台上的列存储

列存储的优势

  • 更少的IO操作:读取数据的时候,支持Prject Pushdown,甚至 是Predicate Pushdown,可大大减少IO操作

  • 更大的压缩比:每列中数据类型相同,可以针对性的编码、压缩

  • 更少缓存失效:每列数据相同,可以使用更适合的Cpu Pipline 编码方式,减少CPU cache miss

RCFile

Record Columnar File Format:FB、Ohio州立、中科院计算所合作 研发的列存储文件格式,首次在Hadoop中引入列存储格式

  • 允许按行查询,同时提供列存储的压缩效率的列存储格式

    • 具备相当于行存储的数据加载速度、负载适应能力
    • 读优化可以在扫描表格时,避免不必要的数据列读取
    • 使用列维度压缩,有效提升存储空间利用率
  • 具体存储格式

    • 首先横向分割表格,生成多个Row Group,大小可以由用户 指定
    • 在RowGroup内部,按照列存储一般做法,按列把数据分开, 分别连续保存
      • 写盘时,RCFile针对每列数据,使用Zlib/LZO算法进行 压缩,减少空间占用
      • 读盘时,RCFile采用Lazy Decompression策略,即用户 查询只涉及表中部分列时,会跳过不需要列的解压缩、 反序列化的过程

ORC存储格式

Optimized Row Columnar File:对RCFile优化的存储格式

  • 支持更加丰富的数据类型

    • 包括Date Time、Decimal
    • Hive的各种Complex Type,包括:Struct、List、Map、 Union
  • Self Describing的列存储文件格式

    • 为Streaming Read操作进行了优化
    • 支持快速查找少数数据行
  • Type Aware的列存储文件格式

    • 文件写入时,针对不同的列的数据类型,使用不同的编码器 进行编码,提高压缩比
      • 整数类型:Variable Length Compression
      • 字符串类型:Dictionary Encoding
  • 引入轻量级索引、基本统计信息

    • 包括各数据列的最大/小值、总和、记录数等信息
    • 在查询过程中,通过谓词下推,可以忽略大量不符合查询 条件的记录

文件结构

一个ORC文件由多个Stripe、一个包含辅助信息的FileFooter、以及 Postscript构成

Stripe

每个stripe包含index data、row data、stripe footer

  • stripe就是ORC File中划分的row group

    • 默认大小为256MB,可扩展的长度只受HDFS约束
    • 大尺寸的strip、对串行IO的优化,能提高数据吞吐量、 读取更少的文件,同时能把减轻NN负担
  • Index Data部分

    • 包含每个列的极值
    • 一系列Row Index Entry记录压缩模块的偏移量,用于跳转 到正确的压缩块的位置,实现数据的快速读取,缺省可以 跳过10000行
  • Row Data部分;包含每个列的数据,每列由若干Data Stream 构成

  • Stripe Footer部分

    • Data Stream位置信息
    • 每列数据的编码方式

包含该ORCFile文件中所有stripe的元信息

  • 每个Stripe的位置
  • 每个Stripe的行数
  • 每列的数据类型
  • 还有一些列级别的聚集结果,如:记录数、极值、总和
Postscript
  • 用来存储压缩参数
  • 压缩过后的Footer的长度

Parquet

灵感来自于Google关于Drenel系统的论文,其介绍了一种支持嵌套 结构的列存储格式,以提升查询性能

支持

Parquet为hadoop生态系统中的所有项目,提供支持高压缩率的 列存储格式

  • 兼容各种数据处理框架

    • MapReduce
    • Spark
    • Cascading
    • Crunch
    • Scalding
    • Kite
  • 支持多种对象模型

    • Avro
    • Thrift
    • Protocol Buffers
  • 支持各种查询引擎

    • Hive
    • Impala
    • Presto
    • Drill
    • Tajo
    • HAWQ
    • IBM Big SQL

Parquet组件

  • Storage Format:存储格式,定义了Parquet内部的数据类型、 存储格式

  • Object Model Converter:对象转换器,由Parquet-mr实现, 完成对象模型与Parquet数据类型的映射

    • 如Parquet-pig子项目,负责把内存中的Pig Tuple序列化 并按存储格式保存为Parquet格式的文件,已经反过来, 把Parquet格式文件的数据反序列化为Pig Tuple
  • Object Model:对象模型,可以理解为内存中的数据表示,包括 Avro、Thrift、Protocal Buffer、Hive Serde、Pig Tuple、 SparkSQL Internal Row等对象模型

Parquet数据schema

数据schema(结构)可以用一个棵树表达

  • 有一个根节点,根节点包含多个Feild(子节点),子节点可以 包含子节点

  • 每个field包含三个属性

    • repetition:field出现的次数

      • required:必须出现1次
      • optional:出现0次或1次
      • repeated:出现0次或多次
    • type:数据类型

      • primitive:原生类惬
      • group:衍生类型
    • name:field名称

  • Parquet通过把多个schema结构按树结构组合,提供对复杂类型 支持

    • List、Set:repeated field
    • Map:包含键值对的Repeated Group(key为Required)
  • schema中有多少叶子节点,Parquet格式实际存储多少列, 父节点则是在表头组合成schema的结构

Parquet文件结构

  • HDFS文件:包含数据和元数据,数据存储在多个block中
  • HDFS Block:HDFS上最小的存储单位
  • Row Group:按照行将数据表格划分多个单元,每个行组包含 一定行数,行组包含该行数据各列对应的列块
    • 一般建议采用更大的行组(512M-1G),此意味着更大的 列块,有毅力在磁盘上串行IO
    • 由于可能依次需要读取整个行组,所以一般让一个行组刚好 在一个HDFS数据块中,HDFS Block需要设置得大于等于行组 大小
  • Column Chunk:每个行组中每列保存在一个列块中
    • 行组中所有列连续的存储在行组中
    • 不同列块使用不同压缩算法压缩
    • 列块存储时保存相应统计信息,极值、空值数量,用于加快 查询处理
    • 列块由多个页组成
  • Page:每个列块划分为多个Page
    • Page是压缩、编码的单元
    • 列块的不同页可以使用不同的编码方式

HDFS命令

用户

  • HDFS的用户就是当前linux登陆的用户

Hadoop组件

Hadoop Streaming

Keras 后端

Keras后端

Keras是一个模型级的库,提供了快速构建深度学习网络的模块

  • Keras并不处理如张量乘法、卷积等底层操作,而是依赖于某种 特定的、优化良好的张量操作库

  • Keras依赖于处理张量的库就称为“后端引擎”

    • [Theano][Theano]:开源的符号主义张量操作框架,由 蒙特利尔大学LISA/MILA实验室开发
    • [Tensorflow][Tensorflow]:符号主义的张量操作框架, 由Google开发
    • [CNTK][CNTK]:由微软开发的商业级工具包
  • Keras将其函数统一封装,使得用户可以以同一个接口调用不同 后端引擎的函数

切换后端

  • 修改Keras配置文件
  • 定义环境变量KERAS_BACKEND覆盖配置文件中设置(见python 修改环境变量的3中方式)

Keras后端抽象

可以通过Keras后端接口来编写代码,使得Keras模块能够同时在 Theano和TensorFlow两个后端上使用

  • 大多数张量操作都可以通过统一的Keras后端接口完成,不必 关心具体执行后端
1
2
3
4
5
6
7
8
9
10
11
12
13
from keras import backend as K

input = K.placeholder(shape=(2, 4, 5))
input = K.placeholder(shape=(None, 4, 5))
input = K.placeholder(ndim=3)
# 实例化输出占位符

val = np.random.random((3, 4, 5))
var = K.variable(value=val)
# 实例化共享变量
# 等价于`tf.Variable`、`theano.shared`
var = K.zeros(shape=(3, 4, 5))
var = K.ones(shape=(3, 4, 5))

配置相关函数

backend

返回当前后端

epsilon

1
K.epsilon()

返回数字表达式使用fuzz factor

set_epsilon

1
K.set_epsilon(e(float))

设置模糊因子的值

floatx

1
K.floatx()

返回默认的浮点数数据类型

  • float16
  • float32
  • float64

set_floatx

1
2
3
K.set_floatx(
floatx="float16"/"float32"/"float64"
)

设置默认的浮点数数据类型(字符串表示)

cast_to_floatx

1
K.cast_to_floatx(x)

将NDA转换为默认的Keras floatx类型(的NDA)

  • 例子

    1
    2
    3
    4
    5
    6
    7
    8
    K.floatx()
    # "float32"
    arr = np.array([1.0, 2.0], dtype="flaot64")
    arr.dtype
    # "float64"
    new_arr = K.cast_to_float(arr)
    new_arr.dtype
    # "float32"

image_data_format

1
K.image_data_format()

返回图像的默认维度顺序

  • channels_last
  • channels_first

set_image_data_format

1
2
3
K.set_image_data_format(
data_format="channel_first"/"channel_last"
)

设置图像的默认维度顺序

辅助函数

is_keras_tensor

判断是否是Keras Tensor对象

1
2
3
4
5
6
7
8
9
10
11
np_var = np.array([1, 2])
K.is_keras_tensor(np_var)
# False
keras_var = K.variable(np_var)
K.is_keras_tensor(keras_var)
# A variable is not a Tensor.
# False
keras_placeholder = K.placeholder(shape=(2, 4, 5))
K.is_keras_tensor(keras_placeholder)
# A placeholder is a Tensor.
# True

get_uid

1
K.get_uid(prefix=''/str)

获得默认计算图的uid

  • 说明

    • 依据给定的前缀提供一个唯一的UID
  • 参数

    • prefix:图的可选前缀
  • 返回值:图的唯一标识符

reset_uids

1
K.reset_uids()

重置图的标识符

clear_session

1
K.clear_session()

结束当前的TF计算图,并创建新图

  • 说明
    • 有效的避免模型/网络层的混乱

manual_variable_initialization

1
2
3
K.manual_variable_initialization(
value=False/True
)

设置变量手动初始化标志

  • 参数

    • value

      • False:默认,变量在实例时出事的(由其默认值 初始化)

      • True:用户自行初始化,如 tf.initialize_all_variables()

learning_phase

返回学习阶段标致

  • 返回值:布尔张量

    • 0:测试模式
    • 1:训练模式

set_learning_phase

1
2
3
K.set_learning_phase(
value=0/1
)

设置学习阶段为固定值

  • 参数
    • value:学习阶段值
      • 0:测试模式
      • 1:训练模式

OPs、Tensors

is_sparse

判断一个Tensor是不是一个稀疏的Tensor

  • 返回值:布尔值

稀不稀疏由Tensor的类型决定,而不是Tensor实际上有多稀疏

1
2
3
4
5
6
7
from keras import backend as K
a = K.placeholder((2, 2), sparse=False)
print(K.is_sparse(a))
# False
b = K.placeholder((2, 2), sparse=True)
print(K.is_sparse(b))
# True

to_dense

将一个稀疏tensor转换一个不稀疏的tensor并返回

variable

1
2
3
4
5
def variable(
value,
dtype='float32'/str,
name=None
)

实例化一个张量,返回之

  • 参数
    • value:用来初始化张量的值
    • dtype:张量数据类型
    • name:张量的名字(可选)

placeholder

1
2
3
4
5
6
def placeholder(
shape=None,
ndim=None,
dtype='float32'/str,
name=None
)

实例化一个占位符

  • 参数

    • shape:占位符的shape(整数tuple,可能包含None)
    • ndim: 占位符张量的阶数
      • 要初始化占位符,至少指定shapendim之一, 如果都指定则使用shape
    • dtype: 占位符数据类型
    • name: 占位符名称(可选)

shape

1
def shape(x)

返回张量的符号shape

  • 返回值:OPs(需要执行才能得到值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from keras import backend as K
tf_session = K.get_session()
val = np.array([[1, 2], [3, 4]])
kvar = K.variable(value=val)
input = keras.backend.placeholder(shape=(2, 4, 5))
K.shape(kvar)
# <tf.Tensor 'Shape_8:0' shape=(2,) dtype=int32>
K.shape(input)
# <tf.Tensor 'Shape_9:0' shape=(3,) dtype=int32>
# To get integer shape (Instead, you can use K.int_shape(x))
K.shape(kvar).eval(session=tf_session)
# array([2, 2], dtype=int32)
K.shape(input).eval(session=tf_session)
# array([2, 4, 5], dtype=int32)

int_shape

1
def int_shape(x)

返回张量shape

  • 返回值:tuple(int)/None
1
2
3
4
5
6
7
8
from keras import backend as K
input = K.placeholder(shape=(2, 4, 5))
K.int_shape(input)
# (2, 4, 5)
val = np.array([[1, 2], [3, 4]])
kvar = K.variable(value=val)
K.int_shape(kvar)
# (2, 2)

ndim

1
def ndim(x)

返回张量的阶数

  • 返回值:int
1
2
3
4
5
6
7
8
from keras import backend as K
input = K.placeholder(shape=(2, 4, 5))
val = np.array([[1, 2], [3, 4]])
kvar = K.variable(value=val)
K.ndim(input)
# 3
K.ndim(kvar)
# 2

dtype

1
def dtype(x)

返回张量的数据类型

  • 返回值:str
    • float32
    • float32_ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from keras import backend as K
K.dtype(K.placeholder(shape=(2,4,5)))
# 'float32'
K.dtype(K.placeholder(shape=(2,4,5), dtype='float32'))
# 'float32'
K.dtype(K.placeholder(shape=(2,4,5), dtype='float64'))
# 'float64'__Keras variable__

kvar = K.variable(np.array([[1, 2], [3, 4]]))
K.dtype(kvar)
# 'float32_ref'
kvar = K.variable(np.array([[1, 2], [3, 4]]), dtype='float32')
K.dtype(kvar)
# 'float32_ref'

eval

1
def eval(x)

求得张量的值

  • 返回值:NDA
1
2
3
4
5
from keras import backend as K
kvar = K.variable(np.array([[1, 2], [3, 4]]), dtype='float32')
K.eval(kvar)
# array([[ 1., 2.],
# [ 3., 4.]], dtype=float32)

`zeros

1
2
3
4
5
def zeros(
shape,
dtype='float32',
name=None
)

生成shape大小的全0张量

1
2
3
4
5
6
from keras import backend as K
kvar = K.zeros((3,4))
K.eval(kvar)
# array([[ 0., 0., 0., 0.],
# [ 0., 0., 0., 0.],
# [ 0., 0., 0., 0.]], dtype=float32)

ones

1
2
3
4
5
def ones(
shape,
dtype='float32',
name=None
)

生成shape大小的全1张量

eye

1
2
3
4
5
def eye(
size,
dtype='float32',
name=None
)

生成size大小的单位阵

zeros_like

1
2
3
4
def zeros_like(
x,
name=None
)

生成与x shape相同的全0张量

ones_like

1
2
3
4
def ones_like(
x,
name=None
)

生成与x shape相同的全1张量

随机常量OPs

random_uniform_variable

1
2
3
4
5
6
7
8
def random_uniform_variable(
shape,
low,
high,
dtype=None,
name=None,
seed=None
)

初始化均匀分布常量OPs

  • 参数
    • low:浮点数,均匀分布之下界
    • high:浮点数,均匀分布之上界
    • dtype:数据类型
    • name:张量名
    • seed:随机数种子

count_params

1
def count_params(x)

返回张量中标量的个数

cast

1
def cast(x, dtype)

改变张量的数据类型

  • 参数
    • dtypefloat16/float32/float64

update

1
def update(x, new_x)

new_x更新x

update_add

1
def update_add(x, increment)

x增加increment并更新x

update_sub

1
def update_sub(x, decrement)

x减少decrement并更新x

moving_average_update

1
2
3
4
5
def moving_average_update(
x,
value,
momentum
)

使用移动平均更新x

dot

1
def dot(x, y)

求两个张量的点乘

1
2
3
4
5
x = K.placeholder(shape=(2, 3))
y = K.placeholder(shape=(3, 4))
xy = K.dot(x, y)
xy
# <tf.Tensor 'MatMul_9:0' shape=(2, 4) dtype=float32>
  • 当试图计算两个N阶张量的乘积时,与Theano行为相同 (2, 3).(4, 3, 5) = (2, 4, 5))

    1
    2
    3
    4
    5
    x = K.placeholder(shape=(32, 28, 3))
    y = K.placeholder(shape=(3, 4))
    xy = K.dot(x, y)
    xy
    # <tf.Tensor 'MatMul_9:0' shape=(32, 28, 4) dtype=float32>
    1
    2
    3
    4
    5
    x = K.random_uniform_variable(shape=(2, 3), low=0, high=1)
    y = K.ones((4, 3, 5))
    xy = K.dot(x, y)
    K.int_shape(xy)
    # (2, 4, 5)

batch_dot

1
def batch_dot(x, y, axes=None)

按批进行张量xy的点积

  • 参数
    • x:按batch分块的数据
    • y;同x
    • axes:指定进行点乘的轴
1
2
3
4
5
x_batch = K.ones(shape=(32, 20, 1))
y_batch = K.ones(shape=(32, 30, 20))
xy_batch_dot = K.batch_dot(x_batch, y_batch, axes=[1, 2])
K.int_shape(xy_batch_dot)
# (32, 1, 30)

transpose

1
def transpose(x)

张量转置

gather

1
def gather(reference, indices)

在给定的张量中检索给定下标的向量

  • 参数

    • reference:张量
    • indices:整数张量,其元素为要查询的下标
  • 返回值:同reference数据类型相同的张量

max

1
2
3
4
5
def max(
x,
axis=None/int,
keepdims=False
)

求张量中的最大值

min

1
def min(x, axis=None, keepdims=False)

求张量中的最小值

sum

1
sum(x, axis=None, keepdims=False)

计算张量中元素之和

prod

1
prod(x, axis=None, keepdims=False)

计算张量中元素之积

cumsum

1
def cumsum(x, axis=0)

在给定轴上求张量的累积和

cumprod

1
cumprod(x, axis=0)

在给定轴上求张量的累积积

var

1
def var(x, axis=None, keepdims=False)

在给定轴上计算张量方差

std

1
def std(x, axis=None, keepdims=False)

在给定轴上求张量元素之标准差

mean

1
def mean(x, axis=None, keepdims=False)

在给定轴上求张量元素之均值

any

1
def any(x, axis=None, keepdims=False)

按位或,返回数据类型为uint8的张量(元素为0或1)

all

1
def any(x, axis=None, keepdims=False)

按位与,返回类型为uint8de tensor

argmax

1
def argmax(x, axis=-1)

在给定轴上求张量之最大元素下标

argmin

1
def argmin(x, axis=-1)

在给定轴上求张量之最小元素下标

Element-Wise OPs

square

1
def square(x)

逐元素平方

abs

1
def abs(x)

逐元素绝对值

sqrt

1
sqrt(x)

逐元素开方

exp

1
def exp(x)

逐元素求自然指数

log

1
def log(x)

逐元素求自然对数

logsumexp

1
def logsumexp(x, axis=None, keepdims=False)

在给定轴上计算log(sum(exp()))

  • 该函数在数值稳定性上超过手动计算log(sum(exp())),可以 避免由explog导致的上溢和下溢

round

1
def round(x)

逐元素四舍五入

sign

1
def sign(x)

逐元素求元素的符号

  • 返回值
    • +1
    • -1

pow

1
def pow(x, a)

逐元素求x的a次方

clip

1
2
3
4
5
def clip(
x,
min_value,
max_value
)

逐元素clip(将超出指定范围的数强制变为边界值)

equal

1
def equal(x, y)

逐元素判相等关系

  • 返回值:布尔张量OP

not_equal

1
def not_equal(x, y)

逐元素判不等关系

greater

1
def greater(x,y)

逐元素判断x>y关系

greater_equal

1
def greater_equal(x,y)

逐元素判断x>=y关系

lesser

1
def lesser(x,y)

逐元素判断x<y关系

lesser_equal

1
def lesser_equal(x,y)

逐元素判断x<=y关系

maximum

1
def maximum(x, y)

逐元素取两个张量的最大值

minimum

1
def minimum(x, y)

逐元素取两个张量的最小值

sin

1
def sin(x)

逐元素求正弦值

cos

1
def cos(x)

逐元素求余弦值

变换OPs

batch_normalization

1
2
3
4
5
6
7
8
def batch_normalization(
x,
mean,
var,
beta,
gamma,
epsilon=0.0001
)

对batch的数据进行batch_normalization,计算公式为: $output = (x-mean)/(\sqrt(var)+\epsilon)*\gamma+\beta$

  • 手动指定meanvar

normalize_batch_in_training

1
2
3
4
5
6
7
def normalize_batch_in_training(
x,
gamma,
beta,
reduction_axes,
epsilon=0.0001
)

对batch数据先计算其均值和方差,然后再进行 batch_normalization

concatenate

1
def concatenate(tensors, axis=-1)

在给定轴上将一个列表中的张量串联为一个张量

reshape

1
def reshape(x, shape)

将张量的shape变换为指定shape

permute_dimensions

1
2
3
4
def permute_dimensions(
x,
pattern(tuple(int))
)

按照给定的模式重排一个张量的轴

  • 参数
    • pattern:代表维度下标的tuple如(0, 2, 1)

resize_images

1
2
3
4
5
6
def resize_images(
X,
height_factor(uint),
width_factor(uint),
dim_ordering=None/'th'/'tf'
)

依据给定的缩放因子height_factorwidth_factor,改变batch 数据(图片)的shape

  • 参数
    • height_factor/width_factor:正整数

resize_volumes

1
2
3
4
5
6
7
def resize_volumes(
X,
depth_factor,
height_factor,
width_factor,
dim_ordering
)

依据给定的缩放因子,改变一个5D张量数据的shape

repeat_elements

1
def repeat_elements(x, rep, axis)

在给定轴上重复张量元素rep

  • np.repeat类似

repeat

1
def repeat(x, n)

重复2D张量

arange

1
2
3
4
5
6
def arange(
start,
stop=None,
step=1,
dtype="int32"
)

生成1D的整数序列张量

  • 参数:同np.arange

    • 如果只有一个参数被提供了,则0~stop
  • 返回值:默认数据类型是int32的张量

tile

1
def tile(x, n)

x在各个维度上重复n[i]

batch_flatten

1
def batch_flatten(x)

将n阶张量转变为2阶张量,第一维度保留不变

expand_dims

1
def expand_dims(x, dim=-1)

dim指定轴后增加一维(轴)

squeeze

1
def squeeze(x, axis)

axis指定的轴从张量中移除(保留轴上首组张量切片)

temporal_padding

1
def temporal_padding(x, padding=1)

向3D张量中间那个维度的左右两端填充padding个0值

asymmetric_temporal_padding

1
2
3
4
5
def asymmetric_temporal_padding(
x,
left_pad=1,
right_pad=1
)

向3D张量中间的那个维度的左右分别填充0值

spatial_2d_padding

1
2
3
4
5
def spatial_2d_padding(
x,
padding=(1, 1),
dim_ordering='th'
)

向4D张量高度、宽度左右两端填充padding[0]padding[1] 个0值

spatial_3d_padding

1
2
3
4
5
def spatial_3d_padding(
x,
padding=(1, 1, 1),
dim_ordering='th'
)

向5D张量深度、高度、宽度三个维度上填充0值

stack

1
def stack(x, axis=0)

将列表x中张量堆积起来形成维度+1的新张量

one_hot

1
def one_hot(indices, nb_classes)

为张量indices进行one_hot编码

  • 参数
    • indices:n维的整数张量
    • nb_classesone_hot编码列表
  • 输出:n+1维整数张量,最后维为编码

reverse

1
def reverse(x, axes)

将一个张量在给定轴上反转

get_value

1
def get_value(x)

以NDA的形式返回张量的值

batch_get_value

1
def batch_get_value(x)

[NDA]的形式返回多个张量的值

set_value

1
def set_value(x, value)

从NDA将值载入张量中

batch_set_value

1
def batch_set_value(tuples)

将多个值载入多个张量变量中

1
def print_tensor(x, message='')

在求值时打印张量的信息,并返回原张量

function

1
def function(inputs, outputs, updates=[])

实例化一个Keras函数

  • 参数
    • inputs:列表,其元素为占位符或张量变量
    • outputs:输出张量的列表
    • updates:张量列表

gradients

1
def gradients(loss, variables)

返回loss函数关于variables的梯度

stop_gradient

1
def stop_gradient(variables)

Returns variables but with zero gradient with respect to every other variables.

rnn

1
2
3
4
5
6
7
8
9
10
def rnn(
step_function,
inputs,
initial_states,
go_backwards=False,
mask=None,
constants=None,
unroll=False,
input_length=None
)

在张量的时间维上迭代

  • 参数:

    • inputs:时域信号的张量,阶数至少为3

    • step_function:每个时间步要执行的函数

      参数

      • input:不含时间维张量,代表某时间步batch样本
      • states:张量列表

      返回值

      • output:形如(samples, ...)的张量
      • new_states:张量列表,与states的长度相同
    • initial_states:包含step_function状态初始值

    • go_backwards:逆向迭代序列

    • mask:需要屏蔽的数据元素上值为1

    • constants:按时间步传递给函数的常数列表

    • unroll

      • 当使用TF时,RNN总是展开的’
      • 当使用Theano时,设置该值为True将展开递归网络
    • input_length

      • TF:不需要此值
      • Theano:如果要展开递归网络,必须指定输入序列
  • 返回值:形如(last_output, outputs, new_states)的张量

    • last_output:RNN最后的输出
    • outputs:每个在[s,t]点的输出对应于样本s在t时间的输出
    • new_states: 每个样本的最后一个状态列表

switch

1
2
3
4
5
def switch(
condition,
then_expression,
else_expression
)

依据给定condition(整数或布尔值)在两个表达式之间切换

  • 参数
    • condition:标量张量
    • then_expression:TensorFlow表达式
    • else_expression: TensorFlow表达式

in_train_phase

1
def in_train_phase(x, alt)

如果处于训练模式,则选择x,否则选择alt

  • 注意alt应该与xshape相同

in_test_phase

1
def in_test_phase(x, alt)

如果处于测试模式,则选择x,否则选择alt

  • 注意:alt应该与x的shape相同

预定义(激活)函数

relu

1
2
3
4
5
def relu(
x,
alpha=0.0,
max_value=None
)

修正线性单元

  • 参数
    • alpha:负半区斜率
    • max_value: 饱和门限

elu

1
def elu(x, alpha=1.0)

指数线性单元

  • 参数
    • x:输入张量
    • alpha: 标量

softmax

1
def softmax(x)

计算张量的softmax值

softplus

1
def softplus(x)

返回张量的softplus值

softsign

1
def softsign(x)

返回张量的softsign值

sigmoid

1
def sigmoid(x)

逐元素计算sigmoid值

hard_sigmoid

1
def hard_sigmoid(x)

分段线性近似的sigmoid,计算速度更快

tanh

1
def tanh(x)

逐元素计算tanh值

预定义目标函数

categorical_crossentropy

1
2
3
4
5
def categorical_crossentropy(
output,
target,
from_logits=False
)

计算outputtarget的Categorical Crossentropy(类别交叉熵)

  • 参数
    • output/target:shape相等

sparse_categorical_crossentropy

1
2
3
4
5
def sparse_categorical_crossentropy(
output,
target,
from_logits=False
)

计算outputtarget的Categorical Crossentropy(类别交叉熵)

  • 参数
    • output
    • target:同output shape相等,需为整形张量

binary_crossentropy

1
2
3
4
5
def binary_crossentropy(
output,
target,
from_logits=False
)

计算输出张量和目标张量的交叉熵

dropout

1
def dropout(x, level, seed=None)

随机将x中一定比例的值设置为0,并放缩整个Tensor

l2_normalize

1
def l2_normalize(x, axis)

在给定轴上对张量进行L2范数规范化

in_top_k

1
def in_top_k(predictions, targets, k)

判断目标是否在predictions的前k大值位置

参数

  • predictions:预测值张量
  • targets:真值张量
  • k:整数

conv1d

1
2
3
4
5
6
7
8
def conv1d(
x,
kernel,
strides=1,
border_mode="valid"/"same",
image_shape=None,
filter_shape=None
)

1D卷积

  • 参数
    • kernel:卷积核张量
    • strides:步长,整型
    • border_mode
      • “same”:
      • “valid”:

conv2d

1
2
3
4
5
6
7
8
9
def conv2d(
x,
kernel,
strides=(1, 1),
border_mode="valid"/"same",
dim_ordering="th"/"tf",
image_shape=None,
filter_shape=None
)

2D卷积

  • 参数
    • kernel:卷积核张量
    • strides:步长,长为2的tuple

deconv2d

1
2
3
4
5
6
7
8
9
10
def deconv2d(
x,
kernel,
output_shape,
strides=(1, 1),
border_mode="valid"/"same",
dim_ordering="th"/"tf",
image_shape=None,
filter_shape=None
)

2D反卷积(转置卷积)

  • 参数
    • x:输入张量
    • kernel:卷积核张量
    • output_shape: 输出shape的1D的整数张量
    • strides:步长,tuple类型

conv3d

1
2
3
4
5
6
7
8
9
def conv3d(
x,
kernel,
strides=(1, 1, 1),
border_mode="valid"/"same",
dim_ordering="th"/"tf",
volume_shape=None,
filter_shape=None
)

3D卷积

pool2d

1
2
3
4
5
6
7
8
def pool2d(
x,
pool_size,
strides=(1, 1),
border_mode="valid"/"same",
dim_ordering="th"/"tf",
pool_mode="max"/"avg"
)

2D池化

  • 参数
    • pool_size:含有两个整数的tuple,池的大小
    • strides:含有两个整数的tuple,步长
    • pool_mode: “max”,“avg”之一,池化方式

pool3d

1
2
3
4
5
6
7
8
def pool3d(
x,
pool_size,
strides=(1, 1, 1),
border_mode="valid"/"same",
dim_ordering="th"/"tf",
pool_mode="max"/"avg"
)

3D池化

bias_add

1
def bias_add(x, bias, data_format=None)

为张量增加一个偏置项

random_normal

1
2
3
4
5
6
7
def random_normal(
shape,
mean=0.0,
stddev=1.0,
dtype=None,
seed=None
)

生成服从正态分布的张量

  • 参数
    • mean:均值
    • stddev:标准差

random_uniform

1
2
3
4
5
6
7
def random_uniform(
shape,
minval=0.0,
maxval=1.0,
dtype=None,
seed=None
)

生成服从均匀分布值的张量

  • 参数
    • minval:上界
    • maxval:上界

random_binomial

1
2
3
4
5
6
def random_binomial(
shape,
p=0.0,
dtype=None,
seed=None
)

返回具有二项分布值的张量

  • 参数
    • p:二项分布参数

truncated_normall

1
2
3
4
5
6
7
def truncated_normal(
shape,
mean=0.0,
stddev=1.0,
dtype=None,
seed=None
)

生成服从截尾正态分布值的张量,即在距离均值两个标准差之外的 数据将会被截断并重新生成

ctc_label_dense_to_sparse

1
def ctc_label_dense_to_sparse(labels, label_lengths)

将ctc标签从稠密形式转换为稀疏形式

ctc_batch_cost

1
2
3
4
5
6
def ctc_batch_cost(
y_true,
y_pred,
input_length,
label_length
)

在batch上运行CTC损失算法

  • 参数

    • y_true:包含标签的真值张量
    • y_pred:包含预测值或输出的softmax值的张量
    • input_length:包含y_pred中每个batch的序列长
    • label_length:包含y_true中每个batch的序列长张量
  • 返回值:包含了每个元素的CTC损失的张量

ctc_decode

1
2
3
4
5
6
7
8
def ctc_decode(
y_pred,
input_length,
greedy=True,
beam_width=None,
dict_seq_lens=None,
dict_values=None
)

使用贪婪算法或带约束的字典搜索算法解码softmax的输出

  • 参数

    • y_pred:包含预测值或输出的softmax值的张量
    • input_length:包含y_pred中每个batch序列长的张量
    • greedy:使用贪婪算法
    • dict_seq_lensdic_values列表中各元素的长度
    • dict_values:列表的列表,代表字典
  • 返回值:包含了路径可能性(以softmax概率的形式)张量

  • 注意仍然需要一个用来取出argmax和处理空白标签的函数

map_fn

1
def map_fn(fn, elems, name=None)

元素elems在函数fn上的映射,并返回结果

  • 参数

    • fn:函数
    • elems:张量
    • name:节点的名字
  • 返回值:张量的第一维度等于elems,第二维度取决于fn

foldl

1
2
3
4
5
6
def foldl(
fn,
elems,
initializer=None,
name=None
)

用fn从左到右连接它们,以减少elems

  • 参数

    • fn:函数,例如:lambda acc, x: acc + x
    • elems:张量
    • initializer:初始化的值(elems[0])
    • name:节点名
  • 返回值:与initializer类型和形状一致

foldr

1
2
3
4
5
6
def foldr(
fn,
elems,
initializer=None,
name=None
)

减少elems,用fn从右到左连接它们

  • 参数

    • fn:函数,例如:lambda acc, x: acc + x
    • elems:张量
    • initializer:初始化的值(elems[-1])
    • name:节点名
  • 返回值:与initializer类型和形状一致

_width=None, dict_seq_lens=None, dict_values=None )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

使用贪婪算法或带约束的字典搜索算法解码softmax的输出

- 参数

- `y_pred`:包含预测值或输出的softmax值的张量
- `input_length`:包含`y_pred`中每个batch序列长的张量
- `greedy`:使用贪婪算法
- `dict_seq_lens`:`dic_values`列表中各元素的长度
- `dict_values`:列表的列表,代表字典

- 返回值:包含了路径可能性(以softmax概率的形式)张量

- 注意仍然需要一个用来取出argmax和处理空白标签的函数

#### `map_fn`

```python
def map_fn(fn, elems, name=None)

元素elems在函数fn上的映射,并返回结果

  • 参数

    • fn:函数
    • elems:张量
    • name:节点的名字
  • 返回值:张量的第一维度等于elems,第二维度取决于fn

foldl

1
2
3
4
5
6
def foldl(
fn,
elems,
initializer=None,
name=None
)

用fn从左到右连接它们,以减少elems

  • 参数

    • fn:函数,例如:lambda acc, x: acc + x
    • elems:张量
    • initializer:初始化的值(elems[0])
    • name:节点名
  • 返回值:与initializer类型和形状一致

foldr

1
2
3
4
5
6
def foldr(
fn,
elems,
initializer=None,
name=None
)

减少elems,用fn从右到左连接它们

  • 参数

    • fn:函数,例如:lambda acc, x: acc + x
    • elems:张量
    • initializer:初始化的值(elems[-1])
    • name:节点名
  • 返回值:与initializer类型和形状一致

  • name:节点名

  • 返回值:与initializer类型和形状一致

Pandas函数目录

“内容结构”变换

合并

merge

1
2
3
4
5
6
7
8
9
DF = pd.merge(
left(DF),
right(DF),
on=col_name/[col_names],
left_on="left_col_name",
right_on="right_col_name",
left_index=False/True,
right_index=False/true,
how="Inner"/"outer"/"left"/"right")
  • 功能:合并操作,类似于sql的join操作

  • 参数

    • on:merge合并基准列,可以是多个列名称的list,df1、 df2仅有一列名称相同时可省略,否则返回空DF对象
    • left_onright_on:df1、df2合并基准列名称不同时 使用
    • left_indexright_index:默认为False,值为 True时使用索引作为基准进行合并(此时也可以使用 df1.join(df2)
    • how:合并方式,默认是inner join,参数取值:’outer’ 、’left’、’right’(不知道能不能取’inner’,这个应该 是默认取值,可以使用)
  • 其他

    • df1、df2有多个列名相同且不全是合并基准列时,返回的 DF对象的重复列名称会改变

join

1
2
3
4
5
6
7
DF = df1.join(
other(DF/Ser/[DF]),
on=None/[col_names],
how="Left/right/outer/inner",
lsuffix=str,
rsuffix=str,
sort=False)
  • 说明:和其他DF对象进行join操作

  • 参数

    • other
      • 参数为Ser时,必须设置field名称
    • on
      • 默认None,按照index-on-index进行join
      • 也可以按照col_name进行join
    • how:同上
    • lsuffix:df1重名列使用的后缀
    • rsuffix:df2重名列使用的后缀
    • sort:按照join-key排序

concat

1
2
3
4
5
6
7
8
9
10
11
Df/Ser = pd.concat(
objs=[Ser, DF, dict],
axix=0/1/"index"/"columns",
join="Outer"/"inner",
join_axes=None/[index],
ignore_index=False/True,
keys=None/[]/[()],
levels=None/[],
names=None/[index_names],
verify_integrity=False,
copy=True)
  • 说明:以某个轴为方向将多个Series对象或DF对象拼接

  • 参数

    • objs
      • dict作为参数值传递,排序后的keys传递给keys
    • join:处理其他轴的方式(其他轴长度、命名不同)
    • join_axes:指定其他轴的index
    • ingore_index:默认False,为True拼接后的DF的 Index将为RangeIndex
    • keys:指定构建多级索引最外层Index
    • levels:用于构建多重索引的具体层级,默认从keys 推断
    • names:返回DF对象索引名称
    • verify_integrity:默认False,不检查返回DF对象 是否含有重复index copy:默认拷贝数据
  • 其他

    • pd.concat只涉及拼接方向,而merge只能沿列数增加的 方向“拼接”

    • pd.concat()时即使有相同的列名称、index序号也不会 重命名

    • pd.concat(axis=1,...)pd.merge(left_index=True, right_index=True,...)的 作用应该是一样的,只是不会将相同的列名称重命名

    • pd.merge可以指定合并基准列,而pd.concat只能按 Index“合并”,且只能inner join或时outer join

  • 注意事项

    • pd.concat默认会对索引进行排序,所以若索引包含不可 比较元素则会报错,尤其是在多重索引情况下

      • 改变索引类型规避

        1
        2
        3
        4
        # 改为categorical索引
        df.index.astype("categorical")
        # 改为str类型
        df.index.astype(str)
      • reset索引规避

        1
        2
        df_new = pd.cancat([df1.reset_index(), df2.reset_index()])
        df_new = df_new.set_index(col_name)
    • pd.concat连接会尝试转换数据类型,如: pd.Timestamp可能会被转换为int

combine_first

1
2
Ser = ser1.combine_first(other(Ser))
Df = df1.combine_first(other(DF))
  • 说明:和其他DF/Ser进行元素级别的combine,即填充NULL 元素
    • 元素级别
    • 返回对象包含所有的index

增、删

drop

1
2
3
4
5
6
7
8
DF/None = df1.drop(
labels=None/label/[labels],
axis=0/1/"index"/"columns",
index=None/index_name/[index_names],
columns=None/col_name/[col_names],
level=None/int/level_name,
inplace=False,
errors=="Raise/ignore")
  • 说明:删除df1某轴上的labels

  • 参数

    • columns/index:替代axis+label指定需要删除的 列/行

[new_col_name]

1
2
df1[new_col_name] = list
df1.loc[new_index_name] = list
  • 说明:添加新列/行
    • .iloc[new_index_name]不可用

append

1
2
3
4
DF = df1.append(
other(DF/Ser/dict/[]),
ignore_index=False,
verify_integrity=False)
  • 说明:将other行追加到df1

del

1
2
del df1[col_name]
# python自身语法直接删除某列

形、态变换

形状

stack

1
2
3
DF/Ser = df1.stack(
level=-1/int,
dropna=True)
  • 说明:旋转level级列索引

  • 参数

    • dropna:默认剔除返回DF/Ser对象中NULL行

unstack

1
2
3
DF/Ser = df1.unstack(
level=-1/int,
fill_value=None)
  • 说明:旋转level级行索引

  • 参数

    • fill_value:替换NaN的值
  • 其他

    • 两个函数好像都有排序操作,df1.unstack().stack()会 合并层级索引

排序

sort_index

1
2
3
4
5
6
7
8
DF = df1.sort_index(
axis=0/1,
level=None/int/str,
ascending=True,
inplace=False,
kind="Quicksort"/"mergesort"/"heapsort",
na_position="First"/"last",
sort_remaining=True/False)
  • 说明:按照labels排序

  • 参数

    • kind:排序方法
    • na_position:默认将NaN放在开头
      • 对多重索引无效
    • sort_remaining:默认依次对剩余层级排序

sort_value

1
2
3
4
5
6
7
DF = df1.sort_value(
by(label/[labels]),
axis=0/1,
ascending=True/False,
inplace=False,
kind="Quicksort"/"mergesort"/"heapsort",
na_position="Last"/"first")
  • 说明:依某labels(行、列)值排列

rank

1
2
3
4
5
6
7
DF = df1.rank(
axis=0/1,
method="Average"/"min"/"max"/"first"/"dense",
numeric_only=None/True/False,
na_option="Keep"/"top"/"bottom",
ascending=True,
pct=False)
  • 说明:沿轴计算数值型数据的rank

    • rank值相同者取rank值平均
  • 参数

    • method
      • average:组(延轴分组)rank平均
      • min/max:组内最小/大
      • first:label出现顺序优先
      • dense:类似min,但是rank值总是增加1
    • numeric_only:默认不考虑含有非数值型数据label
      • 但是文档上确实写默认值为None
    • na_option
      • keep:NaN值rank值不变
      • top:NaN作为“小”值
      • bottom:NaN作为“大”值
    • ascending:默认小值rank值小
    • pct:默认不计算rank值占比

take

1
2
3
4
5
6
DF = df1.take(
indices([indice]),
axis=0/1,
covert=True,
is_copy=True,
**kwargs)
  • 说明:按照indices对应的labels顺序返回DF

  • 参数

    • indices:指明返回indices、及其顺序
      • indices是指行数,不是labels
      • 可以为负值indice,类似list
    • convert:是否处理负值indice(将废除,均处理)
    • is_copy:默认创建副本返回

reindex

1
2
3
4
5
6
7
8
9
10
11
DF = df1.reindex(
labels=None/[labels]/Index,
index=None/[labels]/Index,
columns=None/[labels],
axis=0/1/"index"/"columns",
method=None/"backfill"/"bfill"/"pad"/"ffill"/"nearest",
copy=True/False,
level=None/int/level_name,
fill_value=NaN/scalar,
limit=None/limit,
tolerance=scalar/array-like)
  • 说明:将DF对象转换有其他Index的对象

    • 可以包含之前没有的labels
    • 类似于labels版本.take,是选择不是重命名
  • 参数

    • labels:新Index,配合axis决定替换轴
    • index/columns:两根轴的Index
    • method:新labels值填补方法
      • 用于填补的值不一定会出现在返回DF对象中,可能是 使用原DF对象中未被选择labels
    • copy:默认拷贝副本
    • fill_value:新labels值填补值
    • limit:允许最长连续使用method方法填补值
    • tolerance:使用method方法填补新labels值时,用于 填补labels和新labels的最大差距
      • 超过tolerance则为默认NaN

数据处理

简单统计

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

Ser = df1.sum(
level=None/0/1,
axis=0/1)

Ser = df1.mean(
axis=0/1)

Ser = df1.std(
axis=0/1)

DF = df1.describe()

DF = df1.corr()

DF = df1.cov()

float = ser1.corr(
ser1)

Ser = df1.corwith(
other(DF/Ser),
axis=0/1,
drop=False/True)
# `other`为DF对象时计算相同名称相关系数

value_count

1
2
3
4
5
6
7
Ser = pd.value_counts(
values(ndarray(1d)),
sort=True/False,
ascending=False/True,
normalize=False/True,
bins=None/int/[num],
dropna=True/False)
  • 说明:计算hisgram

  • 参数

    • sort:默认按值排序
    • normalize:默认不正则化(计算相对histgram)
    • bins:hisgrams划分bins
      • 默认每个值划分为一个bins
      • 给出int时表示数目,(min, max]均等分
      • 给出[num]计算列表内数量(范围外不考虑)
    • dropna:默认不计算NaN值个数

quantile

1
2
3
4
5
Ser/DF = df1.quantile(
q=0.5/float/[float],
axis=0/1/"index"/"columns",
numeric_only=True,
interpolation="linear")
  • 说明:计算q分位数

  • 参数

    • q:分位,可以是列表,计算多个分位数
    • interpolation:分位数计算方式(分位数位i、j间)
      • lineari+(j-i)*fraction(线性回归)
      • lowi
      • highi+1
      • nearestii+1中近者
      • midpoint:(i+j)/2

元素级

Apply

apply

1
2
3
4
5
6
7
8
DF/Ser = df1.apply(
func(func/{label: func}),
axis=0/1/"index"/"columns",
broadcast=False,
raw=False,
reduce=None/True/False,
args=(),
**kwargs)
  • 说明:对df1沿轴方向labels应用func

    • 可以用于DFGB对象
      • 为聚合函数时返回DF对象Index为groupby键,类似于 agg
      • 非聚合函数时返回DF对象为原Index,类似于 transform,但包含用于groupby的label
      • 但是此时其他参数无法使用,func也仅能为单一 function,而agg可以使用各种
  • 参数

    • broadcast:仅对aggregation(聚合)函数,默认不保持 原shape
      • 0.23.0 deprecated
    • raw:默认不是将label作为ndarray对象传递,而是保持 Series对象
      • 如果函数处理nadarry对象,True性能更好
    • reduce:默认根据函数判断是否返回DF
      • False:尽量返回DF对象
      • 0.23.0 deprecated
    • result_type
      • expand:返回DF对象
      • reduce:尽量返回Ser对象
      • broadcast:保持原shape返回
      • 0.23.0 new
    • args:传递给func的VAR_POSITIONs
    • kwargs:传递给func的VAR_KEYWORDs

agg

1
2
3
4
5
DF = df1.agg(
func(callable/"func_name"(str)/{label:func}/[],
axis=0/1/"index"/"columns",
*args,
**kwargs)
  • 说明:聚合

    • 可以用于DFGB,必然返回DF,因此要求函数结果必须 聚合
      • 如果分组结果都只有单个label,函数可以非聚合
      • 如果分结果中由多label分组,函数必须聚合
  • 参数

    • func
      • dict键应为column labels,值为function

transform

1
2
3
4
DF = df1/dfgb1.transform(
func(callable/"func_name"(str)/dict/[]),
*args,
**kwargs)
  • 说明:返回应用func处理后DF
    • 应用于DF对象时,等同于axis=0aggapply
    • 应用于DFGB对象时,无价值,和应用于原DF对象结果一样, 仅剔除groupby label

替换

replace

1
2
3
4
5
6
7
DF = df1.replace(
to_replace=None/str/regex/num/[]/{}/Series,
value=None/str/num/[]/{},
inplace=False,
limit=None/int,
regex=False,
method="pad"/"ffill"/"bfill"/"backfill")
  • 说明:替换

  • 参数

    • to_replace被替换对象
      • str/regex/num:匹配str/regex/num的值被替换为value
      • [ ]
        • value也为list时,长度必须相同
        • str元素可看作regex
      • {}
        • nested dict顶层键匹配列label,然后应用对应 子dict进行匹配(此时包含value功能)
        • 非顶层键可以为regex
      • None:此时regex必为str/[]/{}/Ser
    • value替换
      • str/num:替换值
      • {}:键匹配列label然后替换值,无匹配保持原样
      • [ ]:长度必须和to_replace相同
    • limit:允许最长连续bfill、ffill替换
    • regex
      • True时,to_replacevalue将作为regex
      • 代替to_replace作regex替换
    • methodto_replace为list时替换方法
  • 说明

    • to_replace为{regex:str}时,只替换DF中str匹配的部分 ,如果需要替换整个str,需要^.*str.*$匹配整个str
    • 但为{regex:int}时,不需要^.*str.*$也会匹配整个str 并替换为int

where

1
2
3
4
5
6
7
8
9
df = df1.where(
cond(df(bool)/callable/[]),
other=num/df/callable,
inplace=false,
axis=none,
level=none,
errors="raise",
try_cast=False,
raise_on_error=None)
  • 说明:mask True,替换Falseother值,默认(other 中无法找到对应值)NaN

    • condother是按照[index][col]对应位置,而不是 打印位置
  • 参数

    • cond
      • DF(bool):True保持原值不变,Falseother 替换
      • callable:应用在df1上,返回DF(bool),不应改变 `df
      • cond不提供位置视为被替换1`
    • other
      • num:替换为num
      • DF:替换值
      • callable:应用在df1上,返回DF用于替换
    • axis:alignment axis if needed
    • level:alignemnt level if needed
    • errors
      • raise:允许raise exceptions
      • ignore:suppress exceptions,错误时返回原对象
    • try_cast:默认不尝试将结果cast为原输入

mask

1
2
3
4
5
6
7
8
9
DF = df1.mask(
cond(DF(bool)/callable/[]),
other=num/DF/callable,
inplace=False,
axis=None,
level=None,
errors="raise",
try_cast=False,
raise_on_error=None)
  • 说明:TrueFalse mask规则同where相反,其他同

筛选

isin

1
2
DF = df1.isin(
values(iterable/{}/DF))
  • 说明:判断元素是否存在于values

  • 参数

    • values
      • iterable:元素在iterable中为True
      • {}:df1元素在dict中对应键中存在为True
      • DF:df1元素在DF对应index、columns labels存在 才为True

类型转换

to_numeric

1
2
3
4
5

Ser = pd.to_numeric(
arg([]/()/ndarray(1d)/Ser),
errors="Raise"/"ingore"/"coerce",
downcast=None/"integer"/"signed"/"unsigned"/"float")
  • 说明:转换为numeric类型

  • 参数

    • errors
      • raise:无效值将raise exception
      • coerce:无效值设为NaN
      • ignore:无效值保持原样
    • downcast:根据参数downcast值为“最小”类型
      • downcast过程中exception不受errors影响

to_datetime

1
2
3
4
5
6
7
8
9
10
11
12
Ser = pd.to_datetime(
arg(int/float/str/datetime/[]/()/ndarray(1d)/Ser/DF,
error="Raise"/"ignore"/"coerce",
dayfirst=False,
yearfirst=False,
utc=None/True,
box=True/False,
format=None/str,
exact=True,
unit="ns"/"D"/"s"/"ms"/"us"/"ns",
infer_datatime_format=False,
origin="unit")
  • 说明:转换为datetime

  • 参数

    • dayfirst:处理类”10/11/12”,设置day在前
    • yearfirst:处理类”10/11/12”,设置year在前
    • utc:默认不返回UTC DatatimeIndex
    • box
      • True:返回DatetimeIndex
      • False:返回ndarray值
    • format:parse格式,如”%d/%m/%y %H:%M:%S”
    • exact:默认要求arg精确匹配format
    • unitarg传递数值时作为单位
    • infer_datatime_format:在formatNone时,尝试 猜测格式,并选择最快的方式parse,耗时5~10倍
    • origin:决定参考(起始)时间
      • unix:起始时间:1970-01-01
      • julian:4714 BC 1.1(此时unit必须为D

infer_objects

1
DF = df1.infer_objects()
  • 说明:soft转换数据类型,无法转换保持原样

astype

1
2
3
4
5
DF = df.astype(
dtype(dtype/{col_name:dtype}),
copy=True,
errors="raise"/"ingore",
**kwargs)
  • 说明:强制转换为dtype类型

  • 参数

    • copy:默认返回拷贝
    • kwargs:传递给构造函数的参数

Ser

map

1
2
3
Ser = ser1.map(
arg={}/Ser/callable,
na_action=None/"ignore")
  • 说明:对Ser中元素进行映射

    • map对无法配置(dict)返回None而不是保持不变
  • 参数

    • arg
      • {}:对Ser中值根据键值对映射
      • callable:对元素应用callable
    • no_action:默认对NA也处理,否则忽略
  • 其他

    • 好像默认会应用类似于apply(convert_type=True),如 直接取值是np.float64类型,传给函数就变成了float 类型

apply

1
2
3
4
5
Ser = ser1.apply(
func(callable/{}/[]),
convert_type=True/False,
args=(),
**kwargs)
  • 说明:在Ser上应用func

  • 参数

    • func

      • {}:返回多重索引Ser,键作为顶层索引,不是对 不同值应用不同方法
      • [ ]:返回DF对象,列labels根据list元素命令,list 元素不能聚合、非聚合混合
    • convert_type:默认转换为合适的类型,否则设为 dtype=object

agg

1
2
3
4
5
Ser = ser1.agg(
func(callable/{}/[]),
axis=0,
args=(),
**kwargs)
  • 说明:好像和apply完全相同,只是参数不同,但axis没用

分组

cut

1
2
3
4
5
6
7
8
Ser = pd.cut(
x(array-like),
bins(int/[num]/IntervalIndex),
right=True,
labels=None/[],
retbins=False,
precision=3/int,
include_lowest=False)
  • 说明:返回各个元素所属区间,同时也是对应indice

  • 参数

    • bins:左开右闭
      • int:将x等分(事实上为了包含最小值,bins 左边界会扩展.1%)
      • [num]:定义bins边界
    • right:默认包含right-most边界
    • labels:指定生成bins的labels
    • retbins:默认不返回bins,设为True将返回tuple
    • precision:bins labels显示精度
    • include_lowest:第一个bin是否应该包括最小值
      • 应该仅对bins=[]有效
      • 设为True仍然为左开右闭区间,但是左边界会小于 bins=[]中的最小值

qcut

1
2
3
4
5
6
7
Ser = pd.qcut(
x(array-like),
q=int/quantile_list,
labels=None,
retbins=False,
precision=3,
duplicates="Raise"/"drop")
  • 说明:将x按照q划分分位数后进组(即按照数量分组)

  • 参数

    • q
      • int:划分int个等分位数
      • [num]:将x元素按照给出分位数分组
    • duplicates
      • raise:bins边缘不唯一raise exception
      • drop:bins边缘不唯一则丢弃不唯一值

groupby

1
2
3
4
5
6
7
8
9
DFGB = df1.groupby(
by(col_name),
axis=0,
level=None,
as_index=True,
sort=True,
group_keys=True,
squeeze=False,
**kwargs)
  • 说明:根据by对DF分组

  • 参数

    • by
      • func:应用于DF的Index上,结果作分组依据
      • dict/Ser:对Index作映射,映射结果作分组依据
      • array-like:其值依顺序标记DF行,据此分组,因此 要求长度必须和DF行数相同
      • col_name/[col_name]:根据col_name分组
    • as_index:默认使用分组依据作为分组Index(labels)
    • sort:默认组内各保持原DF顺序,可以关闭获得更好性能
    • group_keys:默认在calling apply时,添加group键用于 标识各组
    • squeeze:为True时尽可能减少返回值维度,否则返回 consistent type

Index

Index值

pivot

1
2
3
4
DF = df1.pivot(
index=None/str,
columns=None/str,
values=None/str)
  • 说明:根据某列值reshape数据(创建一个

  • 参数

    • index:用作Index的列名,默认Index
    • columns:用作Column的列名
    • values:填充进新DF对象的列名,默认所有剩余所有列 (列索引为层级索引)
  • 其他

    • 但是如果选取的两列元素两两组合有重复回报错

set_index

1
2
3
4
5
6
DF = df1.set_index(
keys(col_name/[ ]/[col_names,[ ]]),
drop=True,
append=False,
inplace=False,
verify_integrity=False)
  • 说明:用一列/多列作为新DF的Index(row labels)

  • 参数

    • keys:列名,列名列表
    • drop:默认删除用作Index的列
    • append:默认不保留原Index(不以添加方式设置 Index)
    • verify_integrity:默认不检查新Index是否唯一,直至 必要的时候

reset_index

1
2
3
4
5
6
DF = df1.reset_index(
level=None/int/str/[int, str],
drop=False,
inplace=False,
col_level=0/int/str,
col_fill='')
  • 说明:

  • 参数

    • level:默认所有层级
    • drop:默认将Index作为一列添加
    • col_level:Index作为列添加进的列索引层次,默认 最高层
    • col_fill:对于多级索引,Index作为列添加时其他层级 的命名,默认xtts
  • 其他

    • Index作为新列的名称默认为”index”/“level_0”、 “level_1”等

Index属性

swaplevel

1
2
3
4
DF = df.swaplevel(
i=-2/int/str,
j=-1/int/str,
axis=0)
  • 说明:交换i、j两层索引

rename

1
2
3
4
5
6
7
8
DF = df.rename(
mapper=None/dict/func,
index=None/dict/func,
columns=None/dict/func,
axis=0/1/"index"/"columns",
copy=True,
inplace=False,
level=None/int/str)
  • 说明:修改labels(行、列名)

  • 参数

    • mapper:labels的重命名规则
    • index/columns:行、列labels重命名规则
      • mapper+axis
      • index
      • columns
    • copy:默认复制数据
      • inplaceTrue时,此参数无效
    • level:默认重命名所有level

add_prefix

1
2
DF = df.add_prefix(
prefix(str))
  • 说明:为列labels添加前缀

特殊值

重复

unique

1
2
3
4
bool = ser1.is_unique
# 无重复元素**属性**为`True`
Ser = ser1.unique()
# 返回非重复元素

duplicated

1
2
3
Ser(bool) = df1.duplicated(
subset=None/label/[labels],
keep="First"/"last"/False)
  • 说明:返回标识“副本(元素相同)”行的Ser(bool)

  • 参数

    • subset:默认检查所有列,否则检查指定列
    • keep
      • first:重复者除第一个外标记为True
      • last:重复者除最后一个外标记为True
      • False:重复者均标记为True
1
2
3
4
DF = df1.drop_duplicates(
subset=None/label/[labels],
keep="First"/"last"/False,
inplace=False)

null

1
2
DF(bool) = df1.isnull()
DF(bool) = df1.notnull()

dropna

1
2
3
4
5
6
DF = df1.dropna(
axis=0/1,
how="Any"/"all",
thresh=None/int,
subset=None/label/[labels],
inplace=False)
  • 说明:剔除空labels(及其数据)

  • 参数

    • how:默认any NaN值存在剔除label
    • thresh:剔除label的NaN值阈值
    • subset:指定考虑的labels

fillna

1
2
3
4
5
6
7
8
DF = df1.fillna(
value=None/scalar/dict/Ser/DF,
method=None/"backfill"/"bfill"/"pad"/"ffill",
axis=0/1/"index"/"columns",
inplace=False,
limit=None/int,
downcast=None/dict,
**kwargs)
  • 说明:填补NaN值

  • 参数

    • value:用于填补NaN的值,dict-like时无法找到的NaN 不被填补
    • method
      • “pad”/“ffill”:使用last(前一个)值填补
      • “bfill”/“backfill”:使用next(后一个)值填补
    • limit:填补允许的最大连续NaN值,超过部分保留NaN
    • downcast:数据类型精度降级dict,或者"infer"由 自行推断

其他

any

1
2
3
4
5
6
Ser = df1(bool).any(
axis=0/1/"index"/"columns",
bool_only=None,
skipna=True,
level=None/int/str,
**kwargs)
  • 说明:label(行、列)对应值存在真返回True

  • 参数

    • skipna:默认skip NA,如果全label为NA则结果为NA
    • level:默认考虑整个索引
    • bool_only:默认将所有转换为bool值再做判断

Rust 语法

Rust是基于表达式的语言

表达式返回一个值,而语句不返回,rust中除两种语句外,全是 表达式

  • let引入绑定
    • 可变绑定赋值是表达式,返回空tuple
    • 声明后初始化绑定?#todo
  • 表达式语句:表达式后跟分号转换为语句

代码中rust希望语句后跟语句,使用分号分隔表达式,所以rust 看起来和其他大部分分号结尾语言相似

{}包裹的代码块内最后一“句”没有以”;”结尾,那么是表达式,且 返回该表达式的值,整个代码块可以看作是表达式,否则为语句, 没有返回值,函数同

模式匹配

Refutability(可反驳性)

  • refutable(可反驳的):对某些可能值匹配失败的模式, if letwhile let只能接受可反驳的模式,因为这就用于 处理可能的失败
  • irrefutable(不可反驳的):能匹配任何传递的可能值,let 语句、函数参数、for循环只能接受不可反驳的模式,因为 通过不匹配值的程序无意义

可能值是指“类型”相同,可用于匹配的值

1
2
3
4
5
6
7
8
let Some(x) = some_optiona_value;
//`Some(x)`是refutable模式,若`some_optional_value`为
//None,则此时无法成功匹配,此语句可能无法正常工作
if let x = 5 {
//`x`是inrefutable模式,对所有可能值都可以匹配,此语句
//无意义
println!("{}", x);
}

Refutable

match控制流

  • 各分支模式同值“类型”必须完全一致才能匹配
  • 返回类型必须一致,如果有返回值
  • 匹配必须是穷尽的,可以使用通配符_(匹配所有的值)代替
    • match是匹配到就退出,不像switch一样会继续下沉
    • 通配符不总是需要的,对于枚举类型只要含有所有枚举 成员的分支即可
1
2
3
4
5
6
7
8
9
10
11
let x = Some(5);
let y = 10;

match x{
Some(50) => println!("got 50"),
//`Some(50)`这个模式规定了“值”
Some(y) => println!("matched, y = {:?}", y),
//match表达式作用域中的`y`会覆盖周围环境的`y`
_ => println!("default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);

if let[else]简洁控制流

  • 只匹配关心的一个模式
  • 可以添加else语句,类似match通配符匹配
  • if letelse ifelse if let等相互组合可以提供更多 的灵活性
    • else if可以不是模式匹配
    • if后的比较的值可以没有关联
  • 没有穷尽性检查,可能会遗漏一些情况
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
fn main(){
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();

if let Some(color) = favorite_color{
//模式匹配
//注意这里是`=`而不是一般值比较`==`
//`while`同
println!("favorite color is {}", color};
}else if is_tuesday{
//普通`if`条件语句
println!("Tuesday is green day!");
}else if let Ok(age) = age{
//`age`变量覆盖原变量
//此时`age`是`u8`类型
if age > 30 {
//因此此条件不能提出,因为两个`age`变量不同,
//不能共存在同一条语句中
println!("Using purple as the background color");
}else{
println!("Using orange as the background color");
}
}else{
println!("Using blue as the background color");
}
}

Irrefutable

while let条件循环

if let条件表达式类似,循环直到模式不匹配

1
2
3
4
5
6
7
8
9
fn main(){
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop(){
println!("{}", top);
}
}

for解构

1
2
3
4
5
6
7
fn main(){
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate(){
//解构tuple
println!("{} is at index {}", value, index);
}
}

let解构

let语句本“应该“看作是模式匹配

1
2
3
4
5
6
7
8
9
let PARTTERN = EXPRESSION;
//这样就和`if let`模式匹配中`=`一致
//应该可以把`=`看作是模式匹配的符号

let x = 5;
//这里`x`是一个模式“变量”
let (x, y, z) = (1, 2, 4);
//`(x, y, z)`是模式“3元元组“
//解构元组

函数参数

类似于let语句,函数参数也“应该”看作是模式匹配

1
2
3
4
5
6
7
8
9
fn foo(x: i32){
//`x`表示模式“变量”
}
fn print_coordinates(&(x, y): &(i32, i32)){
//这里`(x, y)`是模式“元组”
//但是这里处于函数变量类型的要求,有点像元组结构体
//调用时使用元组即可
println!("Current location: ({}, {})", x, y);
}

模式匹配用法

|...“或“

  • |“或”匹配多个模式
  • ...闭区间范围模式,仅适用于数值、char
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let x = 1;
match x {
1 => println!("one"),
//`1`是模式“字面值”
2 | 3 => println!("two or three"),
//`|`分隔,匹配多个模式
5...10 => println!("through 5 to 10"),
//`...`表示匹配一个**闭区间**范围的值
//这个语法只能用于数字或者是`char`值,因为编译器会
//检查范围不为空,而只有数字、`char`值rust可以判断
_ => println!("anything"),
}

let x = 'c';
match x {
'a'...'j' => println!("early ASCII letter"),
'k'...'z' => println!("late ASCII letter"),
_ => println!("something else"),
}

_..”忽略“

  • _忽略整个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fn foo(_: i32, y: i32){
    //函数签名中忽略整个值
    println!("this code only use the y parameter: {}", y);
    }
    fn main(){
    foo(3, 4);

    let mut setting_value = Some(5); let new_setting_value = Some(10);
    match (setting_value, new_setting_value){
    (Some(_), Some()) => {
    //嵌套`_`忽略部分值
    //此时没有任何值所有权
    println!("can't overwrite an exsiting customized value");
    }
    _ => {
    setting_value = new_setting_value;
    }
    }
    println!("setting is {:?}", setting_value);
    }
  • _var变量名前添加下划线忽略未使用变量,此时值所有权 仍然会转移,只是相当于告诉编译器忽略该未使用变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    fn main(){
    let _x = 5;
    //两个变量未使用,但是编译时只有一个warning
    let y = 10;

    let s = Some(5);
    if let Some(_s) = s {
    //值所有权已转移,`s`不能继续使用
    //编译器忽略未使用`_s`,不给出warning
    println!("get a integer");
    }
    }
  • ..忽略剩余值

    • ..的使用必须无歧义
    • 对于结构体即使只有一个field,需使用..忽略剩余值, 不能使用_
      1
      2
      3
      4
      5
      6
      7
      8
      fn main(){
      let numbers = (2, 4, 6, 8, 10);
      match numbers{
      (first, .., last) => {
      println!("Some numbers: {}, {}", first, last);
      }
      }
      }
1
2
3
4
let (x, _, z) = (1, 2, 4);
//`_`忽略模式中各一个值
let (x, .., z) = (1, 2, 3, 4);
//`..`忽略模式中多个值

解构结构体

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
struct Point{
x: i32,
y: i32,
}

fn main(){

let p = Point{x: 0, y: 7};
let Point{x: a, y: b} = p;
//`Point{x: a, y: b}`是模式”Point结构体“
//解构结构体

let p = Point{x: 0, y: 7};
let Point{x, y} = p;
//模式匹配解构结构体简写
//只要列出结构体字段,模式创建相同名称的变量

let p = Point{x: 0, y: 7};
match p {
Point {x, y: 0} => println!("on the x axis at {}", x),
Point {x: 0, y} => println!("on the y axis at {}", y),
Point {x, y} => println!("on neither axis: ({}, {})", x, y),
//这里没有`_`通配符,因为`Point {x, y}`模式已经
//是irrefutable,不需要
}
}

解构枚举

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
enum Message{
Quit,
Move{x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn main(){

let msg = Message::ChangeColor(0, 160, 255);

let p = match msg{
//这里`match`返回值必须类型完全相同
Message::Move{x, y} if x == 0 => (x, y),
//对于`Message`中的匿名结构体类型的成员,匿名
//结构体没有枚举类型外的定义、名称,无法、也
//不应该直接获取结构体
Message::Write(ref str) => {
//`Message::Write`不是`Message`的一个枚举成员
//必须`Message::Write(str)`才是(能够匹配)
println!("write {}", str);
(1,1)
},
Message::ChangeColor(..) => (1,0),
//类似的,需要使用`..`忽略值,仅`ChangeColor`
//不是`Message`成员
_ => {
println!("quit");
(0,0)
},
};
}

&refref mut”引用“

  • &匹配引用,“获得”值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let points = vec![
    Point {x: 0, y: 0},
    Point {x: 1, y: 5},
    Point {x: 10, y: -3},
    }

    let sum_of_squares: i32 = points
    .iter()
    .map(|&Point {x, y}| x * x + y * y)
    //`&`匹配一个引用
    .sum();
  • ref匹配值,“获得”不可变引用

    1
    2
    3
    4
    5
    6
    7
    8
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref name) => println!("found a name: {}", name),
    //使用`ref`获取不可变引用才能编译成功
    //否则所有权转移,之后报错
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);
  • ref mut匹配值,“获得”可变引用

    1
    2
    3
    4
    5
    6
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref mut name) => *name = String::from("NewName"),
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);

ifmatch guard

匹配守卫match guard:放在=>之前的if语句,match 分支的额外条件,条件为真才会继续执行分支代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let num = Some(4);
let y = 5;
match num{
Some(x) if x < y => println!("'less than y: {}", x),
Some(x) => println!("{}", x),
None => (),
}

let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
//`4 | 5 | 6`整体作为一个模式,match guard作用于
//模式整体,而不是单独的`6`
_ => println!("no"),
}

@绑定

@允许在创建存放值的变量时,同时测试值是否匹配模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Message{
Hello { id: i32},
}

let msg = Message::Hello { id: 5 };
match msg{
Message::Hello{ id: id_variable @ 3...7 } => {
//匹配结构体模式(值绑定`id_variable`)&&值在`3...7`范围
println!("Found an id in range: {}", id_variable)
},
Message::Hello{ id: 10...12 } => {
//此分支模式指定了值的范围,但是没有绑定值给变量`id`
//结构体匹配简略写法不能应用与此
println!("Found an id in another range")
}
Message::Hello{ id } => {
//此分支结构体匹配简略写法,值绑定于`id`
println!("Found some other id: {}", id)
},
}

闭包closures和函数指针

闭包是可以保存进变量或作为参数传递给其他函数的匿名函数, 可以在一个地方创建闭包,而在不同的上下文执行闭包。和函数 的区别在于,其可以捕获调用者作用域中的值,当然这会有性能 损失,如果不需要捕获调用者作用域中的值可以考虑使用函数

1
2
3
4
let closures = |param1, param2|{
...
expression
}
  • 闭包参数:调用者使用,
    • 创建闭包赋值给变量,再通过变量调用闭包
    • 创建闭包作为参数传递,其他函数调用
  • 捕获环境变量:创建闭包作为参数传递,直接使用周围环境变量

闭包类型推断和注解

闭包不要求像函数一样需要在参数和返回值上注明类型,函数需要 类型注解因为其是需要暴露给的显示接口的一部分,而闭包不用于 作为对外暴露的接口

  • 作为匿名函数直接使用,或者存储在变量中
  • 通常很短,使用场景上下文比较简单,编译器能够推断参数和 返回值类型

当然,闭包也可以添加注解增加明确性

1
2
3
4
5
fn  add_one_v1   {x: u32} -> u32 { x + 1 };
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1;
//闭包体只有一行可以省略`{}`

Rust会根据闭包出调用为每个参数和返回值推断类型,并将其锁定, 如果尝试对同一闭包使用不同类型的参数调用会报错

1
2
3
4
5
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
//此时已经锁定闭包参数、返回值类型
let n = example_closure(5);
//尝试使用`i32`类型调用闭包会报错

Fntrait bound

每个闭包实例有自己独有的匿名类型,即使两个闭包有相同的签名, 其类型依然不同。为了定义使用闭包的结构体、枚举、函数参数, (这些定义中都需要指定元素类型),需要使用泛型和trait bound

  • FnOnce:获取从周围环境捕获的变量的所有权,因此只能调用 一次,即Once的含义
  • Fn:获取从周围环境捕获的变量的不可变引用
  • FnMut:获取从周围环境捕获的变量的可变引用

所有的闭包都实现了以上3个trait中的一个,Rust根据闭包如何 使用环境中的变量推断其如何捕获环境,及实现对应的trait

1
2
3
4
5
6
struct Cacher<T>
where T: Fn(u32) -> u32{
//`T`的类型中包括`Fn`、参数、返回值三个限定
calculation: T,
value: Option<u32>,
}

Function Pointer fn

1
2
3
4
5
6
7
8
9
10
11
fn add_one(x: i32) -> i32{
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32{
//`f`是`fn`函数指针类型
f(arg) + f(arg)
}
fn main(){
let answer = do_twice(add_one, 5);
println!("the anwser is: {}", anwser);
}

函数指针类型实现了以上全部FnFnMutFnOnce三个 trait,所以总是可以在调用期望闭包作为参数的函数时传递函数 指针,因此倾向于使用泛型和闭包trait bound的函数,这样可以 同时使用闭包、函数指针作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let list_of_numbers = vec![1, 2, 3];
let list_of_Strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
//闭包作为参数传递
.collect();

let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
//函数作为参数传递
//使用了完全限定语法,因为存在多个`to_string`函数
//标准库为所有实现了`Display`的类型实现了此trait
.collect();

与不存在闭包的外部代码(如C语言,只有函数没有闭包)交互时, 只能使用函数作为参数,不能使用闭包。

move关键字

move关键字强制闭包获其捕获的环境变量的所有权,在将闭包 传递给新线程以便将数据移动到新线程时非常实用

1
2
3
4
5
6
7
fn main(){
let x = vec![1, 2, 3]
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
//此时`x`的所有权已经转移进闭包,不能在闭包外使用
let y = vec![1, 2, 3];
assert!(equal_to_x(y));

返回闭包

闭包表现为trait,没有确定的类型、大小,无法直接返回,也不 允许使用函数指针fn作为返回值类型,需要使用trait对象返回 闭包

1
2
3
4
fn return_closure() -> Box<Fn(i32) -> i32>{
//trait对象作为返回值
Box::new(|x| x + 1)
}

迭代器Iterator

迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑。Rust 中迭代器时惰性的,直到调用方法”消费“迭代器之前都不会有效果

Iteratortrait

迭代器都实现了标准库中Iteratortrait

1
2
3
4
5
6
7
8
trait Iterator{
type Item;
//定义`Iterator`的关联类型
fn next(&mut self) -> Option<Self::Item);
//参数是`&mut self`,要求迭代器是`mut`类型
//`next`方法改变了迭代器中用来记录序列位置的状态
//methods with default implementation elided
}

next方法

nextIterator唯一要求被实现的方法,其返回迭代器中封装 在Some中的一项(消费迭代器中的一项),迭代器结束时, 返回None

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[test]
fn iterator_demostration(){
let v1 = vec![1, 2, 3];
let mut v_iter = v1.iter();
//注意`v_iter`声明为`mut`
//使用`for`循环时无需使`v1_iter`可变,`for`会获取其
//所有权并在后台使其可变

assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
//真的很难理解,rust中&integer是怎么比较的
assert_eq!(v1_iter.nett(), None));
}

消费适配器Comsuming Adaptors

Iteratortrait中定义,调用next方法,消耗迭代器

1
2
3
4
5
6
7
8
#[test]
fn iterator_sum(){
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

let total:i32 = v1_iter.sum();
//`sum`获取迭代器所有权,`v1_iter`不能继续使用
assert_eq!(total, 6);

迭代器适配器Iterator Adaptors

Iteratortrait中定义,将当前迭代器变为其他迭代器,同样是 惰性的,必须调用消费适配器以便获取迭代适配器的结果

1
2
3
4
5
6
let v1:Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
//这里因为没有调用消费适配器,其实没有做事
let v2:Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4])

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
#[derive(PartialEq, Debug)]
struct Shoe{
size: u32,
style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, show_size: u32) -> Vec<Shoe>{
shoes.into_iter()
//获取vector所有权的迭代器
.filter(|s| s.size == show_size)
//这里使用闭包获取外部环境变量
.collect()
}

#[test]
fn filter_by_size(){
let shoes = vec![
Shoe{size: 10, style: String::from("sneaker")},
Shoe{size: 13, style: String::from("sandal")},
Shoe{size: 10, style: String::from("boot")},
];

let in_my_size = shoes_in_my_size(shoes, 10);

assert_eq!(
in_my_size,
vec![
Shoe{size: 10, style: String::from("sneaker")},
Shoe{size: 10, style: String::from("boot")},
]
);
}

实现Iterator

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
struct Counter{
count: i32,
}
impl Counter{
fn new() -> Counter{
Counter{count: 0}
}
}

impl Iterator for Counter{
type Item = u32;
//迭代器将返回`u32`值集合

fn next(&mut self) -> Option<Self::Item>{
self.count += 1;

if self.count < 6{
Some(self.count)
}else{
None
}
}
}

#[test]
fn using_other_iterator_trait_methods(){
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18, sum);

并发(并行)

多线程可以改善性能,但是也会增加复杂性

  • 竞争状态Race Conditions:多个线程以不一致的顺序访问资源
  • 死锁Dead Lock:线程互相等待资源释放,阻止继续运行
  • 只会在特定情况出现、无法稳定重现的bug

线程模型

  • 1:1模型:一个OS线程对应一个语言线程,语言调用操作系统 API创建线程,性能较好
  • M:N模型:语言有自己的线程实现,其提供的线程称为 绿色(green)线程,M个绿色线程对应N个OS线程,更好的 运行控制、更底的上下文切换成本

Rust为了更小的运行时(这里表示二进制文件中语言自身提供的 代码)考虑,标准库中只提供了1:1线程模式实现。可以通过一些 crate扩展M:N线程模式。

spawn创建新线程

std::thread::spawn接受一个闭包作为参数,返回JoinHandle 类型的句柄。作为spawn参数的闭包和一般的闭包有些不同, 线程直接独立执行,所以此时闭包捕获外部环境变量不能按照默认 的获取不可变引用,因为此时捕获的变量值可能已经被丢弃,必须 使用move关键字获取所有权,而一般的闭包是顺序执行的,没有 特殊需要可以直接获取不可变引用,而能够保证值不被丢弃。

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
use std::thread;
use std::time:Duration;
fn main(){
let handle = thread::spawn(|| {
//`thread::spawn`接受一个闭包作为参数,返回
//`JoinHandle`类型的值(不是引用)
for i in 1..10{
println!("number {} from spwaned thread!", i);
thread::sleep(Duration::from_millis(1));
}
);

for i in 1..5{
println!("number{} from main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().wrap();
//`JoinHandle.join()`将阻塞直到其对应的线程结束
//如果调用`join`,spawn线程可能无法执行完毕
//因为主线程执行完,整个进行结束
//注意`join`调用的位置决定阻塞的位置

let v = vec![1, 2 , 3];
let handle = thread::spawn(move || {
//这里`move`关键字将捕获的外部环境中变量`v`所有权
//移入spawn线程,否则无法正常编译
prinln!("here's a vector: {:?}", v);
});
handle.join().unwrap();
}

消息传递

Rust中实现消息传递并发的主要工具是通道(channel)

mpsc::channel

mpsc:multiple producer single consumer,多个生产者,单个 消费者,即Rust标准库实现通道的方式允许多个产生值的发送端,但 只能有一个消费这些值的接收端。发送端或接收端任一被丢弃时, 意味着通道被关闭

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
use std::thread;
use std::sync:mpsc;
use std::time:Duration;

fn main(){
let (tx, rx) = mpsc::channel();
//`tx`表示发送端,`rx`表示接收端

let tx1 = mpsc::Sender::clone(&tx);
//clone发送端创建多个生产者

thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];

for val in vals {
tx1.send(val).unwrap();
//`send`会获取参数所有权归接收者所有,避免
//值被其他线程丢弃、修改导致意外结果

//`send`返回`Result<T, E>`,如果接收端被丢弃
//将没有发送值的目标,将返回`Err<E>`
thread::sleep(Duration::from_secs(1));
}
});

thread::spawn(move || {d
let vals = vec![
String::from("move"),
String::from("messages"),
String::from("for"),
String::from("you"),
];

for val in vals{
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

let received = rx.recv().unwrap();
//`recv`将阻塞直到接收到一个值,返回`Result<T, E>`
//通道发送端关闭时`recv`将返回`Err<E>`表明不会有新值

let received = rx.try_recv().unwrap();
//`try_recv`不阻塞,立刻返回`Result<T, E>`,`Err<E>`
//表示当前没有消息
//可以再循环中多次调用`try_recv`,有消息进行处理,
//否则进行其他工作直到下次调用`try_recv`

for received in rx{
//将接收端`rx`当作迭代器使用,返回接收到值
println!("Got: {}", received);
}
}

共享状态

(任何语言)通道都类似于单所有权,一旦值通过通道传送,将无法 再次使用,而共享内存类似于多所有权

Mutex<T>

互斥器mutex:mutual exclusion,任意时刻只允许一个线程访问 数据

  • 线程在访问数据之前需要获取互斥器的锁lock,lock是作为 互斥器一部分的数据结构,记录数据所有者的排他访问权
  • 处理完互斥器保护的数据之后,需要解锁,这样才能允许其他 线程获取数据

Mutex<T>类似于线程安全版本的RefCell<T>cell族), 提供了内部可变性,Mutex<T>有可能造成死锁,如一个操作需要 两个锁,两个线程各持一个互相等待。

Arc<T>原子引用计数atomically reference counted,则是 线程安全版本的Rc<T>,而线程安全带有性能惩罚,如非必要, 使用单线程版本Rc<T>性能更好。

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
use std::sync::{Mutex, Arc};
use std::thread;

fn main(){
let counter = Arc::new(Mutex::new(0));
//因为需要在多个线程内引用,所以需要使用多所有权
//数据结构,而`Rc`不是线程安全的,需要使用线程安全
//`Arc`
let mut handles = vec![];

for _ in 0..10{
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
//`lock`返回一个`MutexGuard`类型的智能指针,
//实现了`Deref`指向其内部数据,`Drop`当
//`MutexGuard`离开作用域时自动释放锁

//只有`lock`才能获取值,类型系统保证访问数据之前
//获取锁;而锁的释放自动发生,保证锁一定会释放

//这里发生了一次强制解引用多态,将`counter`
//解引用为`Mutex<T>`类型
*num += 1;
});

handles.push(handle);
}

for handle in handles{
handle.join().unwrap();
}
println!("Result: {}", counter.lock.unwrap());
}

Synctrait、Sendtrait

Rust的并发模型大部分属于标准库的实现,但是 std::marker::Sendstd::marker::Sync时内嵌于语言的

  • Send:表明类型所有权可以在线程间传递,几乎所有类型都是 Send的,Rc<T>是其中的一个例外,因为Rc<T>clone之后 在两个线程间可能同时更新引用计数,trait bound保证无法将 不安全的Rc<T>在线程间传递。任何全部由Send组成的类型 会自动标记为Send
  • Sync:表明类型可以安全的在多线程中拥有其值的引用,对于 任何类型,如果&TSend的,那么T就是Sync的。 Cell<T>系列不是Sync的,Mutex<T>是。基本类型是Sync 的,全部由Sync组成的类型也是Sync

SendSync是标记trait,不需要实现任何方法,全部是SendSync组成的类型就是SendSync,一般不需要手动实现 它们,而且手动实现这些标记trait涉及编写不安全代码

Rust 标准库数据类型

通用集合类型

Vec<T>

vector允许在单独数据结构中存储多于一个的值,它们在内存中相邻 排列,vector被丢弃时,其中的数据也会被丢弃

存储不同类型

vector只能存储相同类型的值,因为vector必须在编译前知道所有 存储元素所需内存、允许的元素类型,否则对vector进行操作可能 会出错。但是可以使用枚举类型存储”不同类型”(Message为例)

1
vec![Message::Write(String::from("ab"), Message::Move{x:5, y:6}]

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let mut v:Vec<i32> = Vec::new();
let mut v = Vec::with_capacity(3);
//为vector预先分配空间,比`new`稍有效率
//但是这个为啥不用指定类型啊,这样怎么分配空间
let v = vec![1, 2, 3];

v.push(5)
let third:&i32 = &v[2]
//尝试获取一个引用值,如果越界则报错
let third:Option<&i32> = v.get(2)
//尝试获取Option<&i32>,越界则返回None

let mut v = vec![100, 32, 57]
for i in &mut v{
*i += 50;
}

let iter = v.iter();
//不可变引用迭代器
let iter_mut = v.iter_mut();
//可变引用迭代器
let iter_owner = v.into_iter();
//所有权引用迭代器

使用enum+match就能保证处理所有类型,不会出错

字符串

通常意义上的字符串往往是以下两种的”综合“

  • rust核心语言中的字符串slice&str:存储在别处的utf-8 编码字节序列的引用

    字符串slice是&str类型,这个好像体现了rust引用更像 指针,字符串字面值应该是一系列字节序列(流)存储, 所以”返回值“应该是”首地址“,因此是引用类型

  • rust标准库中String类型,是Vec<u8>的封装

    • 可增长
    • 可变
    • 有所有权
    • utf-8编码

索引字符串

因此String类型不能索引获取字符,索引操作预期是常数时间, 而utf-8字列序列并不能保证在常数时间内获取“字符”,rust需要 从头检查。另外,字符串中可能有不可见字符(如发音字符), 即字形簇字符串不等价,此时索引的意义也不明确。

更加有价值的是使用[]和range创建一个字符串slice需要注意的是 如果字符串slice不是有效处utf-8编码序列,程序会在运行时 panic!

1
2
3
4
5
let len = String::from("Здравствуйте").len()
//`len`返回的是utf-8编码序列的长度20,不是“字符”数目12

let hello = "Здравствуйте";
let s = &hello[0..4];

遍历字符串

  • 返回字符Unicode值char类型
    1
    2
    3
    for c in "नमस्ते".chars() {
    println!("{}", c);
    }
    将会打印出6个字符,两个不可见的发音字符
    1
    2
    3
    4
    5
    6






  • 返回字节byte值u8类型
    1
    2
    3
    for b in "नमस्ते".bytes() {
    println!("{}", b);
    }

String常用方法

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
let mut s = String::new();
let s = String::from("initial contet");

let data = "initial content";
let s = data.to_string();
let s = "initial content".to_string();

let mut s = String::from("foo");
s.push_str("bar");
let s2 = "bar";
s.push_str(&s2);
println!(s2);
//此时,`s2`仍然可以打印出来,因为`push_str`的参数是它的
//一个引用,应该是方法中对`&&str`类型做了处理?
s.push('l');

let s1 = String::from("hello, ");
let s2 = String::from("world";
let s3 = s1 + &s2;
//此时`s1`所有权被转移给`s3`不能再使用

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = string::from("toc");
//let s = s1 + "-" + &s2 + "-" + &s3;
let s = format!("{}-{}-{}", s1, s2, s3);
//`format!`宏是更好地连接字符串的方法,且不会获取任何
//参数的所有权

HashMap

HashMap键、值必须是同质的,相对来说使用频率较低,没有引入 prelude,使用之前需要用use关键字引入

HashMap默认使用一种密码学安全的哈希函数,它可以抵抗拒绝 服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的 算法,不过为了更高的安全性值得付出一些性能的代价。可指定不同 hasher来切换为其它函数。hasher是一个实现了BuildHasher trait的类型

常用函数

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
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 30);
//对于没有实现`copy`trait的`string`类型,所有权将转移给
//HashMap
scores.insert(String::from("Blue", 20);
//之前存储的值被覆盖

scores.entry(String::from("Yellow")).or_insert(90);
scores.entry(String::from("Red")).or_insert(90);
//`entry`以想要检查的键作为参数,返回`Entry`类型的枚举
//代表可能存在的值,`or_insert`方法在键对应的值存在时
//返回值`Entry`(实际上时值的可变引用),否则将参数作为
//新值插入,并返回修改后的`Entry`

let text = "hello world wonderful word";
let mut map = HashMap::new();
for word in text.split_whitespace(){
let count = map.entry(word).or_insert(0);
*count += 1;
}
//这段将在`map`中存储`text`中个单词出现次数

let teams = vec![String::from("Blue"), String::from("Yello")];
let initial_scores = vec![10, 30];
let scores: HashMap<_,_> = teams.iter.zip(initial_scores.iter()).collect();
//`collect`可能返回很多不同的数据结构,需要显式指定`scores`
//的类型`HashMap<_,_>`

let team_name = String::from("Blue");
let team_score = scores.get(&team_name);

for (key, val) in &scores{
println!("{}:{}", key, val);
}

智能指针

  • 指针pointer:包含内存地址的变量,这个地址引用(指向) 其他数据
  • 智能指针smart pointer:一类数据结构,表现类似于指针, 拥有额外的元数据和功能

Rust中最常见的指针是引用reference,除了引用数据没有其他 特殊功能,也没有任何额外开销。

智能指针通常由结构体实现,区别于常规结构体的特在于实现了 DerefDroptrait

事实上,StringVec<T>也是智能指针

Dereftrait、DerefMuttrait

  • Dereftrait:重载解不可变引用运算符*
  • DerefMuttrait:重载解可变引用引用运算符*

允许智能指针结构体实例表现得像引用,可以让代码兼容智能指针 和引用

1
2
3
4
5
6
7
8
fn main(){
let x = 5;
let y = &x;
let z = Box::new(5);

assert_eq!(5, x);
assert_eq!(5, *y);
assert_eq!(5, *z);

自定义类型实现Dereftrait

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
struct MyBox<T>(T);
//`Box<T>`从本质上被定义为包含一个元素的元组结构体
//类似定义自定义类型`MyBox`
impl<T> MyBox<T>{
fn new(x: T) -> MyBox<T>{
MyBox(X)
}
}

use::std::Deref;
impl<T> Deref for MyBox<T>{
type Target = T;

fn deref(&self) -> &T{
&sell.0
}
//`deref`返回引用,因为大部分使用解引用时不希望获取
//`MyBox`内部值的所有权
}
//为`MyBox<T>`实现`Deref`trait

fn main(){
let x = 5;
let y = MyBox::new(x);

println!("y = {}", *y);
//对于`*y`Rust实际在底层`*(y.deref())`
}

DerefMuttrait类似

隐式解引用强制多态Deref Coercions

将实现了Dereftrait或DerefMuttrait类型的引用转换为其他 类型的引用,通过多次隐式转换使得实参和型参类型 一致,(这些解析发生在编译时,没有运行时损失)避免多次使用 &* 引用和解引用,也使得代码更容易兼容智能指针和引用。

1
2
3
4
5
6
7
fn hello(name: &str){
println!("hello, {}", name);
}
fn main(){
let m = MyBox::new(String::from("Rust"));
hello(&m);
}

实参类型T和型参类型U满足(间接)

  • T: Deref<Target = U>&T转换为&U
  • T: Deref<Target = U>&mut T转换为&U
  • T: DerefMut<Target = U>&mut T转换为&mut U

相当于在引用外面添加任意层&(*_)&mut(*_),直到实参类型 和型参类型一致

Droptrait

Droptrait要求实现drop方法(析构函数destructor),获取 &mut self可变引用,智能指针离开作用域时运行drop方法中的 代码,用于释放类似于文件或网络连接的资源,编译器会自动插入 这些代码。

  • Droptrait会自动清理代码
  • 所有权系统确drop只会在值不再使用时被调用一次

todo:获取&mut self,那么之前不能获取可变引用了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct CustomSmartPointer{
data: String,
}

impl Drop for CustomSmartPointer{
fn drop(&mut self){
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main(){
let c = CustomSmartPointer{ data: String::from("pointer1")};
let d = CustomSmartPointer{ data: String::from("pointer2")};
println!("CustomSmartPointer created!");
}

输出顺序如下,变量以被创建时相反的顺序丢弃

1
2
3
CustomSmartPointer created!
Dropping CustomSmartPointer with data `pointer1`!
Dropping CustomSmartPointer with data `pointer2`!

Rust不允许显示调用drop函数,因为Rust仍然会在值离开作用域时 调用drop函数导致double free的错误。如果需要提早清理, 可以使用std::mem::drop函数(已经位于prelude中)。

1
2
3
4
5
6
fn man(){
let c = CustomSmartPointer{ data: String::from("some data")};
println!("CumstomSmartPointer Created");
drop(c);
//调用`std::mem::drop`函数,不是`c.drop()`方法
println!("CustomSmartPointer dropped before the end of main");

Box<T>

在堆上存储数据,而栈上存放指向堆数据的指针,常用于

  • 类型编译时大小未知,而想要在确切大小的上下文中使用
  • 大量数据希望在拷贝时不转移所有权
  • 只关心数据是否实现某个trait而不是其具体的类型 (trait对像)
1
2
3
4
let b = Box::new(5);
println!("b = {}", b);
//可以像数据存储在栈上一样访问数据
//box离开作用域时,栈上和指向的堆上的数据都被释放

创建递归类型

Rust需要在编译时知道类型占用的空间,而递归类型(recursive type)中值的一部分可以时相同类型的另一个值,所以Rust无法知道 递归类型占用的空间。而box大小已知,可以在递归类型中插入box 创建递归类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum List{
Cons(i32, Box<list>),
Nil,
//代表递归终止条件的规范名称,表示列表的终止
//不同于`null`或`nil`
}

use List::{Cons, Nil};

fn main(){
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
}

Rc<T>

Rc:引用计数reference counting,记录一个值引用的数量判断 这个值是否仍然被使用,如果值只有0个引用,表示没有任何有效 引用,可以被清理。

Rc<T>允许多个不可变引用,让值有多个所有者共享数据, 引用计数确保任何所有者存在时值有效。用于在堆上分配内存供 程序多个部分读取,且在无法在编译时确定哪部分最后结束使用 (否则令其为所以者即可)

Rc<T>只适合单线程场景

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
enum List{
Cons(i32, Rc<List>),
Nil,
}
//使用`Rc<T>`代替`Box<T>`,可以构造共享List

use List::{Cons, Nil};
use std::rc::Rc;

fn main(){
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
//1

let b = Cons::(3, Rc::clone(&a));
//`Rc::clone`并不像大多数`clone`方法一样对所有数据
//进行深拷贝,只会增加引用计数,允许`a`和`b`**共享**
//`Rc`中数据的所有权
//这里可以调用`a.clone()`代替`Rc::clone(&a)`,但是
//习惯上使用`Rc::cloen(&a)`
println!("count after creating b = {}", Rc::strong_count(&a));
//2

{
let c = Cons::(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
//3
}

println!("count after c goes out of scope = {}", Rc::strong_count(&a));
//2
}

RefCell<T>

RefCell<T>是一个遵守内部可变性模式的类型,允许通过 不可变引用更改T值。实际上仍然是通过可变引用更改值, 只是获得的T的可变引用在RefCell<T>内部。

RefCell<T>同样只能应用于单线程场景

可以理解为,将Rust静态引用改成时分复用引用,Rust在 运行时进时引用检查,只要保证在运行时任意时刻满足引用规则 即可。

内部可变性interior mutability

Rust中的一个设计模式,允许在有不可变引用时改变数据,这违反 了引用规则,因此在该模式中使用unsafe代码模糊Rust通常的 可变性和引用规则。但是引用规则依然适用,只是在运行时检查, 会带来一定运行时损失。

在确保代码运行时遵守借用规则,即使编译器不能保证,可以选择 使用运用内部可变性模式的类型,涉及的unsafe代码被封装进 安全的API中,外部类型依然不可变

RefRefMut

  • Ref = RefCell<T>.borrow():获取T的不可变引用
  • RefMut = RefCell<T>.borrow_mut():获取T的一个可变引用

RefRefMut均是实现Dereftrait的智能指针,RefCell<T> 记录当前活动的RefRefMut指针,调用borrow时,不可变 引用计数加1,Ref离开作用域时不可变引用计数减1

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
pub trait Messenger{
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T:'a + Messenger>{
Messenger:&'a T,
value: usize,
max : usize,
}
//这个结构体有`‘a`和`T`两个泛型参数,且`T`还以生命周期
//注解作为trait bound

impl<'a, T> LimitTracker<a', T>
where T: Messenger{
//这里的`T`就没有加上`‘a`作为trait bound
pub fn new(messenger: &T, max: usize) -> LimitTracker<T>{
LimitTracker{
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value:usize){
self.value = value;
let percentage_of_max = self.max as f64 / self.max as f64;
if percentage_of_max >= 0.75{
self.messenger.send("Warning: over 75% of quota has been used!");
}
}

}

#[cfg(test)]
mod tests{
use supper::*;
use std::cell:RefCell;

struct MockMessenger{
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger{
fn new() -> MockMessenger{
MockMessenger{ sent_messages: RefCell<vec![]> }
}
}
impl Messenger for MockMessenger{
fn send(&self, message: &str){
self.sent_messages.borrow_mut().push(String::from(message));
}
}

#[test]
fn it_send_an_over_75_percent_warning_message(){
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);

assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}

Rc<RefCell<T>>

T值可以修改,且可以被多个所有者拥有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Debug)]
enum List{
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil
}

use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main(){
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

*value.borrow_value += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

RefCell<Rc<T>>

T值不能改变,但是Rc<T>整体可以改变,此时可能出现引用循环 ,导致内存泄露。引用循环是程序逻辑上的bug,Rust无法捕获。

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
use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
enum List{
Cons(i32, RefCell<Rc<list>>),
Nil,
}
impl List{
fn tail(&self) -> Option<&RefCell<R<List>>>{
match *self{
Cons(_, ref item) => Some(item),
Nil => None,
}
}
}

fn main(){
let a = Rc::new(Cons(5, RefCell::new(Rc::New(Nil))));
println!("a initial rc count ={}", Rc::strong_count(&a));
//1
println!("a next item = {:?}", a.tail());

let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creating = {}", Rc::strong_count(&a));
//2
println!("b initial rc count = {}", Rc::strong_count(&b));
//1
println!("b next item = {:?}"<, b.tail());

if let Some(link) = a.tail(){
*link.borrow_mut() = Rc::clone(&b);
//此时`a`、`b`循环引用,离开作用域时,两个值的
//引用计数因为`a`、`b`被丢弃而减1,但是它们互相
//引用,引用计数保持在1,在堆上不会被丢弃
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
//2
println!("a rc count after changing a = {}", Rc::strong_count(&a));
//2
}

Weak<T>

强引用Rc<T>代表共享Rc实例的引用,代表所有权关系, 而弱引用Weak<T>不代表所有权关系,不同于Rc<T>使用 strong_count计数,Weak<T>使用weak_count计数,即使 weak_count无需为0,Rc实例也会被清理(只要strong_count 为0)

  • Weak<T>指向的值可能已丢弃,不能像Rc<T>一样直接解引用 ,需要调用upgrade方法返回Option<Rc<T>>
  • Weak<T>避免Rc<T>可能导致的引用循环
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
use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node{
value: i32,
parent: RefCell<Weak<Node>>,
Children: RefCell<Vec<Rc<Node>>>,
}

fn main(){
let leaf = Rc::new(Node{
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});

println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 1, weak = 0

{
let branch = Rc::new(Node{
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
//`downgrade`返回`Weak<T>`

println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
//strong = 1, weak = 1

println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 2, weak = 0
}

println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
//`upgrade`返回`Option<Rc<T>>`,此例中因为`branch`
//离开作用域已经丢弃,这里返回`None`
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
//strong = 1, weak = 0
//如此不会造成引用循环导致内存泄露
}

比较

智能指针 数据拥有者 引用检查 多线程
Box<T> 单一所有者 编译时执行可变(不可变)引用检查
Rc<T> 多个所有者 编译时执行不可变引用检查
RefCell<T> 单一所有者 运行时执行不可变(可变)引用检查
Weak<T> 不拥有数据 编译时执行可变(不可变)检查

常用枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Option<T>{
Some<T>,
None,
}

some = Some(9);
some.take();
//`take`获取`some`的值所有权作为返回值,并设置`some`为
//`None`

Result<T, U>{
Ok<T>,
Err<U>,
}